Testar se string é um guia sem gerar exceções?


180

Quero tentar converter uma string em um Guid, mas não quero depender de exceções de captura (

  • por razões de desempenho - as exceções são caras
  • por motivos de usabilidade - o depurador é exibido
  • por motivos de design - o esperado não é excepcional

Em outras palavras, o código:

public static Boolean TryStrToGuid(String s, out Guid value)
{
    try
    {
        value = new Guid(s);
        return true;
    }
    catch (FormatException)
    {
        value = Guid.Empty;
        return false;
    }
}

Não é adequado.

Eu tentaria usar o RegEx, mas como o guid pode ser empacotado entre parênteses, empacotado entre chaves, nenhum empacotado, fica difícil.

Além disso, pensei que certos valores de Guid são inválidos (?)


Atualização 1

ChristianK teve uma boa idéia para capturar apenas FormatException, e não todos. O exemplo de código da pergunta foi alterado para incluir sugestão.


Atualização 2

Por que se preocupar com exceções lançadas? Estou realmente esperando GUIDs inválidos com tanta frequência?

A resposta é sim . É por isso que estou usando o TryStrToGuid - estou esperando dados ruins.

Exemplo 1 As extensões de espaço para nome podem ser especificadas anexando um GUID a um nome de pasta . Talvez eu esteja analisando os nomes das pastas, verificando se o texto após a final . é um GUID.

c:\Program Files
c:\Program Files.old
c:\Users
c:\Users.old
c:\UserManager.{CE7F5AA5-6832-43FE-BAE1-80D14CD8F666}
c:\Windows
c:\Windows.old

Exemplo 2 Talvez eu esteja executando um servidor da Web muito usado e queira verificar a validade de alguns dados publicados de volta. Não quero que dados inválidos vinculem recursos de 2 a 3 ordens de magnitude maiores do que precisam.

Exemplo 3 Talvez eu esteja analisando uma expressão de pesquisa inserida por um usuário.

insira a descrição da imagem aqui

Se eles digitarem GUIDs, desejo processá-los especialmente (como pesquisar especificamente por esse objeto ou realçar e formatar esse termo de pesquisa específico no texto de resposta).


Atualização 3 - Benchmarks de desempenho

Teste a conversão de 10.000 Guids bons e 10.000 Guids ruins.

Catch FormatException:
   10,000 good:     63,668 ticks
   10,000 bad:   6,435,609 ticks

Regex Pre-Screen with try-catch:
   10,000 good:    637,633 ticks
   10,000 bad:     717,894 ticks

COM Interop CLSIDFromString
   10,000 good:    126,120 ticks
   10,000 bad:      23,134 ticks

ps Eu não deveria ter que justificar uma pergunta.


7
Por que no mundo este é um wiki da comunidade?
11138 Jeff

36
Você está certo; você não precisa justificar uma pergunta. No entanto, li a justificativa com interesse (pois é muito parecido com o motivo de estar aqui lendo isso). Então, obrigado pela grande justificativa.
BW

2
@ Jeff provável becasue OP editou mais de 10 vezes - ver meta na comunidade wiki
Marijn

3
Continue procurando nesta página soluções para Guid.TryParse ou Guid.TryParseExact. Com o .NET 4.0 + a solução acima não é o mais elegante
dplante

1
@plante Quando eu originalmente fiz a pergunta em 2008, não havia 4.0. É por isso que a pergunta e a resposta aceita são do jeito que são.
22814 Ian Boyd

Respostas:


107

Benchmarks de desempenho

Catch exception:
   10,000 good:    63,668 ticks
   10,000 bad:  6,435,609 ticks

Regex Pre-Screen:
   10,000 good:   637,633 ticks
   10,000 bad:    717,894 ticks

COM Interop CLSIDFromString
   10,000 good:   126,120 ticks
   10,000 bad:     23,134 ticks

Resposta Intertop (mais rápida) COM:

/// <summary>
/// Attempts to convert a string to a guid.
/// </summary>
/// <param name="s">The string to try to convert</param>
/// <param name="value">Upon return will contain the Guid</param>
/// <returns>Returns true if successful, otherwise false</returns>
public static Boolean TryStrToGuid(String s, out Guid value)
{
   //ClsidFromString returns the empty guid for null strings   
   if ((s == null) || (s == ""))   
   {      
      value = Guid.Empty;      
      return false;   
   }

   int hresult = PInvoke.ObjBase.CLSIDFromString(s, out value);
   if (hresult >= 0)
   {
      return true;
   }
   else
   {
      value = Guid.Empty;
      return false;
   }
}


namespace PInvoke
{
    class ObjBase
    {
        /// <summary>
        /// This function converts a string generated by the StringFromCLSID function back into the original class identifier.
        /// </summary>
        /// <param name="sz">String that represents the class identifier</param>
        /// <param name="clsid">On return will contain the class identifier</param>
        /// <returns>
        /// Positive or zero if class identifier was obtained successfully
        /// Negative if the call failed
        /// </returns>
        [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = true)]
        public static extern int CLSIDFromString(string sz, out Guid clsid);
    }
}

Conclusão: se você precisar verificar se uma sequência é um guia e se preocupa com o desempenho, use a Interoperabilidade COM.

Se você precisar converter um guia na representação String em um Guia, use

new Guid(someString);

8
Você os executou com o depurador ativado ou desativado? O desempenho do lançamento de exceção é aprimorado várias vezes sem anexar o depurador.
Daniel T.

obrigado. Eu estava prestes a fazer essa pergunta eu mesmo. Ainda bem que encontrei sua resposta.
David

Criei um novo arquivo chamado PInvoke.cs com o snippet de código PInvoke do espaço para nome acima, mas não consigo fazer o código funcionar. Quando depuro, vejo que o resultado de CLSIDFromString é SEMPRE negativo. Tentei alterar a linha de chamada para: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); mas ainda é sempre negativo. O que estou fazendo de errado?
JALLRED


65

Você não vai gostar disso, mas o que faz você pensar que capturar a exceção será mais lento?

Quantas tentativas fracassadas de analisar um GUID você espera em comparação com as bem-sucedidas?

Meu conselho é usar a função que você acabou de criar e criar um perfil do seu código. Se você achar que essa função é realmente um ponto de acesso , corrija-a, mas não antes.


2
Boa resposta, a otimização prematura é a raiz de todo mal.
Kev

33
É péssimo confiar em exceções que não são excepcionais. É um mau hábito que eu não gostaria que ninguém se metesse. E eu especialmente não gostaria de fazê-lo em uma rotina de biblioteca em que as pessoas confiam que isso funciona e bem.
Ian Boyd

Anônimo, sua pergunta original declarou desempenho como o motivo pelo qual você queria evitar exceções. Se não é assim, então talvez você deva ajustar sua pergunta.
AnthonyWJones

6
Exceção deve ser usada no significado de casos EXCEPTIONNAL: não gerenciado pelo desenvolvedor. Eu sou um oponente da maneira "excepção" da Microsoft de gerenciar erros. Regras de programação defensiva. Por favor, desenvolvedores de framework da Microsoft, considere adicionar um 'TryParse' à classe Guid.
Mose

14
em resposta ao meu próprio comentário => Guid.TryParse foi adicionado ao framework 4.0 --- msdn.microsoft.com/en-us/library/… --- thxs MS para uma reação tão rápida;)
Mose

39

No .NET 4.0, você pode escrever da seguinte maneira:

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}

3
Esta deve realmente ser uma das principais respostas.
Tom Lint

21

Eu pelo menos reescreveria como:

try
{
  value = new Guid(s);
  return true;
}
catch (FormatException)
{
  value = Guid.Empty;
  return false;
}

Você não deseja dizer "GUID inválido" em SEHException, ThreadAbortException ou outros itens fatais ou não relacionados.

Atualização : A partir do .NET 4.0, há um novo conjunto de métodos disponíveis para o Guid:

Realmente, eles devem ser usados ​​(mesmo que não sejam implementados de forma "ingênua" usando internamente o try-catch).


13

A interoperabilidade é mais lenta do que apenas capturar a exceção:

No caminho feliz, com 10.000 Guids:

Exception:    26ms
Interop:   1,201ms

No caminho infeliz:

Exception: 1,150ms
  Interop: 1,201ms

É mais consistente, mas também consistentemente mais lento. Parece-me que seria melhor configurar seu depurador para interromper apenas exceções não tratadas.


msgstr "seu depurador para quebrar apenas exceções não tratadas" Não é uma opção.
Ian Boyd #

1
@Ian Boyd - Se você estiver usando alguma das edições do VS (incluindo o Express), é uma opção. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett

1
eu quis dizer que não é uma opção viável. Como, "Falha não é uma opção". Ele é uma opção, mas um que eu não vou usar.
3107 Ian Boyd

9

Bem, aqui está o regex que você precisará ...

^[A-Fa-f0-9]{32}$|^({|\\()?[A-Fa-f0-9]{8}-([A-Fa-f0-9]{4}-){3}[A-Fa-f0-9]{12}(}|\\))?$|^({)?[0xA-Fa-f0-9]{3,10}(, {0,1}[0xA-Fa-f0-9]{3,6}){2}, {0,1}({)([0xA-Fa-f0-9]{3,4}, {0,1}){7}[0xA-Fa-f0-9]{3,4}(}})$

Mas isso é apenas para iniciantes. Você também precisará verificar se as várias partes, como a data / hora, estão dentro dos limites aceitáveis. Não consigo imaginar que isso seja mais rápido que o método try / catch que você já descreveu. Espero que você não esteja recebendo tantos GUIDs inválidos para garantir esse tipo de verificação!


Hum, IIRC GUIDs que são gerados a partir de um selo de tempo são geralmente considerados para ser uma má idéia e o outro tipo (tipo 4) estão totalmente randome
BCS

5

por motivos de usabilidade - o depurador é exibido

Se você estiver adotando a abordagem try / catch, poderá adicionar o atributo [System.Diagnostics.DebuggerHidden] para garantir que o depurador não quebre, mesmo que você o tenha configurado para lançamento.


4

Enquanto ele seja verdade que o uso de erros seja mais caro, a maioria das pessoas acredita que a maioria de seus GUIDs será gerada por computador; portanto, isso TRY-CATCHnão é muito caro, pois gera apenas o custo CATCH. Você pode provar isso para si mesmo com um teste simples dos dois (público do usuário, sem senha).

Aqui está:

using System.Text.RegularExpressions;


 /// <summary>
  /// Validate that a string is a valid GUID
  /// </summary>
  /// <param name="GUIDCheck"></param>
  /// <returns></returns>
  private bool IsValidGUID(string GUIDCheck)
  {
   if (!string.IsNullOrEmpty(GUIDCheck))
   {
    return new Regex(@"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$").IsMatch(GUIDCheck);
   }
   return false;
  }

4

Eu tive uma situação semelhante e notei que quase nunca a string inválida tinha 36 caracteres. Portanto, com base nesse fato, alterei um pouco o seu código para obter melhor desempenho, mantendo-o simples.

public static Boolean TryStrToGuid(String s, out Guid value)
{

     // this is before the overhead of setting up the try/catch block.
     if(value == null || value.Length != 36)
     {  
        value = Guid.Empty;
        return false;
     }

    try
    {
        value = new Guid(s);
        return true;
    }
    catch (FormatException)
    {
        value = Guid.Empty;
        return false;
    }
}

1
O Guid aceita mais do que apenas a forma de string tracejada em seu ctor. Os GUIDs podem ter chaves com traços ao redor ou estar livres de traços ou chaves. Este código irá gerar falsos negativos quando usado por esses formulários alternativos, mas também perfeitamente válidos.
Chris Charabaruk

1
Para acompanhar, os comprimentos válidos para GUIDs em forma de sequência são 32, 36 e 38 - hexadecimal puro, tracejado e chaves com traços, respectivamente.
Chris Charabaruk

1
@ Chris, seu argumento é válido, mas a idéia de sanidade do @JBrooks, verificando o GUID em potencial antes de entrar em tentativa / captura, faz sentido, principalmente se a entrada suspeita for comum. Talvez algo como if (valor == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
BW

1
Na verdade, seria melhor, embora eu mantenha o alcance mais apertado, 32..38 em vez de 30..40.
precisa saber é o seguinte

2

Até onde eu sei, não há algo como Guid.TryParse no mscrolib. De acordo com a fonte de referência, o tipo Guid possui um construtor de mega-complexo que verifica todos os tipos de formatos de guia e tenta analisá-los. Não existe um método auxiliar que você possa chamar, mesmo através da reflexão. Eu acho que você precisa procurar por analisadores Guid de terceiros ou escrever seus próprios.


2

Execute o GUID potencial por meio de um RegEx ou algum código personalizado que faça uma verificação de integridade para garantir que o strig pareça pelo menos um GUID e consista apenas em caracteres válidos (e talvez pareça se adequar ao formato geral). Se não passar na verificação de sanidade, retorne um erro - que provavelmente eliminará a grande maioria das seqüências inválidas.

Em seguida, converta a sequência conforme descrito acima, ainda capturando a exceção para as poucas sequências inválidas que passam pela verificação de sanidade.

Jon Skeet fez uma análise para algo semelhante para analisar Ints (antes do TryParse estar no Framework): Verificando se uma string pode ser convertida em Int32

No entanto, como AnthonyWJones indicou, você provavelmente não deveria se preocupar com isso.


1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }

"-" "{" "}" (" e ')' não são caracteres hexadecimais válidos, mas são válidos em uma corda guid.
Preston Guillot

2
e este código irá funcionar perfeitamente se a cadeia guid entrada contém esses caracteres não-hexadecimais
rupello

1
  • Get Reflector
  • copy'n'paste .ctor de Guid (String)
  • substitua toda ocorrência de "throw new ..." por "return false".

O ctor de Guid é praticamente um regex compilado, dessa forma você obtém exatamente o mesmo comportamento sem sobrecarga da exceção.

  1. Isso constitui uma engenharia reversa? Eu acho que sim e, como tal, pode ser ilegal.
  2. Quebrará se o formulário GUID for alterado.

Uma solução ainda mais legal seria instrumentar dinamicamente um método, substituindo "jogar novo" rapidamente.


1
Eu tentei roubar o código do ctor, mas ele faz referência a muitas classes privadas internas para executar seu trabalho de suporte. Acredite, essa foi minha primeira tentativa.
3107 Ian Boyd

1

Eu voto no link GuidTryParse postado acima por Jon ou em uma solução semelhante (IsProbablyGuid). Escreverei um como os da minha biblioteca de conversões.

Eu acho que é totalmente idiota que essa questão tenha que ser tão complicada. A palavra-chave "is" ou "as" seria adequada se um Guid pudesse ser nulo. Mas, por algum motivo, mesmo que o SQL Server esteja de acordo, o .NET não. Por quê? Qual é o valor de Guid.Empty? Este é apenas um problema bobo criado pelo design do .NET e realmente me incomoda quando as convenções de uma linguagem se sobrepõem. Até agora, a resposta com melhor desempenho foi usar o COM Interop porque o Framework não o trata normalmente? "Esta cadeia pode ser um GUID?" deve ser uma pergunta fácil de responder.

Contar com a exceção lançada não tem problema, até que o aplicativo entre na Internet. Nesse ponto, acabei de me preparar para um ataque de negação de serviço. Mesmo que eu não seja "atacado", sei que alguns yahoo vão enganar a URL, ou talvez meu departamento de marketing envie um link incorreto e meu aplicativo sofra um desempenho bastante pesado que PODE trazer no servidor porque não escrevi meu código para lidar com um problema que NÃO DEVE acontecer, mas todos sabemos que ACONTECERÁ.

Isso atrapalha um pouco a linha em "Exceção" - mas a linha inferior, mesmo que o problema não seja frequente, se isso acontecer várias vezes em um curto espaço de tempo para que seu aplicativo travar, atendendo às capturas de tudo, acho que lançar uma exceção é má forma.

TheRage3K



0
Private Function IsGuidWithOptionalBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^[\{]?[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}[\}]?$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function


Private Function IsGuidWithoutBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function


Private Function IsGuidWithBraces(ByRef strValue As String) As Boolean
    If String.IsNullOrEmpty(strValue) Then
        Return False
    End If

    Return System.Text.RegularExpressions.Regex.IsMatch(strValue, "^\{[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\}$", System.Text.RegularExpressions.RegexOptions.IgnoreCase)
End Function

0

Com um método de extensão em C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.