Sprawdź, czy łańcuch jest przewodnikiem bez zgłaszania wyjątków?

180

Chcę spróbować przekonwertować ciąg na Guid, ale nie chcę polegać na wyłapywaniu wyjątków (

  • ze względu na wydajność - wyjątki są drogie
  • ze względów użyteczności - pojawia się debugger
  • ze względów projektowych - oczekiwany nie jest wyjątkowy

Innymi słowy kod:

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

nie jest odpowiedni.

Spróbowałbym użyć RegEx, ale ponieważ GUID może być owinięty w nawiasy, owinięty nawiasami klamrowymi, żaden owinięty, utrudnia.

Dodatkowo myślałem, że niektóre wartości Guid są nieprawidłowe (?)


Aktualizacja 1

ChristianK miał dobry pomysł na złapanie FormatException, a nie wszystkich. Zmieniono przykładowy kod pytania, aby uwzględnić sugestię.


Aktualizacja 2

Po co martwić się zgłoszonymi wyjątkami? Czy naprawdę tak często oczekuję nieprawidłowych identyfikatorów GUID?

Odpowiedź brzmi: tak . Dlatego używam TryStrToGuid - I am spodziewa złych danych.

Przykład 1 Rozszerzenia przestrzeni nazw można określić, dodając identyfikator GUID do nazwy folderu . Mogę analizować nazwy folderów, sprawdzając, czy tekst po finale . jest 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

Przykład 2 Być może korzystam z intensywnie używanego serwera WWW, który chce sprawdzić poprawność niektórych przesłanych danych. Nie chcę, aby nieprawidłowe dane wiązały zasoby o 2-3 rzędy wielkości wyższe, niż trzeba.

Przykład 3 Mogę analizować wyrażenie wyszukiwania wprowadzone przez użytkownika.

wprowadź opis zdjęcia tutaj

Jeśli wprowadzą identyfikatory GUID, chcę je przetworzyć specjalnie (na przykład wyszukiwanie konkretnego obiektu lub wyróżnienie i sformatowanie tego konkretnego wyszukiwanego terminu w tekście odpowiedzi).


Aktualizacja 3 - Testy wydajności

Przetestuj konwersję 10 000 dobrych przewodników i 10 000 złych przewodników.

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 Nie powinienem uzasadniać pytania.

Ian Boyd
źródło
7
Dlaczego na świecie jest to wiki społeczności?
Jeff
36
Masz rację; należy nie trzeba uzasadniać pytanie. Jednak czytam uzasadnienie z zainteresowaniem (ponieważ jest bardzo podobne do tego, dlaczego tutaj czytam). Dziękuję za wspaniałe uzasadnienie.
mc
2
@Jeff prawdopodobnie dlatego, że OP edytował go ponad 10 razy - patrz meta na wiki społeczności
Marijn
3
Proszę szukać na tej stronie rozwiązań Guid.TryParse lub Guid.TryParseExact. W .NET 4.0 + powyższe rozwiązanie nie jest najbardziej eleganckie
dplante
1
@dplante Kiedy pierwotnie zadałem pytanie w 2008 roku, nie było 4.0. Dlatego pytanie i zaakceptowana odpowiedź są takie, jakie są.
Ian Boyd

Odpowiedzi:

107

Testy wydajności

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 (najszybsza) odpowiedź:

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

Konkluzja: Jeśli chcesz sprawdzić, czy łańcuch jest przewodnikiem i zależy Ci na wydajności, użyj COM Interop.

Jeśli chcesz przekonwertować identyfikator GUID w reprezentacji ciągów na identyfikator GUID, użyj

new Guid(someString);
Ian Boyd
źródło
8
Czy uruchomiłeś je z włączonym lub wyłączonym debuggerem? Wydajność zgłaszania wyjątków jest kilkakrotnie poprawiona bez dołączania debugera.
Daniel T.
Dziękuję Ci. Już miałem zadać to pytanie. Cieszę się, że znalazłem twoją odpowiedź.
David
Utworzyłem nowy plik o nazwie PInvoke.cs z fragmentem kodu PInvoke z góry powyżej, ale nie mogę uruchomić kodu. Podczas debugowania widzę, że wynik CLSIDFromString jest ZAWSZE ujemny. Próbowałem zmienić linię wywołującą na: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), wartość out); ale wciąż jest zawsze negatywny. Co ja robię źle?
JALLRED
88

Gdy .net 4.0 będzie dostępny, możesz go użyć Guid.TryParse().

Bez zwrotów Bez zwrotów
źródło
8
Jeszcze szybszym sposobem jest użycie metody Guid.TryParseExact ().
4
Jeśli parsowanie łańcuchów Guid jest najwolniejszą częścią twojej aplikacji, to jesteś błogosławiony.
Brak zwrotów Brak zwrotów
65

Nie spodoba ci się to, ale co sprawia, że ​​myślisz, że złapanie wyjątku będzie wolniejsze?

Ile nieudanych prób parsowania GUID oczekuje się w porównaniu z udanymi?

Moja rada to skorzystanie z właśnie utworzonej funkcji i profilowanie kodu. Jeśli okaże się, że ta funkcja jest naprawdę hotspot następnie go naprawić, ale nie wcześniej.

AnthonyWJones
źródło
2
Dobra odpowiedź, przedwczesna optymalizacja jest źródłem wszelkiego zła.
Kev,
33
Zła forma polega na wyjątkach, które nie są wyjątkowe. To zły nawyk, do którego nie chciałbym, żeby ktokolwiek się w to wdawał. I szczególnie nie chciałbym tego robić w bibliotece, w której ludzie będą ufać, że to działa i dobrze.
Ian Boyd,
Anonimowo, twoje pierwotne pytanie określało wydajność jako powód, dla którego chciałeś uniknąć wyjątków. Jeśli tak nie jest, być może powinieneś poprawić swoje pytanie.
AnthonyWJones
6
Wyjątek należy stosować w przypadkach WYJĄTKOWYCH, co oznacza: nie zarządzany przez programistę. Jestem przeciwnikiem sposobu zarządzania błędami „wszystkich wyjątków” firmy Microsoft. Defensywne zasady programowania. Proszę programistów Microsoft Framework, rozważ dodanie „TryParse” do klasy Guid.
Mose
14
w odpowiedzi na mój komentarz => Guid.TryParse został dodany do frameworka 4.0 --- msdn.microsoft.com/en-us/library/... --- dzięki MS za tak szybką reakcję;)
Mose
39

W .NET 4.0 możesz pisać w następujący sposób:

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}
zilia
źródło
3
To naprawdę powinna być jedna z najlepszych odpowiedzi.
Tom Lint
21

Przynajmniej przepisałbym to jako:

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

Nie chcesz mówić „nieprawidłowy identyfikator GUID” w przypadku SEHException, ThreadAbortException lub innych krytycznych lub niezwiązanych z tym rzeczy.

Aktualizacja : Począwszy od .NET 4.0, dla Guid dostępny jest nowy zestaw metod:

Naprawdę, należy ich użyć (choćby dlatego, że nie są „naiwnie” wdrażane przy użyciu try-catch wewnętrznie).

Christian.K
źródło
13

Interop jest wolniejszy niż tylko wyłapanie wyjątku:

Na szczęśliwej ścieżce z 10 000 przewodników:

Exception:    26ms
Interop:   1,201ms

Na nieszczęśliwej ścieżce:

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

Jest bardziej spójny, ale także konsekwentnie wolniejszy. Wydaje mi się, że lepiej byłoby skonfigurować swojego debuggera tak, aby łamał się tylko w przypadku nieobsługiwanych wyjątków.

Mark Brackett
źródło
„Twój debugger może złamać tylko nieobsługiwane wyjątki” Brak opcji.
Ian Boyd
1
@Ian Boyd - Jeśli używasz którejkolwiek z wersji VS (w tym Express), jest to opcja. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett
1
miałem na myśli, że nie jest to wykonalna opcja. Na przykład „Awaria nie jest opcją”. Jest to opcja, ale nie zamierzam jej używać.
Ian Boyd
9

Oto regex, którego będziesz potrzebować ...

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

Ale to tylko na początek. Będziesz także musiał sprawdzić, czy różne części, takie jak data / czas, mieszczą się w dopuszczalnych zakresach. Nie mogę sobie wyobrazić, aby było to szybsze niż metoda try / catch, którą już zarysowałeś. Mamy nadzieję, że nie otrzymujesz tylu nieprawidłowych identyfikatorów GUID, aby uzasadnić tego rodzaju kontrolę!

pdavis
źródło
Um, identyfikatory GUI IIRC generowane ze znacznika czasu są ogólnie uważane za zły pomysł, a inne rodzaje (typ 4) są całkowicie losowe
BCS
5

ze względów użyteczności - pojawia się debugger

Jeśli wybierasz metodę try / catch, możesz dodać atrybut [System.Diagnostics.DebuggerHidden], aby upewnić się, że debugger się nie psuje, nawet jeśli ustawiłeś go tak, aby był łamany podczas rzutu.

JMD
źródło
4

Chociaż prawdą jest, że używanie błędów jest droższe, większość ludzi uważa, że ​​większość ich identyfikatorów GUID będzie generowana komputerowo, więc TRY-CATCHnie jest zbyt droga, ponieważ generuje tylko koszty CATCH. Możesz to sobie udowodnić za pomocą prostego testu obu (użytkownik publiczny, bez hasła).

Proszę bardzo:

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
źródło
4

Miałem podobną sytuację i zauważyłem, że prawie nigdy niepoprawny ciąg 36 znaków. W związku z tym zmieniłem nieco twój kod, aby uzyskać lepszą wydajność, jednocześnie zachowując prostotę.

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
źródło
1
Guid akceptuje coś więcej niż tylko przerywaną formę łańcucha w swoim ctor. Identyfikatory GUID mogą mieć otaczające nawiasy klamrowe z myślnikami lub być wolne od myślników lub nawiasów klamrowych. Ten kod wygeneruje fałszywe negatywy, gdy zostaną użyte przez te alternatywne, ale również doskonale poprawne formy ciągów.
Chris Charabaruk
1
Aby kontynuować, prawidłowe długości GUID w postaci łańcucha wynoszą odpowiednio 32, 36 i 38 - odpowiednio czysty szesnastkowy, przerywany i nawiasy klamrowe z myślnikami.
Chris Charabaruk
1
@Chris, masz rację, ale @JBrooks pomysł zdrowego rozsądku sprawdzania przyszłego identyfikatora GUID przed wejściem do try / catch ma sens, szczególnie jeśli podejrzane dane są powszechne. Może coś w stylu if (wartość == null || wartość. Długość <30 || wartość. Długość> 40) {wartość = Guid.Empty; return false;}
bw
1
Rzeczywiście, byłoby lepiej, chociaż utrzymałbym mniejszy zasięg, 32..38 zamiast 30..40.
Chris Charabaruk,
2

O ile mi wiadomo, w mscrolib nie ma czegoś takiego jak Guid.TryParse. Według Source Source typ Guid ma mega-złożony konstruktor, który sprawdza wszystkie rodzaje formatów GUID i próbuje je parsować. Nie ma metody pomocniczej, do której można by zadzwonić, nawet poprzez odbicie. Myślę, że musisz szukać zewnętrznych parserów Guid lub napisać własny.

Ilya Ryzhenkov
źródło
2

Uruchom potencjalny identyfikator GUID za pomocą RegEx lub jakiegoś niestandardowego kodu, który sprawdza poprawność, aby upewnić się, że strig przynajmniej wygląda jak GUID i składa się tylko z prawidłowych znaków (i być może wydaje się, że pasuje do ogólnego formatu). Jeśli nie przejdzie kontroli poprawności, zwróci błąd - prawdopodobnie wyeliminuje to większość nieprawidłowych ciągów.

Następnie przekonwertuj ciąg, jak masz powyżej, wciąż wychwytując wyjątek dla kilku nieprawidłowych ciągów, które przechodzą kontrolę poprawności.

Jon Skeet przeprowadził analizę pod kątem czegoś podobnego do analizowania Ints (zanim TryParse był w frameworku): Sprawdzanie, czy łańcuch można przekonwertować na Int32

Jednak, jak wskazał AnthonyWJones , prawdopodobnie nie powinieneś się tym martwić.

Michael Burr
źródło
1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
rupello
źródło
„-” „{„ ”}” („i”) ”nie są prawidłowymi znakami szesnastkowymi, ale są prawidłowe w łańcuchu prowadzącym.
Preston Guillot
2
i ten kod będzie działał doskonale, jeśli wejściowy łańcuch guid zawiera znaki niebędące heksadecymami
rupello
1
  • Zdobądź odbłyśnik
  • copy'n'paste Guid's .ctor (String)
  • zamień każde wystąpienie „rzucaj nowym ...” na „return false”.

Ctor Guida jest właściwie skompilowanym wyrażeniem regularnym, dzięki czemu uzyskasz dokładnie to samo zachowanie bez narzutu wyjątku.

  1. Czy to stanowi inżynierię wsteczną? Myślę, że tak, i jako taki może być nielegalny.
  2. Zepsuje się, jeśli zmieni się formularz GUID.

Jeszcze fajniejszym rozwiązaniem byłoby dynamiczne oprzyrządowanie metody, zastępując „rzut nowy” w locie.

THX-1138
źródło
1
Próbowałem ukraść kod z ctor, ale odwołuje się on do wielu wewnętrznych klas prywatnych w celu wykonania pracy wspierającej. Uwierz mi, to była moja pierwsza próba.
Ian Boyd
1

Głosuję na link GuidTryParse opublikowany powyżej przez Jona lub podobne rozwiązanie (IsProbablyGuid). Będę pisać taki jak te dla mojej biblioteki konwersji.

Myślę, że to całkowicie nieprzyjemne, że to pytanie musi być tak skomplikowane. Słowo kluczowe „is” lub „as” byłoby w porządku, JEŚLI Guid mógłby mieć wartość zerową. Ale z jakiegoś powodu, chociaż SQL Server jest z tym w porządku, to .NET nie. Czemu? Jaka jest wartość Guid.Empty? To tylko głupiutki problem stworzony przez .NET. Naprawdę mnie wkurza, gdy konwencja języka narzuca się sama. Jak dotąd najskuteczniejszą odpowiedzią jest użycie COM Interop, ponieważ Framework nie radzi sobie z tym płynnie? „Czy ten ciąg może być identyfikatorem GUID?” powinno być pytaniem, na które łatwo odpowiedzieć.

Poleganie na zgłoszonym wyjątku jest OK, dopóki aplikacja nie przejdzie do Internetu. W tym momencie właśnie przygotowałem się na atak typu „odmowa usługi”. Nawet jeśli nie zostanę „zaatakowany”, wiem, że niektórzy yahoo zamierzają wprowadzić małpy za pomocą adresu URL, a może mój dział marketingu wyśle ​​zniekształcony link, a wtedy moja aplikacja będzie musiała ponieść dość wysoką wydajność, którą MOGŁO przynieść nie działa, ponieważ nie napisałem kodu, aby poradzić sobie z problemem, który NIE POWINIEN się zdarzyć, ale wszyscy wiemy, że się zdarzy.

To nieco zaciera linię w „Wyjątku” - ale dolna linia, nawet jeśli problem jest rzadki, jeśli może się zdarzyć wystarczająco dużo razy w krótkim czasie, że twoja aplikacja zawiesza się, obsługując wszystkie wyłapania, wtedy myślę, że zgłoszenie wyjątku jest zła forma.

TheRage3K

TheRage3K
źródło
0

jeśli typ TypeOf (myvar, Object) jest Guid, to .....

mbm_tn
źródło
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
źródło
0

Z metodą rozszerzenia w C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
Mikrofon
źródło