Closing a file in C# (Unity) - c#

I am trying to figure out how to work with files and I got confused due to the amount of different methods.
I have used current way of writting to the file. So far it seems to work fine, but I wonder if I have to add a file closing when the user exits the game?
Another questions is why the file gets created without using File.Create()?
if (!File.Exists(path))
{
File.WriteAllText(path, "Date\t\t| ROM \t\t| Left Hand | Right Hand |\n");
}
The whole code is attached:
public class SavingData : MonoBehaviour
{
public static string path = #"C:\users\Desktop\Game1.txt";
void Start()
{
CreateFile();
}
void CreateFile()
{
if (!File.Exists(path))
{
File.WriteAllText(path, "Date\t\t| ROM \t\t| Left Hand | Right Hand |\n");
}
else
{
string date = "Login date: " + System.DateTime.Now + "\n";
File.AppendAllText(path, date);
}
}
public static void WriteToFile()
{
File.AppendAllText(path, "hellow\n");
}
public static void CloseFile()
{
}
}

The documentation of File.WriteAllText states:
Creates a new file, writes the specified string to the file, and then closes the file. If the target file already exists, it is overwritten.
So this single method does all the things that you thought you didn't do - creating the file and closing it.
If you would like to close the file manually, don't use File.WriteAllText and File.AppendAllText. Use another way of writing to a file, like a StreamWriter.
This is what I would write if I had to use StreamWriter.
public class SavingData : MonoBehaviour
{
public string path = #"C:\users\Desktop\Game1.txt";
private StreamWriter writer;
void Start()
{
CreateFile();
}
void CreateFile()
{
writer = new StreamWriter(path, true);
if (!File.Exists(path))
{
writer.WriteLine("Date\t\t| ROM \t\t| Left Hand | Right Hand |\n");
}
else
{
string date = "Login date: " + System.DateTime.Now + "\n";
writer.WriteLine(date);
}
}
public void WriteToFile()
{
writer.WriteLine("hellow\n");
}
public void CloseFile()
{
writer.Close();
}
}

Actually, if you want more control you need to use FileStream. It gives you more control while writing into files. It allows you to keep the file handle open and just write data without any additional control.
But FileStream also has some type of disadvantages.
From the documentation
When a FileStream object does not have an exclusive hold on its
handle, another thread could access the filehandle concurrently and
change the position of the operating system's file pointer that is
associated with the filehandle. In this case, the cached position in
the FileStream object and the cached data in the buffer could be
compromised. The FileStream object routinely performs checks on
methods that access the cached buffer to ensure that the operating
system's handle position is the same as the cached position used by
the FileStream object.
On the other hands :
System.IO.File contains wrappers around file operations for basic actions such as saving a file, reading a file to lines, etc. It's simply an abstraction over FileStream.
So WriteAllText is the abstraction for over the Create, Save and Close and automatically doing it and you don't need to know each of the implementations.
So the basic answer to your question is: NO, you don't need to manually close file, it will do it automatically.

Related

Multiple threads writes multiple files same time - it is being used by another process

In our WCF service the clients can upload file chunks. Several clients uploads files, some clients uploads on multiple threads. To serialize file access on server side we created a dictionary to lock the given file access. When a thread wants to access a file for writing - checks this dictionary if it contains or not the given filename. If it contains - it signs that a thread is currently access the file. To release the lock we simple removes the filename from the dictionary.
public class FileUploader
{
private static readonly Dictionary FileStreams = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
public void SaveChunk(String storagePath, File file, Int64 currentPos, Byte[] chunk)
{
using (var writer = new BinaryWriter(OpenFileStream(storagePath)))
{
try
{
SaveChunk(writer, currentPos, chunk);
}
finally
{
writer.Close();
RemoveLock(storagePath);
}
}
}
private Stream OpenFileStream(String fullFileName)
{
Stream result = null;
lock (FileStreams)
{
while (FileStreams.ContainsKey(fullFileName))
{
Monitor.Wait(FileStreams);
}
result = System.IO.File.Open(fullFileName, FileMode.OpenOrCreate, FileAccess.Write);
FileStreams[fullFileName] = true;
}
return result;
}
private void RemoveLock(String fileName)
{
lock (FileStreams)
{
FileStreams.Remove(fileName);
Monitor.Pulse(FileStreams);
}
}
Somehow we spmetime got an error message:
The process cannot access the file '\ourstorage\folder\a_file_name.zip'
because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
But we cannot see why. It seems the code is correct, and wont access two threads same file same time. Any idea?
You write with different threads in the same file. You have to check in every thread, if you are already writing in this file.

Why doesn't File.WriteAllText write data to file?

I've got a problem that has apparently been encountered before, though the solutions then don't help with what I'm seeing.
I'm trying to write data to a file, based on a base 64 encoding of the contents of a Dictionary<string, object>. It's supposed to go like this: if the file doesn't exist,
It gets created
Some default values get added to the Dictionary
The Dictionary is turned into base 64 data
The data is written to the file
Steps 1 through 3 are working fine. Step 4 appears to be working fine, until you I open the file - when it's revealed that there's nothing in it. It gets created but not written to, and I end up with an empty file.
The code goes like this, where the top method (CreateDefaultConfigFile is called if the file doesn't exist):
private static void CreateDefaultConfigFile()
{
Console.WriteLine("AppConfig.CreateDefaultConfigFile");
File.Create(CONFIG_FILE_PATH);
Configuration.Clear();
Configuration.Add("ClientId", "5577");
Configuration.Add("RedirectUri", "https://stackexchange.com/oauth/login_success");
Configuration.Add("RequestKey", "2WQ5ksCzcYLeeYJ0qM4kHw((");
Save();
}
public static void Save()
{
Console.WriteLine("AppConfig.Save");
string data = "";
foreach (KeyValuePair<string, object> pair in Configuration)
{
Console.WriteLine(" {0}: {1}", pair.Key, pair.Value.ToString());
if (pair.Value.GetType() == typeof(string))
{
data += pair.Key + SC_SPLITTER + pair.Value + "\n";
}
else if (pair.Value.GetType() == typeof(Array))
{
data += pair.Key + SC_SPLITTER + "[" + string.Join(",", pair.Value) + "]\n";
}
else
{
Configuration.Remove(pair.Key);
}
}
if (data.EndsWith("\n"))
{
data.Remove(data.Length - 2);
}
byte[] dataBytes = Encoding.UTF8.GetBytes(data);
string encoded = Convert.ToBase64String(dataBytes);
File.WriteAllText(CONFIG_FILE_PATH, encoded);
Console.WriteLine(" Written to file.");
}
Important fact to note: the "Written to file." message never gets logged to the console (though if I put a log directly before the File.WriteAllLines call, it does log). A breakpoint at the final Console.Log call never raises.
No exceptions are thrown, and it's not because data or encoded are empty - logging them just before the write reveals data in both.
CONFIG_FILE_PATH is a constant value of C:\Users\Me\Documents\longpath\bin\Debug\config\general.cgf.
I've also tried using a FileStream and FileStream.Flush(), as suggested in the question I linked at the top - this doesn't work.
The File.Create method doesn't do what you appear to think that it does.
It does create a file, but it also leaves the file open and returns a FileStream object to handle the open file.
If you just call Create and ignore the returned FileStream object, then the file will be left open until the object is disposed by the garbage collector.
Depending on when the garbage collection runs, the File.WriteAllText call will either be able to write to the file, or you will get an IOException. The fact that you don't see anything written to the file, and that you don't see the text that is written out after the call, suggests that you actually get an exception but catch that at some other level and ignore it.
If you want to use File.Create to create the file, you should get the FileStream object and dispose it to close the file:
using (FileStream stream = File.Create(CONFIG_FILE_PATH)) {
}
However, you don't have to create the file before calling File.WriteAllText, it will create the file if it doesn't exist.

IO Exception - File being used by another process (Can't open file following directory creation)

I have got a project on the go that monitors patients for a vet while they are being operated on and writes the result to a text file. While I was experimenting with the outputting I just let the files save in the Debug folder, which worked fine. However, I've now created a full directory that creates or opens a main folder, and then a sub folder (based on input text from the program), to save the text file into.
private void createDirectory()
{ //create output file in this folder using owner name and current date
//main folder path (contains all files output from system)
string rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\Horse Monitoring Records";
//sub folder path (each patient has individual subfolder)
string subDirectory = rootDirectory + "\\" + txtPatName.Text + "-" + txtOwnerName.Text;
//file name (patient has file created for each operation)
fileName = subDirectory + "\\" + txtOwnerName.Text + "-" + DateTime.Now.Date.ToString("ddMMyyyy") + ".txt";
if (!Directory.Exists(rootDirectory)) //if main folder does not exist...
{
Directory.CreateDirectory(rootDirectory); //create it in My Documents
}
if (!Directory.Exists(subDirectory)) //if patient sub folder does not exist...
{
Directory.CreateDirectory(subDirectory); //create it in Patient-Owner format
}
if (!File.Exists(fileName)) //if monitoring file does not exist...
{
File.Create(fileName); //create it in Owner-Date format
}
}
This stage works fine, but as soon as you try to save some data to the text file, it throws to a run time error stating that
The file cannot be accessed because it is being used by another process.
The exception is brought up here:
private void saveFileDetails()
{
//Once case details have been entered, create new file using these details and add data input structure
StreamWriter consoleFile = new StreamWriter(fileName);
...
}
When I went and checked out the folder, the relevant sub-folder and file had been created but the text file was blank.
I'm guessing it's something to do with closing the text file after creating the directory, which means it's already open when the system tries to open it. I can't figure out how to sort this issue out though!
The two functions shown above are called like this:
private void btnStart_Click(object sender, EventArgs e)
{
...
//file details entered upon load written to new file - according to PatID
createDirectory();
saveFileDetails();
}
Any suggestions on where to go from here would be very much appreciated!
Thanks,
Mark
The issue here is that you do
if (!File.Exists(fileName)) //if monitoring file does not exist...
{
File.Create(fileName); //create it in Owner-Date format
}
Right before you try to write to the file. Because you've just created it (if it didn't exist), chances are that the operating system hasn't released the file yet.
Like #Jauch mentioned in the comments, you could skip this check completely and use the StreamWriter overload which will create file if it doesn't exist, or append to it if it does.
private void saveFileDetails()
{
//Once case details have been entered, create new file using these details and add data input structure
using (StreamWriter consoleFile = new StreamWriter(fileName, true))
{
// ...
}
}
Alternatively you can use the following to write all of your text at once:
File.AppendAllText(textToWrite, fileName);
File.Create(fileName) returns an open stream to the file which is never closed.
To create an empty file use File.WriteAllBytes(fileName, new byte[0]);
Otherwise the 2 methods can be shortend
private void SaveFileDetails()
{
string subDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"Horse Monitoring Records");
// create the folder hierarchy if not exists. does nothing if already there
Directory.CreateDirectory(subDirectory);
// each patient has individual file
var filepath = Path.Combine(subDirectory,
txtPatName.Text + "-" + txtOwnerName.Text + "-" + DateTime.Now.Date.ToString("yyyyMMdd") + ".txt");
// creates the file if not exists
using (var writer = new StreamWriter(filepath, append: true, encoding: Encoding.UTF8))
{
// write details
}
}
Note:
merged 2 methods
.NET naming conventions applied
changed dateformat to better sort by name in explorer
StreamWriter implements IDisposable, so wrapping it in a using block can manage closing and disposing the writer and ensuring it is available the next time you want to touch that file. It can also manage creating the text file if it doesn't exist, removing the need to explicitly call File.Create.
StreamWriter consoleFile = new StreamWriter(fileName);
becomes
using (StreamWriter writer = File.AppendText("log.txt"))
{
// writing, etc.
}
or
using (StreamWriter writer = new StreamWriter(fileName, true))
{ // true says "append to file if it exists, create if it doesn't"
// writing, etc.
}
Whatever seems more readable to you will work fine.

Why does my StreamWriter not write to file?

I'm making a log for my program using a StreamWriter to write to a file. I have some code that looks like it works, but its only doing part of what it should.
I made a simple class called Log (code below).
public class Log
{
string path;
StreamWriter fs;
public Log(string fullPathToLogFile)
{
path = fullPathToLogFile;
fs = new StreamWriter(path);
writeLog("Starting log");
}
public void writeLog(string s)
{
fs.WriteLine(DateTime.Now.ToString("dd-MM-yyyy H:mm:ss ") + s);
}
public void closeLog()
{
writeLog("Closing log");
fs.WriteLine(); //add a blank line at the end
fs.Close();
}
}
I made a simple test program that works perfectly. It executes these three lines:
Log l = new Log(#"C:\Users\SADunkerton\Desktop\l.txt");
l.writeLog("testing log");
l.closeLog();
But in my much larger program, where I actually want to use the Log class, all I get is an empty file --no text inside. Its code looks like this:
Log log = new Log(folderPDFs + #"\Log.txt"); //folderPDFs is a parameter of this method--it is a string that is a complete path to a destination folder.
log.writeLog("Beginning conversions");
//do some stuff, including write to the log
log.writeLog("Finished converting. Success = " + success);
Can anyone tell me why the program version of this code isn't working?
I would rewrite your Log class to avoid the close part.
Just open, write and close after finishing the write part
class MySimpleLog
{
private string _filename;
public MySimpleLog(string filename)
{
_filename = filename;
}
public void AppendText(string msg)
{
// Create an instance of StreamWriter to write text to a file.
// The using statement also closes the StreamWriter.
using (StreamWriter sw = new StreamWriter(_filename, true))
{
// Add some text to the file.
sw.WriteLine(msg);
}
}
}
In this way the using statement will close the stream and you don't have to worry about closing it. Indeed closing the stream could be very troublesome if something unexpected happens in your code. (Like an exception that change your code flow).
This is just a starting point to have the code tested and verified, but you could add some more complex logic following the pattern given. For example you could add a constructor with a flag to add a timestamp for everyline (or a flag to add a separator line, or a flag to recreate the file if exists...)
class MySimpleLog
{
private string _filename;
private bool _addtime;
public MySimpleLog(string filename)
{
_filename = filename;
}
public MySimpleLog(string filename, bool addtime)
{
_filename = filename;
_addtime = addtime;
}
public void AppendText(string msg)
{
// Create an instance of StreamWriter to write text to a file.
// The using statement also closes the StreamWriter.
using (StreamWriter sw = new StreamWriter(_filename, true))
{
// Add some text to the file.
msg = (_addtime ? DateTime.Now.ToString() + ": " + msg : msg);
sw.WriteLine(msg);
}
}
}
FINALLY: Keep in mind that specialized log libraries exists well tested and free to use. Perhaps you could invest some of your time in learning them
Log4Net
NLog
1. You do not close the stream in the second example - and what will happen on garbage collection I am not sure. Call the l.closeLog(); in the end to at least get some output. But it is not a good idea. What will happen if some method throws between l.write(); and l.closeLog;. Something not good - file will left opened till GC deals with it.
2. You overwrite the file with each call. Probably what you need is to append the data - http://msdn.microsoft.com/en-us/library/3zc0w663(v=vs.110).aspx or even better change your code to use File.AppendText method:
public void writeLog(string s)
{
using (StreamWriter sw = File.AppendText(path))
{
sw.WriteLine(DateTime.Now.ToString("dd-MM-yyyy H:mm:ss ") + s);
}
}
And remove the closeLog method, because it is unnecessary.
EDIT:
The best idea is to just use stateless standard methods(as pointed by #leppie), that won't leak any resources:
It is File.WriteAllText if you create one logfile for each log instance:
public void writeLog(string s)
{
File.WriteAllText(path,
DateTime.Now.ToString("dd-MM-yyyy H:mm:ss ") + s);
}
or File.AppendAllText if you need to continue already existing logs:
public void writeLog(string s)
{
File.AppendAllText(path,
DateTime.Now.ToString("dd-MM-yyyy H:mm:ss ") + s);
}

Create new file with StreamWriter everytime the program is executed

I am making simple tool for manipulating images in a database. I want to show the output result in a txt file and because the outcome may be different each time, I want the file to be rewritten with the fresh data every time the data is executed.
Also I want (if possible) to use some default location where the txt file will be created even though I have an App.Config file and that's also an option.
The problem I am having is with this code:
string Resultfile =
System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) +
"\\PictureStatus.txt";
FileStream strm = new FileStream(Resultfile , FileMode.Create);
TextWriter tw = new StreamWriter(strm);
This populates the PictureStatus.txt only once and then I get the same text over and over again. I noticed that if I use some random destination the file is updated. Not sure if it's just random behavior or have something to do with using MyDocuments, but what I need is a way to be sure that I'll rewrite the file with the new data each time, and if possible, use some default destination that will work for other people.
You can try something like this
public partial Form2 : Form
{
public string path = Environment.CurrentDirectory + "/" + "Name.txt";
public Form2()
{
InitializeComponent();
if (!File.Exists(path))
{
File.Create(path);
}
}
private void button2_Click(object sender, EventArgs e)
{
using (StreamWriter sw = new StreamWriter(path, true))
{
sw.WriteLine("This text will be writen in the txt file", true);
sw.Close();
}
}
}
I have add to the button, when I pressed it will be written in the next line every time. If you remove "true" from code, it will be overwritten every time.

Categories