¿Prueba si la cadena es un guid sin lanzar excepciones?

180

Quiero intentar convertir una cadena en un Guid, pero no quiero confiar en la captura de excepciones (

  • por motivos de rendimiento: las excepciones son caras
  • por razones de usabilidad: aparece el depurador
  • por razones de diseño: lo esperado no es excepcional

En otras palabras, el 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;
    }
}

no es adecuado.

Intentaría usar RegEx, pero dado que el guid puede estar entre paréntesis, entre paréntesis, ninguno, lo hace difícil.

Además, pensé que ciertos valores Guid no son válidos (?)


Actualización 1

ChristianK tuvo una buena idea para atrapar solo FormatException, en lugar de todos. Se modificó el ejemplo de código de la pregunta para incluir una sugerencia.


Actualización 2

¿Por qué preocuparse por las excepciones lanzadas? ¿Realmente espero GUID inválidos con tanta frecuencia?

La respuesta es si . Es por eso que estoy usando TryStrToGuid - Yo estoy esperando los datos erróneos.

Ejemplo 1 Las extensiones de espacio de nombres se pueden especificar agregando un GUID al nombre de una carpeta . Podría estar analizando los nombres de las carpetas, comprobando si el texto aparece después de la final . es un 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

Ejemplo 2 Podría estar ejecutando un servidor web muy usado que quiere verificar la validez de algunos datos devueltos. No quiero datos no válidos que agrupen recursos de 2 a 3 órdenes de magnitud más de lo necesario.

Ejemplo 3 Podría estar analizando una expresión de búsqueda ingresada por un usuario.

ingrese la descripción de la imagen aquí

Si ingresan los GUID, quiero procesarlos especialmente (como buscar específicamente ese objeto, o resaltar y formatear ese término de búsqueda específico en el texto de respuesta).


Actualización 3 - Puntos de referencia de rendimiento

Pruebe convertir 10,000 Guías buenas y 10,000 Guías malas.

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

PD: No debería tener que justificar una pregunta.

Ian Boyd
fuente
77
¿Por qué en el mundo es una wiki comunitaria?
Jeff
36
Tienes razón; usted debe no tener que justificar una pregunta. Sin embargo, leí la justificación con interés (ya que es muy similar a por qué estoy leyendo esto). Entonces, gracias por la gran justificación.
bw
2
@Jeff probablemente porque el OP lo ha editado más de 10 veces - ver meta en el wiki de la comunidad
Marijn
3
Siga buscando en esta página las soluciones con Guid. TryParse o Guid. CalculateParseExact. Con .NET 4.0 + la solución anterior no es la más elegante
dplante
1
@dplante Cuando originalmente hice la pregunta en 2008, no había 4.0. Es por eso que la pregunta y la respuesta aceptada son como son.
Ian Boyd

Respuestas:

107

Benchmarks de rendimiento

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

Respuesta intertop COM (más rápida):

/// <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);
    }
}

En pocas palabras: si necesita verificar si una cadena es un guid y le preocupa el rendimiento, use COM Interop.

Si necesita convertir un guid en representación de cadena en un Guid, use

new Guid(someString);
Ian Boyd
fuente
8
¿Los ejecutó con el depurador activado o desactivado? El rendimiento del lanzamiento de excepciones se mejora varias veces sin conectar el depurador.
Daniel T.
gracias. Estaba a punto de hacer esta pregunta yo mismo. Me alegro de haber encontrado tu respuesta.
David
He creado un nuevo archivo llamado PInvoke.cs con el fragmento de código PInvoke del espacio de nombres de arriba, pero no puedo hacer que el código funcione. Cuando depuro veo que el resultado de CLSIDFromString es SIEMPRE negativo. Intenté cambiar la línea de llamada a: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); Pero sigue siendo siempre negativo. ¿Qué estoy haciendo mal?
JALLRED
88

Una vez que .net 4.0 esté disponible, puede usarlo Guid.TryParse().

Sin reembolsos Sin devoluciones
fuente
8
Una forma aún más rápida es utilizando el método Guid. TryParseExact ().
44
Si analizar cadenas Guid es la parte más lenta de su aplicación, entonces está bendecido.
Sin reembolsos Sin devoluciones
65

No te va a gustar esto, pero ¿qué te hace pensar que atrapar la excepción va a ser más lento?

¿Cuántos intentos fallidos de analizar un GUID espera en comparación con los exitosos?

Mi consejo es usar la función que acaba de crear y perfilar su código. Si encuentra que esta función es realmente un punto de acceso, entonces corríjala, pero no antes.

AnthonyWJones
fuente
2
Buena respuesta, la optimización prematura es la raíz de todo mal.
Kev
33
Es una forma pobre confiar en excepciones que no son excepcionales. Es un mal hábito que no quisiera que nadie se metiera. Y especialmente no me gustaría hacerlo en una rutina de biblioteca donde la gente confiará en que funciona bien.
Ian Boyd
Anónimo, su pregunta original indicaba el rendimiento como la razón por la que deseaba evitar excepciones. Si eso no es así, entonces quizás deberías modificar tu pregunta.
AnthonyWJones
66
La excepción debe usarse en el significado de casos EXCEPCIONALES: no administrado por el desarrollador. Soy un oponente a la forma de "errores" de Microsoft de manejar todas las excepciones. Reglas defensivas de programación. Por favor, los desarrolladores de marcos de Microsoft consideran agregar un 'TryParse' a la clase Guid.
Mose
14
en respuesta a mi propio comentario => Guid. TryParse ha sido agregado al framework 4.0 --- msdn.microsoft.com/en-us/library/… --- thxs MS por una reacción tan rápida;)
Mose
39

En .NET 4.0 puede escribir de la siguiente manera:

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}
zhilia
fuente
3
Esta realmente debería ser una de las mejores respuestas.
Tom Lint
21

Al menos lo reescribiría como:

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

No desea decir "GUID inválido" en SEHException, ThreadAbortException u otras cosas fatales o no relacionadas.

Actualización : a partir de .NET 4.0, hay un nuevo conjunto de métodos disponibles para Guid:

En realidad, deberían usarse (aunque solo sea por el hecho de que no se implementan "ingenuamente" usando try-catch internamente).

Christian.K
fuente
13

La interoperabilidad es más lenta que solo detectar la excepción:

En el camino feliz, con 10,000 Guías:

Exception:    26ms
Interop:   1,201ms

En el camino infeliz:

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

Es más consistente, pero también es consistentemente más lento. Me parece que sería mejor configurar su depurador para que solo rompa las excepciones no controladas.

Mark Brackett
fuente
"su depurador solo se romperá en excepciones no controladas" No es una opción.
Ian Boyd
1
@Ian Boyd: si está utilizando alguna de las ediciones VS (incluida Express), es una opción. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett
1
Quise decir que no es una opción factible. Como, "El fracaso no es una opción". Que es una opción, pero que no voy a usar.
Ian Boyd
9

Bueno, aquí está la expresión regular que necesitará ...

^[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}(}})$

Pero eso es solo para empezar. También deberá verificar que las distintas partes, como la fecha / hora, se encuentren dentro de los rangos aceptables. No puedo imaginar que esto sea más rápido que el método try / catch que ya ha descrito. ¡Esperemos que no esté recibiendo tantos GUID inválidos para garantizar este tipo de verificación!

pdavis
fuente
Um, IIRC GUID que se genera a partir de una marca de tiempo se considera que en general una mala idea y el otro tipo (tipo 4) están totalmente randome
BCS
5

por razones de usabilidad: aparece el depurador

Si va por el enfoque de prueba / captura, puede agregar el atributo [System.Diagnostics.DebuggerHidden] para asegurarse de que el depurador no se rompa incluso si lo ha configurado para que se rompa en el lanzamiento.

JMD
fuente
4

Si bien es cierto que el uso de errores es más costoso, la mayoría de las personas creen que la mayoría de sus GUID serán generados por computadora, por TRY-CATCHlo que no es demasiado costoso ya que solo genera costos en el CATCH. Puede probarlo usted mismo con una simple prueba de los dos (usuario público, sin contraseña).

Aqui tienes:

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;
  }
Josef
fuente
4

Tuve una situación similar y noté que casi nunca la cadena inválida tenía 36 caracteres. Entonces, en base a este hecho, cambié un poco su código para obtener un mejor rendimiento sin dejar de ser simple.

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;
    }
}
JBrooks
fuente
1
Guid acepta más que solo la forma de cadena discontinua en su ctor. Los GUID pueden tener llaves rizadas con guiones o estar libres de guiones o llaves. Este código generará falsos negativos cuando los usen formas de cadena alternativas pero también perfectamente válidas.
Chris Charabaruk
1
Para el seguimiento, las longitudes válidas para los GUID de forma de cadena son 32, 36 y 38: hexadecimal puro, discontinuo y llaves con guiones, respectivamente.
Chris Charabaruk
1
@Chris, su punto es válido, pero la idea de @JBrooks de que la cordura verifique el posible GUID antes de lanzarse a intentar / atrapar tiene sentido, especialmente si la información sospechosa es común. Tal vez algo así como si (valor == nulo || valor.Longitud <30 || valor.lengtura> 40) {valor = Guid.Empty; return false;}
bw
1
De hecho, eso sería mejor, aunque mantendría el rango más ajustado, 32..38 en lugar de 30..40.
Chris Charabaruk
2

Que yo sepa, no hay nada como Guid. TryParse en mscrolib. Según Reference Source, el tipo Guid tiene un constructor megacomplejo que verifica todo tipo de formatos guid e intenta analizarlos. No hay un método auxiliar al que pueda llamar, incluso a través de la reflexión. Creo que tienes que buscar analizadores Guid de terceros o escribir el tuyo propio.

Ilya Ryzhenkov
fuente
2

Ejecute el GUID potencial a través de un RegEx o algún código personalizado que haga una comprobación de sanidad para garantizar que el strig al menos se parezca a un GUID y consista solo en caracteres válidos (y tal vez parece que se ajusta al formato general). Si no pasa la verificación de cordura, se devuelve un error, eso probablemente eliminará la gran mayoría de las cadenas no válidas.

Luego, convierta la cadena como lo hizo anteriormente, aún capturando la excepción para las pocas cadenas no válidas que pasan por la comprobación de cordura.

Jon Skeet hizo un análisis de algo similar para analizar Ints (antes de que TryParse estuviera en el Framework): Verificar si una cadena se puede convertir a Int32

Sin embargo, como AnthonyWJones indicó, probablemente no deberías preocuparte por esto.

Michael Burr
fuente
1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
rupello
fuente
"-" "{" "}" ("y") "no son caracteres hexadecimales válidos, pero son válidos en una cadena de guid.
Preston Guillot
2
y este código funcionará perfectamente bien si la cadena guid de entrada contiene esos caracteres no hexadecimales
rupello
1
  • Obtener reflector
  • copiar y pegar Guid's .ctor (String)
  • reemplace cada aparición de "lanzar nuevo ..." con "devolver falso".

Guid's ctor es más o menos una expresión regular compilada, de esa manera obtendrá exactamente el mismo comportamiento sin sobrecarga de la excepción.

  1. ¿Constituye esto una ingeniería inversa? Creo que sí, y como tal podría ser ilegal.
  2. Se romperá si cambia el formulario GUID.

Una solución aún mejor sería instrumentar dinámicamente un método, al reemplazar "lanzar nuevo" sobre la marcha.

THX-1138
fuente
1
Traté de robar el código de ctor, pero hace referencia a muchas clases privadas internas para realizar su trabajo de soporte. Créeme, ese fue mi primer intento.
Ian Boyd
1

Voto por el enlace Guid TryParse publicado anteriormente por Jon o una solución similar (IsProbablyGuid). Escribiré uno como esos para mi biblioteca de conversión.

Creo que es totalmente lamentable que esta pregunta tenga que ser tan complicada. La palabra clave "es" o "como" estaría bien SI un Guid pudiera ser nulo. Pero por alguna razón, aunque SQL Server está bien con eso, .NET no lo está. ¿Por qué? ¿Cuál es el valor de Guid.Empty? Este es solo un problema tonto creado por el diseño de .NET, y realmente me molesta cuando las convenciones de un lenguaje se interponen en sí mismas. ¿La respuesta de mejor desempeño hasta ahora ha sido usar COM Interop porque el Framework no lo maneja con gracia? "¿Puede esta cadena ser un GUID?" debe ser una pregunta fácil de responder.

Confiar en la excepción lanzada está bien, hasta que la aplicación se conecte a Internet. En ese momento, me preparé para un ataque de denegación de servicio. Incluso si no me "atacan", sé que algunos ya se irán con la URL, o tal vez mi departamento de marketing enviará un enlace con formato incorrecto, y luego mi aplicación tendrá que sufrir un impacto de rendimiento bastante considerable que PODRÍA traer fuera del servidor porque no escribí mi código para manejar un problema que NO DEBERÍA suceder, pero todos sabemos que PASARÁ.

Esto desdibuja un poco la línea en "Excepción", pero el resultado final, incluso si el problema es infrecuente, si puede suceder suficientes veces en un corto período de tiempo que su aplicación se bloquea al atender las capturas de todo, entonces creo que lanzar una excepción es mala forma.

TheRage3K

TheRage3K
fuente
0

si TypeOf ctype (myvar, Object) es Guid entonces .....

mbm_tn
fuente
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
Stefan Steiger
fuente
0

Con un método de extensión en C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Miguel
fuente