Verifica se la stringa è un guid senza generare eccezioni?


180

Voglio provare a convertire una stringa in un Guid, ma non voglio fare affidamento sul rilevamento delle eccezioni (

  • per motivi di prestazioni - le eccezioni sono costose
  • per motivi di usabilità: viene visualizzato il debugger
  • per motivi di design - l'atteso non è eccezionale

In altre parole il codice:

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

non è adatto.

Vorrei provare a usare RegEx, ma poiché il guid può essere racchiuso tra parentesi, impacchettato, nessuno avvolto, lo rende difficile.

Inoltre, ho pensato che alcuni valori Guid non fossero validi (?)


Aggiornamento 1

ChristianK ha avuto una buona idea di catturare solo FormatException, piuttosto che tutti. Modificato l'esempio di codice della domanda per includere suggerimenti.


Aggiornamento 2

Perché preoccuparsi delle eccezioni generate? Mi aspetto davvero tanto spesso GUID non validi?

La risposta è . È per questo che sto usando TryStrToGuid - Io sto aspettando dati errati.

Esempio 1 Le estensioni dello spazio dei nomi possono essere specificate aggiungendo un GUID a un nome di cartella . Potrei analizzare i nomi delle cartelle, controllando per vedere se il testo dopo la finale . è 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

Esempio 2 Potrei eseguire un web server molto utilizzato che vuole verificare la validità di alcuni dati postati. Non voglio dati non validi che leghino le risorse 2-3 ordini di grandezza più alti di quanto debbano essere.

Esempio 3 Potrei analizzare un'espressione di ricerca immessa da un utente.

inserisci qui la descrizione dell'immagine

Se immettono i GUID, li voglio elaborare in modo speciale (come cercare specificamente quell'oggetto, oppure evidenziare e formattare quel termine di ricerca specifico nel testo della risposta).


Aggiornamento 3: benchmark delle prestazioni

Prova a convertire 10.000 buone guide e 10.000 cattive guide.

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 non avrei dovuto giustificare una domanda.


7
Perché nel mondo questa è una wiki della comunità?
Jeff,

36
Hai ragione; si dovrebbe non avere per giustificare una domanda. Tuttavia, ho letto la giustificazione con interesse (poiché è molto simile al motivo per cui sto leggendo questo). Quindi, grazie per l'ottima giustificazione.
bw

2
@Jeff probabilmente perché l'OP lo ha modificato più di 10 volte - vedi meta sulla community wiki
Marijn,

3
Continua a cercare in questa pagina le soluzioni con Guid.TryParse o Guid.TryParseExact. Con .NET 4.0 + la soluzione di cui sopra non è la più elegante
dplante il

1
@dplante Quando inizialmente avevo posto la domanda nel 2008, non c'era 4.0. Ecco perché la domanda e la risposta accettata sono come sono.
Ian Boyd,

Risposte:


107

Benchmark prestazionali

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

Interoperabilità COM (risposta più rapida) Risposta:

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

In conclusione: se devi controllare se una stringa è un guid e ti preoccupi delle prestazioni, usa l'interoperabilità COM.

Se è necessario convertire un guid nella rappresentazione String in un Guid, utilizzare

new Guid(someString);

8
Hai eseguito questi con il debugger attivato o disattivato? Le prestazioni del lancio di eccezioni sono migliorate di più volte senza collegare il debugger.
Daniel T.

grazie. Stavo per porre questa domanda da solo. Sono contento di aver trovato la tua risposta.
David,

Ho creato un nuovo file chiamato PInvoke.cs con lo snippet di codice PInvoke dello spazio dei nomi dall'alto, ma non riesco a far funzionare il codice. Quando eseguo il debug vedo che il risultato di CLSIDFromString è SEMPRE negativo. Ho provato a cambiare la linea chiamante in: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); ma è sempre sempre negativo. Che cosa sto facendo di sbagliato?
JALLRED,


65

Non ti piacerà questo, ma cosa ti fa pensare che catturare l'eccezione sarà più lento?

Quanti tentativi falliti di analizzare un GUID ti aspetti rispetto a quelli riusciti?

Il mio consiglio è utilizzare la funzione che hai appena creato e creare il profilo del tuo codice. Se si scopre che questa funzione è davvero un hotspot poi risolvere il problema, ma non prima.


2
Buona risposta, l'ottimizzazione prematura è la radice di tutti i mali.
Kev

33
È una forma scadente fare affidamento su eccezioni che non sono eccezionali. È una cattiva abitudine in cui non vorrei che qualcuno entrasse. E soprattutto non vorrei farlo in una routine di biblioteca in cui le persone confideranno che funziona e bene.
Ian Boyd,

Anonimo, la tua domanda originale indicava le prestazioni come il motivo per cui volevi evitare eccezioni. Se non è così, forse dovresti modificare la tua domanda.
AnthonyWJones,

6
L'eccezione dovrebbe essere usata nel significato dei casi EXCEPTIONNAL: non gestita dallo sviluppatore. Sono contrario al modo "tutte le eccezioni" di Microsoft di gestire gli errori. Regole di programmazione difensiva. Sviluppatori Microsoft Framework, considera l'aggiunta di un "TryParse" alla classe Guid.
Mose,

14
in risposta al mio commento => Guid.TryParse è stato aggiunto alla Framework 4.0 --- msdn.microsoft.com/en-us/library/... --- thxs MS per una reazione così rapida;)
Mose

39

In .NET 4.0 è possibile scrivere come segue:

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

3
Questa dovrebbe davvero essere una delle risposte migliori.
Tom Lint,

21

Lo riscriverei almeno come:

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

Non vuoi dire "GUID non valido" su SEHException, ThreadAbortException o altre cose fatali o non correlate.

Aggiornamento : a partire da .NET 4.0, è disponibile un nuovo set di metodi per Guid:

In realtà, quelli dovrebbero essere usati (anche solo per il fatto, che non sono implementati "ingenuamente" usando try-catch internamente).


13

L'interoperabilità è più lenta della semplice eccezione:

Nel percorso felice, con 10.000 guide:

Exception:    26ms
Interop:   1,201ms

Nel percorso infelice:

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

È più coerente, ma è anche più lento. Mi sembra che sarebbe meglio configurare il debugger per interrompere solo le eccezioni non gestite.


"il tuo debugger deve interrompere solo eccezioni non gestite" Non è un'opzione.
Ian Boyd,

1
@Ian Boyd - Se stai usando una delle edizioni VS (incluso Express), è un'opzione. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett,

1
volevo dire che non è un'opzione fattibile. Ad esempio, "Il fallimento non è un'opzione". Si è un'opzione, ma che io non ho intenzione di usare.
Ian Boyd,

9

Bene, ecco la regex di cui avrai bisogno ...

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

Ma questo è solo per cominciare. Dovrai anche verificare che le varie parti come la data / ora siano comprese in intervalli accettabili. Non riesco a immaginare che questo sia più veloce del metodo try / catch che hai già delineato. Spero che tu non stia ricevendo così tanti GUID non validi per giustificare questo tipo di controllo!


Uhm, i GUID IIRC generati da un timestamp sono generalmente considerati una cattiva idea e l'altro tipo (tipo 4) sono totalmente randome
BCS

5

per motivi di usabilità: viene visualizzato il debugger

Se stai cercando l'approccio try / catch, puoi aggiungere l'attributo [System.Diagnostics.DebuggerHidden] per assicurarti che il debugger non si rompa anche se l'hai impostato su break on throw.


4

Mentre è vero che l'uso degli errori è più costoso, la maggior parte delle persone crede che la maggior parte dei loro GUID sarà generata dal computer, quindi TRY-CATCHnon è troppo costoso poiché genera solo costi per il CATCH. Puoi dimostrarlo a te stesso con un semplice test dei due (utente pubblico, nessuna password).

Ecco qui:

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

Ho avuto una situazione simile e ho notato che quasi mai la stringa non valida era lunga 36 caratteri. Quindi, basandomi su questo fatto, ho modificato un po 'il tuo codice per ottenere prestazioni migliori pur mantenendolo semplice.

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
Guid accetta più della semplice forma di stringa tratteggiata nel suo ctor. I GUID possono avere parentesi graffe circostanti con trattini o essere privi di trattini o parentesi graffe. Questo codice genererà falsi negativi se utilizzato da questi moduli stringa alternativi ma anche perfettamente validi.
Chris Charabaruk,

1
Per il follow-up, le lunghezze valide per i GUID a forma di stringa sono 32, 36 e 38, rispettivamente esadecimali, tratteggiati e parentesi graffe con trattini.
Chris Charabaruk,

1
@Chris, il tuo punto è valido, ma l'idea di @JBrooks di sanità mentale che controlla il potenziale GUID prima di entrare in try / catch ha senso, in particolare se l'input sospetto è comune. Forse qualcosa del tipo if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw

1
Anzi, sarebbe meglio, anche se manterrei la gamma più stretta, 32..38 anziché 30..40.
Chris Charabaruk,

2

Per quanto ne so, non c'è qualcosa come Guid.TryParse in mscrolib. Secondo la fonte di riferimento, il tipo Guid ha un costruttore mega-complesso che controlla tutti i tipi di formati guid e cerca di analizzarli. Non esiste un metodo di supporto che è possibile chiamare, anche tramite la riflessione. Penso che devi cercare parser Guid di terze parti o scrivere il tuo.


2

Esegui il potenziale GUID attraverso un RegEx o un codice personalizzato che esegua un controllo di integrità per garantire che almeno la strig sia simile a un GUID e sia composta solo da caratteri validi (e forse sembra che si adatti al formato generale). Se non supera il controllo di integrità, restituisce un errore, probabilmente eliminerà la stragrande maggioranza delle stringhe non valide.

Quindi converti la stringa come sopra, ancora catturando l'eccezione per le poche stringhe non valide che superano il controllo di integrità.

Jon Skeet ha fatto un'analisi per qualcosa di simile per l'analisi di Ints (prima che TryParse fosse nel Framework): Verifica se una stringa può essere convertita in Int32

Tuttavia, come indicato da AnthonyWJones , probabilmente non dovresti preoccuparti di questo.


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

"-" "{" "}" ("e") "non sono caratteri esadecimali validi, ma sono validi in una stringa guid.
Preston Guillot

2
e questo codice funzionerà perfettamente se la stringa di guida di input contiene quei caratteri non esadecimali
rupello,

1
  • Ottieni il riflettore
  • copy'n'paste Guid's .ctor (String)
  • sostituire ogni occasione di "lancio nuovo ..." con "ritorno falso".

Il ctor di Guid è praticamente una regex compilata, in questo modo otterrai esattamente lo stesso comportamento senza sovraccarico dell'eccezione.

  1. Ciò costituisce un reverse engineering? Penso di si, e come tale potrebbe essere illegale.
  2. Si interromperà se il modulo GUID cambia.

Una soluzione ancora più interessante sarebbe quella di strumentare dinamicamente un metodo, sostituendo "lanciare nuovo" al volo.


1
ho provato a rubare il codice da ctor, ma fa riferimento a molte classi private interne per eseguire il suo lavoro di supporto. Credimi, è stato il mio primo tentativo.
Ian Boyd,

1

Voto per il link GuidTryParse pubblicato sopra da Jon o una soluzione simile (IsProbablyGuid). Scriverò uno come quelli per la mia biblioteca di conversione.

Penso che sia totalmente zoppo che questa domanda debba essere così complicata. La parola chiave "is" o "as" andrebbe bene SE un Guid potrebbe essere nullo. Ma per qualche ragione, anche se SQL Server è d'accordo, .NET non lo è. Perché? Qual è il valore di Guid.Empty? Questo è solo un problema sciocco creato dalla progettazione di .NET, e mi dà davvero fastidio quando le convenzioni di un linguaggio calpestano se stesso. La risposta più efficace finora è stata l'utilizzo dell'interoperabilità COM perché Framework non la gestisce con garbo? "Può questa stringa essere un GUID?" dovrebbe essere una domanda alla quale è facile rispondere.

Affidarsi all'eccezione generata va bene, fino a quando l'app non passa su Internet. A quel punto mi sono appena preparato per un attacco denial of service. Anche se non vengo "attaccato", so che alcuni yahoo si scervellano con l'URL, o forse il mio ufficio marketing invierà un link non valido, e quindi la mia applicazione dovrà subire un forte successo di prestazioni che POTREBBE portare nel server perché non ho scritto il mio codice per gestire un problema che NON DOVREBBE accadere, ma sappiamo tutti che accadrà.

Ciò sfoca un po 'la linea su "Eccezione" - ma la linea di fondo, anche se il problema è raro, se può accadere abbastanza volte in un breve lasso di tempo che l'applicazione si blocca durante la manutenzione delle catture da tutto, quindi penso che lanciare un'eccezione sia cattiva 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

Con un metodo di estensione in C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.