Testen Sie, ob der String eine Guid ist, ohne Ausnahmen auszulösen?

180

Ich möchte versuchen, einen String in einen Guid zu konvertieren, möchte mich aber nicht darauf verlassen, Ausnahmen abzufangen (

  • Aus Leistungsgründen sind Ausnahmen teuer
  • Aus Gründen der Benutzerfreundlichkeit wird der Debugger angezeigt
  • aus gestalterischen Gründen - das Erwartete ist keine Ausnahme

Mit anderen Worten der Code:

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

ist nicht geeignet.

Ich würde versuchen, RegEx zu verwenden, aber da die Guid in Klammern umwickelt werden kann, macht es die Klammer, keine umwickelt, schwierig.

Außerdem dachte ich, dass bestimmte Guid-Werte ungültig sind (?)


Update 1

ChristianK hatte eine gute Idee, nur FormatExceptionalle und nicht alle zu fangen . Das Codebeispiel der Frage wurde geändert, um Vorschläge einzuschließen.


Update 2

Warum sich über ausgelöste Ausnahmen Gedanken machen? Erwarte ich wirklich so oft ungültige GUIDs?

Die Antwort lautet ja . Deshalb habe ich TryStrToGuid bin mit - ich bin erwartete schlechte Daten.

Beispiel 1 Namespace-Erweiterungen können durch Anhängen einer GUID an einen Ordnernamen angegeben werden . Möglicherweise analysiere ich Ordnernamen und überprüfe, ob der Text nach dem Finale angezeigt wird . ist eine 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

Beispiel 2 Ich verwende möglicherweise einen stark genutzten Webserver, der die Gültigkeit einiger zurückgesendeter Daten überprüfen möchte. Ich möchte nicht, dass ungültige Daten Ressourcen um 2-3 Größenordnungen höher binden, als es sein muss.

Beispiel 3 Ich analysiere möglicherweise einen von einem Benutzer eingegebenen Suchausdruck.

Geben Sie hier die Bildbeschreibung ein

Wenn sie GUIDs eingeben, möchte ich sie speziell verarbeiten (z. B. gezielt nach diesem Objekt suchen oder diesen bestimmten Suchbegriff im Antworttext markieren und formatieren).


Update 3 - Leistungsbenchmarks

Testen Sie die Konvertierung von 10.000 guten und 10.000 schlechten 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

ps Ich sollte keine Frage rechtfertigen müssen.

Ian Boyd
quelle
7
Warum in aller Welt ist das ein Community-Wiki?
Jeff
36
Du hast recht; Sie sollten keine Frage rechtfertigen müssen. Ich habe die Begründung jedoch mit Interesse gelesen (da es sehr ähnlich ist, warum ich hier bin, um dies zu lesen). Vielen Dank für die gute Begründung.
bw
2
@ Jeff wahrscheinlich, weil das OP es mehr als 10 Mal bearbeitet hat - siehe Meta im Community-Wiki
Marijn
3
Bitte suchen Sie auf dieser Seite nach Lösungen mit Guid.TryParse oder Guid.TryParseExact. Mit .NET 4.0 + ist die obige Lösung nicht die eleganteste
dplante
1
@dplante Als ich die Frage ursprünglich im Jahr 2008 stellte, gab es keine 4.0. Deshalb sind die Frage und die akzeptierte Antwort so, wie sie sind.
Ian Boyd

Antworten:

107

Leistungsbenchmarks

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 (schnellste) Antwort:

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

Fazit: Wenn Sie überprüfen müssen, ob eine Zeichenfolge eine Richtlinie ist und Sie Wert auf Leistung legen, verwenden Sie COM Interop.

Wenn Sie eine Guid in der Zeichenfolgendarstellung in eine Guid konvertieren müssen, verwenden Sie

new Guid(someString);
Ian Boyd
quelle
8
Haben Sie diese mit ein- oder ausgeschaltetem Debugger ausgeführt? Die Leistung beim Auslösen von Ausnahmen wird um ein Vielfaches verbessert, ohne dass der Debugger angeschlossen werden muss.
Daniel T.
Danke. Ich wollte diese Frage selbst stellen. Ich bin froh, dass ich deine Antwort gefunden habe.
David
Ich habe eine neue Datei mit dem Namen PInvoke.cs mit dem Namespace PInvoke-Code-Snippet von oben erstellt, aber ich kann den Code nicht zum Laufen bringen. Beim Debuggen sehe ich, dass das Ergebnis von CLSIDFromString IMMER negativ ist. Ich habe versucht, die aufrufende Leitung zu ändern: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); aber es ist immer noch negativ. Was mache ich falsch?
JALLRED
88

Sobald .net 4.0 verfügbar ist, können Sie verwenden Guid.TryParse().

Keine Rückerstattung Keine Rückgabe
quelle
8
Eine noch schnellere Möglichkeit ist die Verwendung der Guid.TryParseExact () -Methode.
4
Wenn das Parsen von Guid-Zeichenfolgen der langsamste Teil Ihrer Anwendung ist, sind Sie gesegnet.
Keine Rückerstattung Keine Rückgabe
65

Das wird dir nicht gefallen, aber warum denkst du, dass das Fangen der Ausnahme langsamer sein wird?

Wie viele fehlgeschlagene Versuche, eine GUID zu analysieren, erwarten Sie im Vergleich zu erfolgreichen?

Mein Rat ist, die gerade erstellte Funktion zu verwenden und Ihren Code zu profilieren. Wenn Sie feststellen, dass diese Funktion wirklich ein Hotspot ist, beheben Sie sie, jedoch nicht vorher.

AnthonyWJones
quelle
2
Gute Antwort, vorzeitige Optimierung ist die Wurzel allen Übels.
Kev
33
Es ist eine schlechte Form, sich auf Ausnahmen zu verlassen, die keine Ausnahme sind. Es ist eine schlechte Angewohnheit, dass ich nicht möchte, dass sich jemand darauf einlässt. Und ich würde es besonders nicht in einer Bibliotheksroutine tun wollen, in der die Leute darauf vertrauen, dass es funktioniert und gut.
Ian Boyd
Anonym, in Ihrer ursprünglichen Frage wurde die Leistung als Grund angegeben, warum Sie Ausnahmen vermeiden wollten. Wenn das nicht so ist, sollten Sie vielleicht Ihre Frage optimieren.
AnthonyWJones
6
Die Ausnahme sollte in EXCEPTIONNAL-Fällen verwendet werden, dh vom Entwickler nicht verwaltet. Ich bin ein Gegner der Microsoft-Methode zur Fehlerverwaltung. Defensive Programmierregeln. Bitte Microsoft Framework-Entwickler, erwägen Sie, der Guid-Klasse ein 'TryParse' hinzuzufügen.
Mose
14
als Antwort auf meinen eigenen Kommentar => Guid.TryParse wurde zu Framework 4.0 hinzugefügt --- msdn.microsoft.com/en-us/library/… --- thxs MS für eine so schnelle Reaktion;)
Mose
39

In .NET 4.0 können Sie wie folgt schreiben:

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}
zhilia
quelle
3
Dies sollte wirklich eine der Top-Antworten sein.
Tom Lint
21

Ich würde es zumindest umschreiben als:

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

Sie möchten nicht "ungültige GUID" für SEHException, ThreadAbortException oder andere schwerwiegende oder nicht verwandte Dinge sagen.

Update : Ab .NET 4.0 stehen für Guid neue Methoden zur Verfügung:

Wirklich, diese sollten verwendet werden (schon allein deshalb, weil sie nicht "naiv" mit try-catch intern implementiert werden).

Christian.K
quelle
13

Interop ist langsamer als nur die Ausnahme zu fangen:

Auf dem glücklichen Weg mit 10.000 Guids:

Exception:    26ms
Interop:   1,201ms

Auf dem unglücklichen Weg:

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

Es ist konsistenter, aber es ist auch konsistent langsamer. Mir scheint, Sie sollten Ihren Debugger besser so konfigurieren, dass er nur bei nicht behandelten Ausnahmen funktioniert.

Mark Brackett
quelle
"Ihr Debugger soll nur bei nicht behandelten Ausnahmen brechen" Keine Option.
Ian Boyd
1
@ Ian Boyd - Wenn Sie eine der VS-Editionen (einschließlich Express) verwenden, ist dies eine Option. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett
1
Ich meinte, es ist keine praktikable Option. Wie "Fehler ist keine Option." Es ist eine Option, aber eine, die ich nicht verwenden werde.
Ian Boyd
9

Nun, hier ist der reguläre Ausdruck, den Sie brauchen werden ...

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

Aber das ist nur für den Anfang. Sie müssen auch überprüfen, ob die verschiedenen Teile wie Datum und Uhrzeit innerhalb akzeptabler Bereiche liegen. Ich kann mir nicht vorstellen, dass dies schneller ist als die Try / Catch-Methode, die Sie bereits beschrieben haben. Hoffentlich erhalten Sie nicht so viele ungültige GUIDs, um diese Art der Überprüfung zu rechtfertigen!

pdavis
quelle
Ähm, IIRC-GUIDs, die aus einem Zeitstempel generiert werden, werden im Allgemeinen als schlechte Idee angesehen, und die andere Art (Typ 4) ist völlig zufällig
BCS
5

Aus Gründen der Benutzerfreundlichkeit wird der Debugger angezeigt

Wenn Sie sich für den Try / Catch-Ansatz entscheiden, können Sie das Attribut [System.Diagnostics.DebuggerHidden] hinzufügen, um sicherzustellen, dass der Debugger auch dann nicht funktioniert, wenn Sie ihn beim Werfen auf "Break" setzen.

JMD
quelle
4

Zwar ist die Verwendung von Fehlern teurer, aber die meisten Menschen glauben, dass ein Großteil ihrer GUIDs computergeneriert sein wird, sodass a TRY-CATCHnicht zu teuer ist, da dies nur Kosten für die GUIDs verursacht CATCH. Sie können sich dies mit einem einfachen Test der beiden beweisen (Benutzer öffentlich, kein Passwort).

Bitte schön:

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
quelle
4

Ich hatte eine ähnliche Situation und bemerkte, dass die ungültige Zeichenfolge fast nie 36 Zeichen lang war. Aufgrund dieser Tatsache habe ich Ihren Code ein wenig geändert, um eine bessere Leistung zu erzielen und ihn dennoch einfach zu halten.

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
quelle
1
Guid akzeptiert mehr als nur die gestrichelte Zeichenfolgenform in seinem ctor. GUIDs können umgebende geschweifte Klammern mit Bindestrichen haben oder frei von Bindestrichen oder Klammern sein. Dieser Code generiert falsche Negative, wenn er von diesen alternativen, aber auch perfekt gültigen Zeichenfolgenformen verwendet wird.
Chris Charabaruk
1
Für die Nachverfolgung sind die gültigen Längen für GUIDs in Zeichenfolgenform 32, 36 und 38 - reine hexadezimale, gestrichelte und geschweifte Klammern.
Chris Charabaruk
1
@Chris, Ihr Punkt ist gültig, aber @JBrooks Idee, die potenzielle GUID vor dem Einstieg in try / catch zu überprüfen, ist sinnvoll, insbesondere wenn verdächtige Eingaben häufig sind. Vielleicht so etwas wie if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw
1
In der Tat wäre das besser, obwohl ich die Reichweite enger halten würde, 32..38 statt 30..40.
Chris Charabaruk
2

Soweit ich weiß, gibt es in mscrolib nichts wie Guid.TryParse. Laut Referenzquelle verfügt der Guid-Typ über einen mega-komplexen Konstruktor, der alle Arten von Guid-Formaten überprüft und versucht, sie zu analysieren. Es gibt keine Hilfsmethode, die Sie aufrufen können, auch nicht durch Reflexion. Ich denke, Sie müssen nach Guid-Parsern von Drittanbietern suchen oder Ihre eigenen schreiben.

Ilya Ryzhenkov
quelle
2

Führen Sie die potenzielle GUID über eine RegEx oder einen benutzerdefinierten Code aus, der eine Überprüfung der Integrität durchführt, um sicherzustellen, dass der Strig mindestens wie eine GUID aussieht und nur aus gültigen Zeichen besteht (und möglicherweise zum Gesamtformat passt). Wenn die Überprüfung der Integrität nicht bestanden wird, wird ein Fehler zurückgegeben. Dadurch wird wahrscheinlich die überwiegende Mehrheit der ungültigen Zeichenfolgen entfernt.

Konvertieren Sie dann die Zeichenfolge wie oben beschrieben, wobei Sie immer noch die Ausnahme für die wenigen ungültigen Zeichenfolgen abfangen, die die Überprüfung der Integrität durchlaufen.

Jon Skeet hat eine Analyse für etwas Ähnliches zum Parsen von Ints durchgeführt (bevor TryParse im Framework enthalten war): Überprüfen, ob eine Zeichenfolge in Int32 konvertiert werden kann

Wie AnthonyWJones jedoch angedeutet hat, sollten Sie sich darüber wahrscheinlich keine Sorgen machen.

Michael Burr
quelle
1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
Rupello
quelle
"-" "{" "}" ("und") "sind keine gültigen Hex-Zeichen, sondern in einer Guid-Zeichenfolge.
Preston Guillot
2
und dieser Code wird perfekt funktionieren, wenn die Eingabe-Guid-Zeichenfolge diese nicht
hexadezimalen
1
  • Holen Sie sich Reflektor
  • copy'n'paste Guid's .ctor (String)
  • Ersetzen Sie jedes Vorkommen von "throw new ..." durch "return false".

Der ctor von Guid ist so ziemlich ein kompilierter regulärer Ausdruck. Auf diese Weise erhalten Sie genau das gleiche Verhalten, ohne die Ausnahme zu überschreiten.

  1. Handelt es sich um ein Reverse Engineering? Ich denke, das tut es und könnte als solches illegal sein.
  2. Wird unterbrochen, wenn sich das GUID-Formular ändert.

Eine noch coolere Lösung wäre, eine Methode dynamisch zu instrumentieren, indem "neu werfen" im laufenden Betrieb ersetzt wird.

THX-1138
quelle
1
Ich habe versucht, den Code von ctor zu stehlen, aber er verweist auf viele interne private Klassen, um seine Support-Arbeit auszuführen. Glauben Sie mir, das war mein erster Versuch.
Ian Boyd
1

Ich stimme für den GuidTryParse-Link, der oben von Jon oder einer ähnlichen Lösung (IsProbablyGuid) gepostet wurde. Ich werde eine solche für meine Konvertierungsbibliothek schreiben.

Ich finde es total lahm, dass diese Frage so kompliziert sein muss. Das Schlüsselwort "is" oder "as" wäre in Ordnung, wenn eine Guid null sein könnte. Aber aus irgendeinem Grund ist .NET dies nicht, obwohl SQL Server damit einverstanden ist. Warum? Was ist der Wert von Guid.Empty? Dies ist nur ein dummes Problem, das durch das Design von .NET verursacht wurde, und es nervt mich wirklich, wenn die Konventionen einer Sprache auf sich selbst übergehen. Die bisher leistungsstärkste Antwort war die Verwendung von COM Interop, weil das Framework nicht ordnungsgemäß damit umgeht. "Kann diese Zeichenfolge eine GUID sein?" sollte eine Frage sein, die leicht zu beantworten ist.

Sich auf die ausgelöste Ausnahme zu verlassen, ist in Ordnung, bis die App ins Internet geht. Zu diesem Zeitpunkt habe ich mich gerade auf einen Denial-of-Service-Angriff eingestellt. Selbst wenn ich nicht "angegriffen" werde, weiß ich, dass ein Yahoo mit der URL Affen wird, oder vielleicht sendet meine Marketingabteilung einen fehlerhaften Link, und dann muss meine Anwendung einen ziemlich starken Leistungseinbruch erleiden, den KÖNNTE den Server heruntergefahren, weil ich meinen Code nicht geschrieben habe, um ein Problem zu lösen, das NICHT auftreten SOLLTE, aber wir alle wissen, dass es passieren wird.

Dies verwischt die Zeile bei "Ausnahme" ein wenig - aber unter dem Strich, selbst wenn das Problem selten auftritt, wenn es in kurzer Zeit so oft vorkommen kann, dass Ihre Anwendung abstürzt und die Fänge von allem bedient, dann ist es meiner Meinung nach eine Ausnahme schlechte Form.

TheRage3K

TheRage3K
quelle
0

Wenn TypeOf ctype (myvar, Object) Guid ist, dann .....

mbm_tn
quelle
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
quelle
0

Mit einer Erweiterungsmethode in C #

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