Saving multiple files with same name

3.9k views Asked by At

I'm working on a C# WPF application that does some data processing and outputs files in .csv format. What I am trying to achieve is that every time my program runs, it produces a test.csv output file, however when I run again with new data.. the new resultant file overwrites the old one. So I am trying to do something so that each created filename is unique. For example, test.csv, then test1.csv, test2.csv and so forth. I came up with the snippet below but I don't think the logic is correct and I can't figure out how to do this. Any help will be appreciated!

filename = "C:\\test.csv";
if (File.Exists(filename))
{
    int count = 0;
    for (int i = 0; i < 10; i++)
    {    
        filename = "C:\\test" + count + ".csv";
        count++;
    }
}
4

There are 4 answers

8
matan129 On BEST ANSWER

In your original code, you do the check only once. Try (note the while):

filename = "C:\\test.csv";
int count = 0;

while (File.Exists(filename))
{
    count++;
    filename = "C:\\test" + count + ".csv";                            
}

//save file goes here

If you prefer, you can replace the while with this for loop:

for(int count = 0; File.Exists(filename); count++)
    filename = "C:\\test" + count + ".csv";                            

Update: As @eFloh indicated (see comments), there might be an issue when two instances of the above code are working simultaneously.

This is because there's a slight window between the loop and the actual write-to-disk operation, so other process might just get in the middle and write a file with the same name, overwriting the file the other program is creating or causing errors.

One possible solution is to write a special file to the directory when the loop is executing (and halt the execution is such file exists) to ensure no two running instances will conflict.

Essentially, this file will signal the program that another program is executing this loop - so the other program shall wait until the other terminates.

Explanation of the code below: The using block is trying to create a file called lock_file in the directory.

  • The FileMode.OpenOrCreate makes it possible to both open the file (if already exists) or create it (if it does not).
  • The FileAccess.Write make is possible to write the lock_file
  • The FileShare.Read makes the using wait until the fine is closed by other processes - so the program will wait when needed.
  • Finally, FileOptions.DeleteOnClose, makes the using block delete the lock_file on exit. This is extremely important because otherwise, the lock_file will stay there forever, rendering this block to wait forever to the deletion of this file.

To wrap up (again, credit for this piece goes to @eFloh):

using(new FileStream("C:\\lock_file", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 4096, FileOptions.DeleteOnClose))
{
    //loop and saving goes here
}
3
Hamid Pourjam On

you can check until you find available file name with LINQ

var pathFormat = "c:\\test{0}.csv";

var maxIndex = Enumerable.Range(0, int.MaxValue)
    .SkipWhile(x => File.Exists(string.Format(pathFormat, x)))
    .First();

var csvPath = string.Format(pathFormat, maxIndex);
Console.WriteLine(csvPath);
0
Alexander Bell On

Alternatively, you can apply a different file naming convention, like for example: [file name]+timestamp to ensure its uniqueness, thus there will be no need for the duplicate name check:

    DateTime _dt = DateTime.Now;
    string filename = @"C:\test_" + 
        _dt.Year.ToString() +
        _dt.Month.ToString() + 
        _dt.Month.ToString() + 
        _dt.Day.ToString() + "_" +
        _dt.Hour.ToString()+
        _dt.Minute.ToString()+
        _dt.Second.ToString() +".csv";
}

Upon necessity, you can extend it by adding _dt.Millisecond.ToString().Or, instead of using the timestamp you can add GUID to the file name to ensure its uniqueness.

Hope this may help.

0
eFloh On

Because whenever you check and then try to write, you may have someone having created another file in the meantime, you should try-open in a loop to escape the race condition:

            int tryCount = 1;
            string pathFormat = "c:\\test{0}.csv";

            while (tryCount <= MAX_TRYCOUNT)
            {
                try
                {
                    using (FileStream fs = File.Open(String.Format(CultureInfo.InvariantCulture, pathFormat, tryCount), FileMode.CreateNew))
                    {
                        //fs.Write or open StreamWriter etc.
                    }
                }
                catch (IOException)
                {
                    /* try next filename */
                }
                catch (Exception ex)
                {
                    /* other unexpected error, escape. */
                    throw;
                }

                tryCount++;
            }

            if (tryCount == MAX_TRYCOUNT)
            {
                throw new IOException("No free filename available.");
            }

an alternative approach would be to lock all your applications (or application instances) writing at the same time:

string lockFile = Path.Combine(PATH_OF_EXPORTDIR,"SingleInstanceCanWrite");
using(new FileStream(lockFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read, 8 * 1024, FileOptions.DeleteOnClose))
{
   /* find next free filename, open write and close */
}

but this only moves the race condition towards correctly checking the lockfile, you will need a try-catch here, too. :)