5

I'm looking for something similar to what would have a signature like this:

static bool TryCreateFile(string path);

This needs to avoid potential race conditions across threads, processes, and even other machines accessing the same filesystem, without requiring the current user to have any more permissions than they would need for File.Create. Currently, I have the following code, which I don't particularly like:

static bool TryCreateFile(string path)
{
    try
    {
        // If we were able to successfully create the file,
        // return true and close it.
        using (File.Open(path, FileMode.CreateNew))
        {
            return true;
        }
    }
    catch (IOException)
    {
        // We want to rethrow the exception if the File.Open call failed
        // for a reason other than that it already existed.
        if (!File.Exists(path))
        {
            throw;
        }
    }

    return false;
}

Is there another way to do this that I'm missing?

This fits into the following helper method, designed to create the "next" sequential empty file for the directory and return its path, again avoiding potential race conditions across threads, processes, and even other machines accessing the same filesystem. So I guess a valid solution could involve a different approach to this:

static string GetNextFileName(string directoryPath)
{
    while (true)
    {
        IEnumerable<int?> fileNumbers = Directory.EnumerateFiles(directoryPath)
                                                 .Select(int.Parse)
                                                 .Cast<int?>();
        int nextNumber = (fileNumbers.Max() ?? 0) + 1;
        string fileName = Path.Combine(directoryPath, nextNumber.ToString());
        if (TryCreateFile(fileName))
        {
            return fileName;
        }
    }
}

Edit1: We can assume that files will not be deleted from the directory while this code is executing.

Joe Amenta
  • 4,662
  • 2
  • 29
  • 38
  • 1
    You are closing (instead of returning) the created FileStream, is that on purpose? – H H Jul 25 '12 at 14:29
  • Note that fileNumbers.Max() will throw an exception if there are no files. – Carra Jul 25 '12 at 14:31
  • @HenkHolterman - yes, this is on purpose. The code will create an empty file (ensuring that other threads, processes, and machines will pick up on it) with that filename. If it didn't throw, then that filename is good. – Joe Amenta Jul 25 '12 at 17:40
  • @Carra - Because of the `.Cast()` call, this will return `null` if there are no files, which will then be coalesced to zero. – Joe Amenta Jul 25 '12 at 17:42
  • @Joe - Check the msdn page, it throws an InvalidOperationException when there are no elements in your IEnumerable. – Carra Jul 25 '12 at 19:39
  • If you're willing to use P/Invoke, the Win32 CreateFile function should be suitable. – Harry Johnston Jul 25 '12 at 19:43
  • @Carra - The MSDN documentation for [Enumerable.Max(IEnumerable)](http://msdn.microsoft.com/en-us/library/bb302925.aspx) says the following: "If the source sequence is empty or contains only values that are **null**, this function returns **null**." – Joe Amenta Jul 25 '12 at 20:02
  • @Joe - You're 100% right. I was looking at the IEnumerable.Max. – Carra Jul 26 '12 at 06:58

3 Answers3

3

No, there is no direct way and no way to avoid the exception handling.

Even when you try to open an existing file, like

if (File.Exists(fName))
   var s = File.OpenRead(fname);

you can still get all kinds of exceptions, including FileNotFound.

This is because of all the reasons you mentioned:

across threads, processes, and even other machines

But you may want to take a look at System.IO.Path.GetRandomFileName(). I think his i based on a WinAPI function that lets you specify a path etc.

H H
  • 263,252
  • 30
  • 330
  • 514
1

Try it this way:

    private bool TryCreateFile(string path)
    {
        try
        {
            FileStream fs = File.Create(path);
            fs.Close();
            return true;
        }
        catch 
        {
            return false;
        }
    }

There is actually no batter way of checking if file has been created or not. There is none any inbuid function of any kind.

Mitja Bonca
  • 4,268
  • 5
  • 24
  • 30
0

This is actually a truly complicated issue.

It's pretty hard to have a true thread-safe method. In your TryCreateFile, imagine someone removes the file from another process just after its creation, before you tested File.Exists? Your code would throw an exception.

If your primary goal is

to create the "next" sequential empty file for the directory and return its path

, I wouldn't try to test if a file exists. I would assume a file with a GUID as name is always unique:

private static void Main(string[] args)
{
    var z = GetNextFileName(@"c:\temp");

    Console.ReadLine();
}

public static string GetNextFileName(string directoryPath)
{
    // Gets file name
    string fileName = Guid.NewGuid().ToString();
    string filePath = Path.Combine(directoryPath, fileName);

    // Creates an empty file
    using (var z = File.Open(filePath, FileMode.CreateNew))
    {
    }

    return filePath;
}

EDIT: For people who are asking if a GUID is truly unique, see Is a GUID unique 100% of the time?

Community
  • 1
  • 1
ken2k
  • 48,145
  • 10
  • 116
  • 176
  • You raise a valid point -- see the above edit: We can assume that files will not be deleted from the directory while this code is executing. As far as the GUID idea goes -- I considered using GUIDs to ensure uniqueness and FileInfo.CreationTime to get ordering; because FileInfo.CreationTime has a resolution of 1ms, however, the possibility exists for inversions when the files are requested very close to one another in time, so we cannot rely on that. – Joe Amenta Jul 25 '12 at 17:47
  • @JoeAmenta You're saying something interesting I didn't understood the first time I read your question: the file creation order is important for you. Please add more background about how you `use` your files after creation, maybe one can provide a good answer based on the safe GUID way. BTW, I edited the initial answer to add some precisions about GUIDs. – ken2k Jul 25 '12 at 18:19