Teste se string é um guid sem jogar exceções?
Pergunta
Eu quero tentar converter uma string para um Guid, mas eu não quero depender de capturar exceções (
- por razões de desempenho - exceções são caros
- por razões de usabilidade - o depurador aparece
- por razões de concepção - a espera 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 usando RegEx, mas desde que o guid pode ser parêntese embrulhado, cinta enrolada, nenhum envolvido, torna difícil.
Além disso, eu pensei que certos valores Guid são inválidos (?)
Update 1
Christiank tinha um boa idéia para pegar única FormatException
, ao invés de todos. Mudou amostra de código da pergunta para incluir sugestão.
Update 2
Por que se preocupar com as exceções lançadas? Estou realmente esperando GUIDs inválidos tudo o que muitas vezes?
A resposta é Sim . É por isso que eu estou usando TryStrToGuid -. I am à espera de dados ruins
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 eu poderia estar executando um web-servidor muito utilizado quer para verificar a validade de alguns dados retrospectivos enviado. Eu não quero dados inválidos amarrando recursos 2-3 ordens de magnitude maior do que ele precisa ser.
Exemplo 3 eu poderia estar analisar uma expressão de pesquisa inserido por um usuário.
Se eles entram GUID é que eu quero processá-los especialmente (como procurando especificamente para esse objeto, ou destaque e formato que termo de pesquisa específico no texto de resposta.)
Update 3 - benchmarks de desempenho
Test converter 10.000 boas Guids e 10.000 maus Guids.
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
P.S. Eu não deveria ter de justificar uma pergunta.
Solução
Performance Benchmarks
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
COM Intertop (Fastest) Resposta:
/// <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);
}
}
A linha inferior: Se você precisa verificar se uma string é um guid, e você se preocupa com o desempenho, use COM Interop.
Se você precisa converter um guid em representação String para um Guid, uso
new Guid(someString);
Outras dicas
Uma vez .NET 4.0 está disponível, você pode usar Guid.TryParse()
.
Você não vai como este, mas o que faz você pensar que captura a exceção vai ser mais lento?
Quantas tentativas para analisar uma GUID que você está esperando em comparação com os bem-sucedidos? Falhou
O meu conselho é usar a função que você acabou de criar e perfil do seu código. Se você achar que esta função é verdadeiramente um hotspot seguida, corrigi-lo, mas não antes.
Em .NET 4.0 você pode escrever como a seguir:
public static bool IsValidGuid(string str)
{
Guid guid;
return Guid.TryParse(str, out guid);
}
Eu, pelo menos, reescrevê-la como:
try
{
value = new Guid(s);
return true;
}
catch (FormatException)
{
value = Guid.Empty;
return false;
}
Você não quer dizer "GUID inválido" na SEHException, ThreadAbortException ou outras coisas fatal ou não-relacionado.
Atualizar : Começando com .NET 4.0, há um novo conjunto de métodos disponíveis para Guid:
Realmente, aqueles deve ser usado (se apenas para o fato de que eles não são "ingenuamente" implementado usando try-catch internamente).
Interop é mais lento do que apenas captura 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 fora de configurar o seu depurador apenas quebrar em exceções não tratadas.
Bem, aqui é o regex 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 é só para começar. Você também terá de verificar se as várias partes, tais como a data / hora estão dentro dos limites aceitáveis. Eu não posso imaginar este ser mais rápido do que o método try / catch que você já delineado. Espero que você não está recebendo que muitos GUIDs inválidos para justificar este tipo de verificação!
por razões de usabilidade - o depurador aparece
Se você estiver indo para a abordagem try / catch que você pode adicionar o atributo [System.Diagnostics.DebuggerHidden] para garantir que o depurador não quebrar mesmo se você configurá-lo para quebrar no lance.
Embora é true que o uso de erros é mais caro, a maioria das pessoas acredita que a maioria dos seus GUIDs vão ser geradas por computador de modo a TRY-CATCH
não é muito caro, uma vez que só gera custos em o CATCH
. Você pode provar isso a si mesmo com um teste simples do dois (público usuário, sem senha).
Aqui vai:
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;
}
Eu tinha uma situação semelhante e notei que quase nunca foi a cadeia inválida 36 caracteres. Assim, com base neste fato, eu mudei o código um pouco para obter um melhor desempenho, enquanto ainda mantê-lo 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;
}
}
Tanto quanto eu sei, não há algo como Guid.TryParse em mscrolib. De acordo com a fonte de referência, tipo Guid tem construtor mega-complexos que verifica todos os tipos de formatos de GUID e tentativas para analisá-los. Não há método auxiliar você pode chamar, mesmo através de reflexão. Eu acho que você tem que procurar por 3rd party analisadores Guid, ou escreve sua própria.
Executar o GUID potencial embora um RegEx ou algum código personalizado que faz uma verificação de sanidade para garantir a strig, pelo menos, parece um GUID e consiste apenas de caracteres válidos (e talvez que parece caber no formato geral). Se ele não passar no teste de sanidade retornar um erro -. Que provavelmente vai eliminar a grande maioria das cordas inválidos
Em seguida, converter a cadeia como você tem acima, ainda captura a exceção para as poucas cordas inválidos que recebem através da verificação de sanidade.
Jon Skeet fez uma análise para algo semelhante para analisar Ints (antes TryParse estava no quadro): Verificando uma lata corda ser convertido para Int32
No entanto, como AnthonyWJones indicou que você provavelmente não deveria estar se preocupando com isso.
bool IsProbablyGuid(string s)
{
int hexchars = 0;
foreach(character c in string s)
{
if(IsValidHexChar(c))
hexchars++;
}
return hexchars==32;
}
- Get Refletor
- copy'n'paste .ctor de Guid (String)
- substituir cada ocorrência de "jogar novo ...", com "falsa retorno".
ctor de Guid é praticamente uma regex compilado, dessa forma você terá exatamente o mesmo comportamento, sem sobrecarga da exceção.
- Será que isso constitui uma engenharia reversa? Eu acho que ele faz, e como tal pode ser ilegal.
- vai quebrar se forma muda GUID.
solução mais frio Mesmo seria instrumento dinamicamente um método, substituindo "throw new" on the fly.
Eu voto para o link GuidTryParse postou acima por Jon ou uma solução similar (IsProbablyGuid). Eu estarei escrevendo um como aqueles para a minha biblioteca de conversão.
Eu acho que é totalmente coxo que esta questão tem de ser tão complicado. O "é" ou "como" palavra-chave seria muito bem se um Guid poderia ser nulo. Mas por alguma razão, embora o SQL Server é OK com isso, .NET não é. Por quê? Qual é o valor de Guid.Empty? Este é apenas um problema bobo criado pelo design do .NET, e isso realmente me incomoda quando as convenções de um passo linguagem em si. A resposta de melhor desempenho até agora tem vindo a utilizar interoperabilidade COM porque o quadro não lidar com isso graciosamente? "Pode esta seqüência de ser um GUID?" deve ser uma questão que é fácil de responder.
Baseando-se na exceção sendo lançada é OK, até que o aplicativo vai para a internet. Nesse ponto eu só me estabeleci para um ataque de negação de serviço. Mesmo se eu não receber o "atacado", eu sei que alguns yahoo vai macaco com o URL, ou talvez o meu departamento de marketing irá enviar um link mal formado, e depois a minha candidatura tem que sofrer um impacto no desempenho bastante robusto que poderia trazer desligar o servidor, porque eu não escrever o meu código para lidar com um problema que não deveria acontecer, mas todos sabemos vai acontecer.
Esta borra a linha um pouco sobre "exceção" - mas a linha de fundo, mesmo se o problema é pouco frequente, se isso pode acontecer bastante vezes em um curto período de tempo que o seu aplicativo falha manutenção das capturas de tudo, então eu acho que jogando uma exceção é má forma.
TheRage3K
Se TypeOf ctype (myvar, Object) é Guid então .....
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
Com um método de extensão em C #
public static bool IsGUID(this string text)
{
return Guid.TryParse(text, out Guid guid);
}