Pregunta

Quiero intentar convertir una cadena en un Guid, pero no quiero depender de detectar excepciones (

  • por razones de rendimiento: las excepciones son costosas
  • 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 se puede envolver entre paréntesis, envolver entre llaves, no envolver ninguno, lo hace difícil.

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


Actualización 1

cristiank Tuve una buena idea para atrapar solo FormatException, en lugar de todos.Se modificó el ejemplo de código de la pregunta para incluir sugerencias.


Actualización 2

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

La respuesta es .Por eso estoy usando TryStrToGuid. soy esperando malos datos.

Ejemplo 1 Las extensiones de espacio de nombres se pueden especificar agregando un GUID al nombre de una carpeta.Podría estar analizando nombres de carpetas, comprobando si el texto después del 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 Es posible que esté ejecutando un servidor web muy utilizado y quiera comprobar la validez de algunos datos publicados.No quiero datos no válidos que ocupen recursos 2 o 3 órdenes de magnitud mayores de lo necesario.

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

enter image description here

Si ingresan 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 la conversión de 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.

¿Fue útil?

Solución

Puntos de referencia 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

COM Intertop (más rápido) Respuesta:

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

Línea de fondo:Si necesita comprobar si una cadena es un guid y le importa el rendimiento, utilice COM Interop.

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

new Guid(someString);

Otros consejos

Una vez que .net 4.0 esté disponible, podrá utilizar Guid.TryParse().

Esto no te va a gustar, pero ¿qué te hace pensar que detectar la excepción será más lento?

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

Mi consejo es utilizar la función que acaba de crear y perfilar su código.Si descubre que esta función es realmente un punto de acceso entonces arreglarlo pero no antes.

En .NET 4.0 puedes escribir lo siguiente:

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

Al menos lo reescribiría como:

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

No querrás decir "GUID no válido" en SEHException, ThreadAbortException u otras cosas fatales o no relacionadas.

Actualizar:A partir de .NET 4.0, hay un nuevo conjunto de métodos disponibles para Guid:

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

La interoperabilidad es más lenta que simplemente 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 interrumpa las excepciones no controladas.

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

^[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 esto es sólo para empezar.También tendrás que verificar que las distintas partes, como la fecha/hora, estén dentro de rangos aceptables.No puedo imaginar que esto sea más rápido que el método try/catch que ya has descrito.¡Esperamos que no reciba tantos GUID no válidos como para justificar este tipo de verificación!

por razones de usabilidad, aparece el depurador

Si opta por el enfoque try/catch, puede agregar el atributo [System.Diagnostics.DebuggerHidden] para asegurarse de que el depurador no se rompa incluso si lo configuró para que se rompa al ejecutarlo.

mientras es Es cierto que usar errores es más costoso, la mayoría de la gente cree que la mayoría de sus GUID serán generados por computadora, por lo que TRY-CATCH no es demasiado caro ya que sólo genera costos en el CATCH.Puedes comprobarlo tú mismo con una sencilla prueba del dos (usuario público, sin contraseña).

Aquí 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;
  }

Tuve una situación similar y noté que casi nunca la cadena no válida tenía 36 caracteres.Entonces, basándome en este hecho, cambié un poco su código para obtener un mejor rendimiento y al mismo tiempo mantenerlo 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;
    }
}

Hasta donde yo sé, no existe algo 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 existe ningún método auxiliar al que pueda llamar, ni siquiera mediante reflexión.Creo que debes buscar analizadores Guid de terceros o escribir el tuyo propio.

Ejecute el GUID potencial a través de una expresión regular o algún código personalizado que realice una verificación de cordura para garantizar que la cadena al menos se parezca a un GUID y consista solo en caracteres válidos (y tal vez parezca ajustarse al formato general).Si no pasa la verificación de cordura, devolverá un error; eso probablemente eliminará la gran mayoría de cadenas no válidas.

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

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

Sin embargo, como antoniowjones Indicó que probablemente no debería preocuparse por esto.

 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
  • Obtener reflector
  • copiar y pegar el .ctor de Guid (cadena)
  • reemplace cada aparición de "lanzar nuevo ..." con "devolver falso".

El ctor de Guid es prácticamente una expresión regular compilada, de esa manera obtendrás exactamente el mismo comportamiento sin la 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, reemplazando "lanzar nuevo" sobre la marcha.

Voto por el enlace GuidTryParse publicado arriba 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 cuestión 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á de acuerdo con eso, .NET no.¿Por qué?¿Cuál es el valor de Guid.Empty?Esto es sólo un problema tonto creado por el diseño de .NET, y realmente me molesta cuando las convenciones de un lenguaje se imponen.¿La respuesta de mejor rendimiento hasta ahora ha sido usar COM Interop porque Framework no lo maneja correctamente?"¿Puede esta cadena ser una guía?" Debería ser una pregunta que sea fácil de responder.

Depender de que se lance la excepción 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 algún Google va a jugar con la URL, o tal vez mi departamento de marketing enviará un enlace con formato incorrecto, y entonces mi aplicación tendrá que sufrir un impacto de rendimiento bastante considerable que PODRÍA traer inactivo el servidor porque no escribí mi código para manejar un problema que NO DEBE suceder, pero todos sabemos que SUCEDERÁ.

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

La rabia 3K

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

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

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

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top