views:

2290

answers:

7

I need to generate a unique temporary file with a .csv extension.

What I do right now is

   string filename = System.IO.Path.GetTempFileName().Replace(".tmp", ".csv");

However, this doesn't guarantee that my .csv file will be unique.

I know the chances I ever got a collision are very low (especially if you consider that I don't delete the .tmp files), but this code doesn't looks good to me.

Of course I could manually generate random file names until I eventually find a unique one (which shouldn't be a problem), but I'm curious to know if others have found a nice way to deal with this problem.

+19  A: 

Guaranteed to be (statistically) unique:

string fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";

(To quote from the wiki article on the probabilty of a collision:

...one's annual risk of being hit by a meteorite is estimated to be one chance in 17 billion [19], that means the probability is about 0.00000000006 (6 × 10−11), equivalent to the odds of creating a few tens of trillions of UUIDs in a year and having one duplicate. In other words, only after generating 1 billion UUIDs every second for the next 100 years, the probability of creating just one duplicate would be about 50%. The probability of one duplicate would be about 50% if every person on earth owns 600 million UUIDs

EDIT: Please also see JaredPar's comments.

Mitch Wheat
But not guaranteed to be in a writable location
JaredPar
System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv" will work. @Mitch : could you please update your answer before I mark it as accepted? thanks.
Brann
And they are **not** guaranteed to be unique at all, just statistically unlikely.
paxdiablo
@Brann: I did it for him :)
Gerrie Schenck
@Pax: you have more chnace of winning the lottery 1000 times in a row than generating two idebtical guids. That's unique enough I guess...
Mitch Wheat
@Gerrie Schenck: thanks !
Mitch Wheat
No, Mitch, it's either guaranteed or not. In this case, it's not. The probability of me winning the lottery 1000 times in a row (or a million) is not zero.
paxdiablo
@Pax: let me know when you generate 2 in a row: I think the estimate was age of the universe...
Mitch Wheat
As you wish, Mitch. I can't in all honesty vote up an incorrect answer, especially when it's so easy to fix. So I'll move on.
paxdiablo
@Mitch the reason it's not unique is because it's *possible* for me to simply create a file with the same name in the same path. GUIDs while guaranteed to be unique are also predictable which means given enough information I could guess the next set of guids generated by your box
JaredPar
@Mitch (cont) as such I could create a file with the same name before you return the GUID. When dealing with the filesystem, very little if anything, is guaranteed. I consider this to be a corner case though because you really need a mean spirited user to *beat* your app. I did not -1 you
JaredPar
@Jared Par: I understand the statistics. I'd be interested in that algorithm...
Mitch Wheat
...and of course it depends on the system implmentation of the GUID generator and presumably the pseudo-random generator. I believe some systems use network packet traffic to help randomise...
Mitch Wheat
@Mitch see (http://en.wikipedia.org/wiki/GUID) it discusses the predictability (possible to predict next 250,000 guids). The probability of collision is not the issue here, it's the potential for prediction of the next GUIDs
JaredPar
@JaredPar: that's an excellent point and one I'd overlooked. Thanks.
Mitch Wheat
I guess I'm wondering why you can't just append ".csv" to the filename...
sixlettervariables
Thought I'd swing back for a quick look. Downvote removed, @Mitch, and upvote added.
paxdiablo
+4  A: 

Try this function ...

public static string GetTempFilePathWithExtension(string extension) {
  var path = Path.GetTempPath();
  var fileName = Guid.NewGuid().ToString() + extension;
  return Path.Combine(path, fileName);
}

It will return a full path with the extension of your choice.

Note, it's not guaranteed to produce a unique file name since someone else could have technically already created that file. However the chances of someone guessing the next guid produced by your app and creating it is very very low. It's pretty safe to assume this will be unique.

JaredPar
Perhaps Path.ChangeExtension() would be more elegant than Guid.NewGuid().ToString() + extension
ohadsc
A: 

Why not checking if the file exists?

string fileName;
do
{
    fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";
} while (System.IO.File.Exists(fileName));
Matías
File.Exists tells you information about the past and is hence not reliable. In between the return of File.Exist and your code executing it's possible for the file to be created.
JaredPar
and if you put this code in a lock statement?
Matías
A: 

EDIT: This doesn't work. This returns a TempFileName with a different prefix, (i.e yourprefix23984.tmp), not a different extension. Sorry.


You can also do what GetTempFile does:

[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern uint GetTempFileName(string tmpPath, string prefix, uint uniqueIdOrZero, StringBuilder tmpFileName);

public static string GetTempFileName(string prefix)
{
    string tempPath = GetTempPath();
    new FileIOPermission(FileIOPermissionAccess.Write, tempPath).Demand();
    StringBuilder tmpFileName = new StringBuilder(260);
    if (GetTempFileName(tempPath, prefix, 0, tmpFileName) == 0)
    {
        throw SomeKindOfException;
    }
    return tmpFileName.ToString();
}

This is reflected from GetTempFileName. I only changed "tmp" to a parameter named "extension". This has the benefit of being handled by the file system- it is guaranteed to be unique and to exist when it returns.

configurator
This doesn't actually work because as you've named it, the second parameter to the WinApi call is actually the prefix not a suffix...so it's not an extension at all.
Michael Prewecki
Whoops! I should have read the docs first
configurator
+1  A: 

You can also do the following

string filename = System.IO.Path.ChangeExtension(System.IO.Path.GetTempFileName(), ".csv");

and this also works as expected

string filename = System.IO.Path.ChangeExtension(System.IO.Path.GetTempPath() + Guid.NewGuid().ToString(), ".csv");
Michael Prewecki
this will fail if there is a file temp.csv and you create temp.tmp and then change the extension to csv
David
No it won't...GetTempFileName() creates a unique filename...upto some limit of 32K at which point you need to delete some files but I think my solution is correct. It's wrong if I were to pass a file path into ChangeExtension that isn't guaranteed to be unique, but that's not what my solution does.
Michael Prewecki
GetTempFileName guarantees that the path it returns will be unique. Not that the path it returns + ".csv" will be unique. Changing the extension in this way could fail as David said.
Marcus Griep
+4  A: 

You can also alternatively use System.CodeDom.Compiler.TempFileCollection.

string tempDirectory = @"c:\\temp";
TempFileCollection coll = new TempFileCollection(tempDirectory, true);
string filename = coll.AddExtension("txt", true);
File.WriteAllText(Path.Combine(tempDirectory,filename),"Hello World");

Here I used a txt extension but you can specify whatever you want. I also set the keep flag to true so that the temp file is kept around after use. Unfortunately, TempFileCollection creates one random file per extension. If you need more temp files, you can create multiple instances of TempFileCollection.

Mehmet Aras
A: 

How about:

Path.Combine(Path.GetTempPath(), DateTime.Now.Ticks.ToString() + "_" + Guid.NewGuid().ToString() + ".csv")

It is highly improbable that the computer will generate the same Guid at the same instant of time. The only weakness i see here is the performance impact DateTime.Now.Ticks will add.

I.R.Baboon