Como posso criar um arquivo temporário com uma extensão específica no .NET?

277

Preciso gerar um arquivo temporário exclusivo com uma extensão .csv.

O que eu faço agora é

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

No entanto, isso não garante que meu arquivo .csv seja exclusivo.

Sei que as chances de colisão são muito baixas (especialmente se você considerar que eu não excluo os arquivos .tmp), mas esse código não parece bom para mim.

É claro que eu poderia gerar manualmente nomes de arquivos aleatórios até encontrar um único (o que não deve ser um problema), mas estou curioso para saber se outras pessoas encontraram uma boa maneira de lidar com esse problema.

Brann
fonte
4
algumas advertências sobre GetTempFileName O método GetTempFileName gerará um IOException se for usado para criar mais de 65535 arquivos sem excluir arquivos temporários anteriores. O método GetTempFileName gerará uma IOException se nenhum nome de arquivo temporário exclusivo estiver disponível. Para resolver esse erro, exclua todos os arquivos temporários desnecessários.
Max Hodges
2
Os arquivos temporários são principalmente usados ​​para um conjunto específico de condições. Se a extensão do arquivo for importante, será que talvez usar o GetTempFileName não seja a solução de gravação. Sei que já faz muito tempo, mas se você nos disser mais sobre o contexto e a necessidade desses arquivos, poderemos sugerir uma abordagem melhor por completo. mais aqui: support.microsoft.com/kb/92635?wa=wsignin1.0
Max Hodges
1
Lembre-se de GetTempFileName() criar um novo arquivo sempre que você o chamar. - Se você alterar imediatamente a string para outra coisa, você acabou de criar um novo arquivo de zero byte no diretório temporário (e como outros observaram, isso acabará causando uma falha ao atingir 65535 arquivos lá ...) - - Para evitar isso, exclua todos os arquivos criados nessa pasta (incluindo os retornados por GetTempFileName(), idealmente em um bloco final).
BrainSlugs83

Respostas:

355

Garantido para ser (estatisticamente) único:

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

(Para citar o artigo da wiki sobre a probabilidade de uma colisão:

... o risco anual de alguém de ser atingido por um meteorito é estimado em uma chance em 17 bilhões [19], o que significa que a probabilidade é de 0,00000000006 (6 × 10-11), equivalente às chances de criar algumas dezenas de trilhões de UUIDs em um ano e com uma duplicata. Em outras palavras, somente após gerar 1 bilhão de UUIDs a cada segundo nos próximos 100 anos, a probabilidade de criar apenas uma duplicata seria de cerca de 50%. A probabilidade de uma duplicata seria de cerca de 50% se todas as pessoas na Terra possuírem 600 milhões de UUIDs

EDIT: Por favor, veja também os comentários de JaredPar.

Mitch Wheat
fonte
29
Mas não garantido para estar em um local gravável
JaredPar
33
E eles são não garantido para ser único em tudo, só estatisticamente improvável.
23420
30
@Pax: você tem mais chances de ganhar na loteria 1000 vezes seguidas do que gerar dois guias idebticos. Isso é o suficiente única eu acho ...
Mitch Wheat
11
@ O motivo pelo qual não é exclusivo é porque é possível criar um arquivo com o mesmo nome no mesmo caminho. GUIDs enquanto a garantia de ser único também são previsíveis quais os meios dado informações suficientes que eu poderia adivinhar o próximo conjunto de guids gerados pelo seu caixa
JaredPar
207
bom senhor, esforce-se mais para manter a cabeça fora das nuvens. A abordagem é: gere um nome de arquivo aleatório e crie-o se ele não existir. Então, apenas ajude-o a codificar isso muito bem. Toda essa conversa sobre geradores pseudo-aleatórios e números universalmente únicos é totalmente desnecessária.
Max Hodges
58

Tente esta função ...

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

Ele retornará um caminho completo com a extensão de sua escolha.

Observe que não é garantido que você produza um nome de arquivo exclusivo, pois outra pessoa já pode ter criado esse arquivo tecnicamente. No entanto, as chances de alguém adivinhar o próximo guia produzido pelo seu aplicativo e criá-lo são muito muito baixas. É bastante seguro assumir que isso será único.

JaredPar
fonte
4
Talvez Path.ChangeExtension () seria mais elegante do que Guid.NewGuid () ToString () + extensão.
Ohad Schneider
@ ohadsc - de fato, Guid.NewGuid().ToString() + extensionnão é nem correto, deveria ser #Guid.NewGuid().ToString() + "." + extension
Stephen Swensen
4
Suponho que depende do contrato do método (se espera .txtou txt) mas desde ChangeExtensionalças ambos os casos, não pode ferir
Ohad Schneider
1
Chamá-lo de sufixo em vez de Extensão e todo mundo está feliz eu acho
Chiel ten Brinke
46
public static string GetTempFileName(string extension)
{
  int attempt = 0;
  while (true)
  {
    string fileName = Path.GetRandomFileName();
    fileName = Path.ChangeExtension(fileName, extension);
    fileName = Path.Combine(Path.GetTempPath(), fileName);

    try
    {
      using (new FileStream(fileName, FileMode.CreateNew)) { }
      return fileName;
    }
    catch (IOException ex)
    {
      if (++attempt == 10)
        throw new IOException("No unique temporary file name is available.", ex);
    }
  }
}

Nota: isso funciona como Path.GetTempFileName. Um arquivo vazio é criado para reservar o nome do arquivo. Faz 10 tentativas, no caso de colisões geradas por Path.GetRandomFileName ();

Maxence
fonte
Lembre-se de que, Path.GetRandomFileName()na verdade, cria um arquivo de zero byte no disco e retorna o caminho completo desse arquivo. Você não está usando este arquivo, apenas seu nome para alterar a extensão. Portanto, se você não tiver certeza de que está excluindo esses arquivos temporários, após 65535 chamar esta função, ela começará a falhar.
Vladimir Baranov
7
Você misturou GetTempFileName()e GetRandomFileName(). GetTempFileName()crie um arquivo de zero byte como meu método, mas GetRandomFileName()não cria um arquivo. Na documentação:> Diferentemente de GetTempFileName, GetRandomFileName não cria um arquivo. Seu link aponta para a página errada.
Maxence
19

Como alternativa, você também pode usar 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");

Aqui eu usei uma extensão txt, mas você pode especificar o que quiser. Também defino o sinalizador keep como true para que o arquivo temporário seja mantido após o uso. Infelizmente, TempFileCollection cria um arquivo aleatório por extensão. Se você precisar de mais arquivos temporários, poderá criar várias instâncias do TempFileCollection.

Mehmet Aras
fonte
10

Por que não verificar se o arquivo existe?

string fileName;
do
{
    fileName = System.IO.Path.GetTempPath() + Guid.NewGuid().ToString() + ".csv";
} while (System.IO.File.Exists(fileName));
Matías
fonte
9
File.Exists informa informações sobre o passado e, portanto, não é confiável. Entre o retorno do File.Exist e a execução do seu código, é possível que o arquivo seja criado.
24410 JaredPar
4
... então você está seguro com o seu próprio programa, mas talvez não com outro processo de escrever um arquivo em exata que mesmo destino ...
Koen
@JaredPar e qual é a propabilidade disso acontecer?
Migol
2
@Migol É muito baixo e, por definição, uma condição excepcional. Hmmm, exatamente para que exceções foram projetadas para serem usadas.
Cody Gray
3
A chance de @CodyGray para choque de guid é 1/2 ^ 128. A chance de isso acontecer 2 vezes é 1/2 ^ 256 etc. Não se preocupe!
Migol
9

A documentação do MSDN para GetTempFileName do C ++ discute sua preocupação e a responde:

GetTempFileName não pode garantir que o nome do arquivo seja exclusivo .

Somente os 16 bits inferiores do parâmetro uUnique são usados. Isso limita GetTempFileName a um máximo de 65.535 nomes de arquivos exclusivos se os parâmetros lpPathName e lpPrefixString permanecerem os mesmos.

Devido ao algoritmo usado para gerar nomes de arquivos, GetTempFileName pode ter um desempenho ruim ao criar um grande número de arquivos com o mesmo prefixo. Nesses casos, é recomendável que você construa nomes de arquivos exclusivos com base nos GUIDs .

Coronel Panic
fonte
2
O método GetTempFileName gerará uma IOException se for usado para criar mais de 65535 arquivos sem excluir arquivos temporários anteriores. O método GetTempFileName gerará uma IOException se nenhum nome de arquivo temporário exclusivo estiver disponível. Para resolver esse erro, exclua todos os arquivos temporários desnecessários.
Max Hodges
Essa é uma citação incompleta. A citação relevante: " Se uUnique não for zero , você mesmo deverá criar o arquivo. Somente um nome de arquivo será criado, porque GetTempFileName não poderá garantir que o nome do arquivo seja exclusivo." Se você chamar da maneira que todos estão discutindo aqui, o uUnique será zero.
jnm2
6

E se:

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

É altamente improvável que o computador gere o mesmo Guid no mesmo instante de tempo. A única fraqueza que vejo aqui é o impacto no desempenho que o DateTime.Now.Ticks adicionará.

IRBaboon
fonte
5

Você também pode fazer o seguinte

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

e isso também funciona como esperado

string filename = Path.ChangeExtension(Path.GetTempPath() + Guid.NewGuid().ToString(), ".csv");
Michael Prewecki
fonte
2
este irá falhar se houver uma Temp.csv arquivo e você criar temp.tmp e altere a extensão para CSV
David
Não, não vai ... GetTempFileName () cria um nome de arquivo exclusivo ... até um limite de 32K, no ponto em que você precisa excluir alguns arquivos, mas acho que minha solução está correta. É errado passar um caminho de arquivo para o ChangeExtension que não garante a exclusividade, mas não é isso que minha solução faz.
25140 Michael Prewecki
11
GetTempFileName garante que o caminho retornado será exclusivo. Não que o caminho que ele retorne + ".csv" seja único. Alterar a extensão dessa maneira pode falhar, como disse David.
Marcus Griep 14/07/2009
5
GetTempFileName cria um arquivo, portanto, seu primeiro exemplo é um vazamento de recurso.
Gary McGill
3

Na minha opinião, a maioria das respostas propostas aqui é subótima. O que mais se aproxima é o original proposto inicialmente por Brann.

Um nome de arquivo temporário deve ser

  • Único
  • Sem conflitos (ainda não existe)
  • Atômico (criação de nome e arquivo na mesma operação)
  • Difícil de adivinhar

Devido a esses requisitos, não é uma boa idéia programar um animal desses por conta própria. Pessoas inteligentes que escrevem bibliotecas de E / S se preocupam com coisas como bloqueio (se necessário) etc. Portanto, não vejo necessidade de reescrever System.IO.Path.GetTempFileName ().

Isso, mesmo que pareça desajeitado, deve fazer o trabalho:

//Note that this already *creates* the file
string filename1 = System.IO.Path.GetTempFileName()
// Rename and move
filename = filename.Replace(".tmp", ".csv");
File.Move(filename1 , filename);
Daniel C. Oderbolz
fonte
2
Mas isso não é livre de conflitos, o 'csv' já pode existir e ser substituído.
X11
3
File.Moveaumenta IOExceptionse o arquivo de destino já existir. msdn.microsoft.com/pt-br/library/…
joshuanapoli 9/17
2

Isso pode ser útil para você ... É criar uma temperatura. pasta e retorne-a como uma string no VB.NET.

Facilmente conversível em C #:

Public Function GetTempDirectory() As String
    Dim mpath As String
    Do
        mpath = System.IO.Path.Combine(System.IO.Path.GetTempPath, System.IO.Path.GetRandomFileName)
    Loop While System.IO.Directory.Exists(mpath) Or System.IO.File.Exists(mpath)
    System.IO.Directory.CreateDirectory(mpath)
    Return mpath
End Function
Simon
fonte
2

Isso parece funcionar bem para mim: verifica a existência do arquivo e cria o arquivo para garantir que seja um local gravável. Deve funcionar bem, você pode alterá-lo para retornar diretamente o FileStream (que normalmente é o que você precisa para um arquivo temporário):

private string GetTempFile(string fileExtension)
{
  string temp = System.IO.Path.GetTempPath();
  string res = string.Empty;
  while (true) {
    res = string.Format("{0}.{1}", Guid.NewGuid().ToString(), fileExtension);
    res = System.IO.Path.Combine(temp, res);
    if (!System.IO.File.Exists(res)) {
      try {
        System.IO.FileStream s = System.IO.File.Create(res);
        s.Close();
        break;
      }
      catch (Exception) {

      }
    }
  }
  return res;
} // GetTempFile
Paolo Iommarini
fonte
2

Função Fácil em C #:

public static string GetTempFileName(string extension = "csv")
{
    return Path.ChangeExtension(Path.GetTempFileName(), extension);
}
Ranch Camal
fonte
GetTempFileName () cria um arquivo temporário na pasta temp. Como resultado, você terá dois arquivos temporários criados, um dos quais vazará.
evgenybf 19/06
1

Com base nas respostas que encontrei na internet, chego ao meu código da seguinte maneira:

public static string GetTemporaryFileName()
{       
    string tempFilePath = Path.Combine(Path.GetTempPath(), "SnapshotTemp");
    Directory.Delete(tempFilePath, true);
    Directory.CreateDirectory(tempFilePath);
    return Path.Combine(tempFilePath, DateTime.Now.ToString("MMddHHmm") + "-" + Guid.NewGuid().ToString() + ".png");
}

E, como livro de receitas do C # de Jay Hilyard, Stephen Teilhet apontou em Usando um arquivo temporário no seu aplicativo :

  • você deve usar um arquivo temporário sempre que precisar armazenar informações temporariamente para recuperação posterior.

  • A única coisa que você deve se lembrar é excluir esse arquivo temporário antes que o aplicativo que o criou seja finalizado.

  • Se não for excluído, ele permanecerá no diretório temporário do usuário até que o usuário o exclua manualmente.

Speedoops
fonte
1

Eu misturei @Maxence e @ Mitch Trigo respostas tendo em mente Eu quero a semântica do método GetTempFileName (o nome do arquivo é o nome de um novo arquivo criado) adicionando a extensão preferido.

string GetNewTempFile(string extension)
{
    if (!extension.StartWith(".")) extension="." + extension;
    string fileName;
    bool bCollisions = false;
    do {
        fileName = Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString() + extension);
        try
        {
            using (new FileStream(fileName, FileMode.CreateNew)) { }
            bCollisions = false;
        }
        catch (IOException)
        {
            bCollisions = true;
        }
    }
    while (bCollisions);
    return fileName;
}
Fil
fonte
0

Isto é o que estou fazendo:

string tStamp = String.Format ("{0: aaaaMMdd.HHmmss}", DateTime.Now);
string ProcID = Process.GetCurrentProcess (). Id.ToString ();
string tmpFolder = System.IO.Path.GetTempPath ();
string outFile = tmpFolder + ProcID + "_" + tStamp + ".txt";
Michael Fitzpatrick
fonte
Bom: inclui identificador de processo Ruim: não inclui identificador de encadeamento (embora você possa executá-lo dentro de um bloqueio) Ruim: o carimbo de data / hora tem apenas 1 segundo de resolução. Em muitos aplicativos, é comum produzir muitos arquivos por segundo.
28514 Andrewf
0

Essa é uma maneira simples, mas eficaz, de gerar nomes de arquivos incrementais. Ele procurará a corrente diretamente (você pode facilmente apontar isso para outro lugar) e procurará por arquivos com o YourApplicationName * .txt básico (novamente, você pode alterar isso facilmente). Ele começará às 0000, para que o primeiro nome do arquivo seja YourApplicationName0000.txt. se, por algum motivo, houver nomes de arquivos com lixo eletrônico (significando não números) as partes esquerda e direita, esses arquivos serão ignorados em virtude da chamada tryparse.

    public static string CreateNewOutPutFile()
    {
        const string RemoveLeft = "YourApplicationName";
        const string RemoveRight = ".txt";
        const string searchString = RemoveLeft + "*" + RemoveRight;
        const string numberSpecifier = "0000";

        int maxTempNdx = -1;

        string fileName;
        string [] Files = Directory.GetFiles(Directory.GetCurrentDirectory(), searchString);
        foreach( string file in Files)
        {
            fileName = Path.GetFileName(file);
            string stripped = fileName.Remove(fileName.Length - RemoveRight.Length, RemoveRight.Length).Remove(0, RemoveLeft.Length);
            if( int.TryParse(stripped,out int current) )
            {
                if (current > maxTempNdx)
                    maxTempNdx = current;
            }
        }
        maxTempNdx++;
        fileName = RemoveLeft + maxTempNdx.ToString(numberSpecifier) + RemoveRight;
        File.CreateText(fileName); // optional
        return fileName;
    }
Nome em Exibição
fonte
-2

Eu acho que você deveria tentar o seguinte:

string path = Path.GetRandomFileName();
path = Path.Combine(@"c:\temp", path);
path = Path.ChangeExtension(path, ".tmp");
File.Create(path);

Ele gera um nome de arquivo exclusivo e cria um arquivo com esse nome em um local especificado.

Smita
fonte
3
Esta solução tem muitos problemas com ela. você não pode combinar C: \ temp com um caminho absoluto, c: \ temp pode não ser gravável e não garante que a versão .tmp do arquivo seja exclusiva.
MarkLakata