Tester si la chaîne est un GUID sans lever d'exceptions?


180

Je veux essayer de convertir une chaîne en Guid, mais je ne veux pas me fier à la capture d'exceptions (

  • pour des raisons de performances - les exceptions coûtent cher
  • pour des raisons de convivialité - le débogueur apparaît
  • pour des raisons de conception - l'attendu n'est pas exceptionnel

En d'autres termes, le code:

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

ne convient pas.

J'essaierais d'utiliser RegEx, mais comme le guid peut être entre parenthèses, entre accolades, aucun enveloppé, cela rend les choses difficiles.

De plus, je pensais que certaines valeurs Guid ne sont pas valides (?)


Mise à jour 1

ChristianK avait une bonne idée d'attraper seulement FormatException, plutôt que tout. L'échantillon de code de la question a été modifié pour inclure une suggestion.


Mise à jour 2

Pourquoi s'inquiéter des exceptions levées? Est-ce que je m'attends vraiment à des GUID invalides si souvent?

La réponse est oui . Voilà pourquoi je me sers TryStrToGuid - Je me attends de mauvaises données.

Exemple 1 Les extensions d'espace de noms peuvent être spécifiées en ajoutant un GUID à un nom de dossier . Je suis peut-être en train d'analyser les noms de dossiers, de vérifier si le texte après la finale . est 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

Exemple 2 J'exécute peut-être un serveur Web très utilisé qui souhaite vérifier la validité de certaines données postées. Je ne veux pas que des données non valides bloquent des ressources de 2 à 3 ordres de grandeur plus élevées que nécessaire.

Exemple 3 Je suis peut-être en train d'analyser une expression de recherche saisie par un utilisateur.

entrez la description de l'image ici

S'ils entrent des GUID, je veux les traiter spécialement (comme rechercher spécifiquement cet objet, ou mettre en évidence et mettre en forme ce terme de recherche spécifique dans le texte de la réponse.)


Mise à jour 3 - Benchmarks de performance

Testez la conversion de 10 000 bons guides et 10 000 mauvais guides.

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 je ne devrais pas avoir à justifier une question.


7
Pourquoi diable est-ce un wiki communautaire?
Jeff

36
Vous avez raison; vous ne devriez pas avoir à justifier une question. Cependant, j'ai lu la justification avec intérêt (car c'est très similaire à la raison pour laquelle je lis ici). Alors, merci pour la grande justification.
bw

2
@Jeff probablement parce que l'OP l'a édité plus de 10 fois - voir la méta sur le wiki de la communauté
Marijn

3
Continuez à chercher sur cette page des solutions avec Guid.TryParse ou Guid.TryParseExact. Avec .NET 4.0 + la solution ci-dessus n'est pas la plus élégante
déplante le

1
@dplante Quand j'ai posé la question à l'origine en 2008, il n'y avait pas 4.0. C'est pourquoi la question et la réponse acceptée sont telles qu'elles sont.
Ian Boyd

Réponses:


107

Benchmarks de performance

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

Réponse COM Intertop (la plus rapide):

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

Bottom line: Si vous devez vérifier si une chaîne est un GUID et que vous vous souciez des performances, utilisez COM Interop.

Si vous devez convertir un GUID dans une représentation String en Guid, utilisez

new Guid(someString);

8
Les avez-vous exécutés avec le débogueur activé ou désactivé? Les performances de la levée d'exceptions sont améliorées de plusieurs fois sans attacher le débogueur.
Daniel T.15

Merci. J'allais me poser cette question moi-même. Heureux d'avoir trouvé votre réponse.
David

J'ai créé un nouveau fichier appelé PInvoke.cs avec l'extrait de code de l'espace de noms PInvoke d'en haut, mais je ne parviens pas à faire fonctionner le code. Quand je débogue, je vois que le résultat de CLSIDFromString est TOUJOURS négatif. J'ai essayé de changer la ligne d'appel en: int hresult = PInvoke.ObjBase.CLSIDFromString (Guid.NewGuid (). ToString (), out value); mais c'est toujours toujours négatif. Qu'est-ce que je fais mal?
JALLRED


65

Vous n'allez pas aimer ça, mais qu'est-ce qui vous fait penser que la capture de l'exception sera plus lente?

Combien de tentatives échouées pour analyser un GUID attendez-vous par rapport aux tentatives réussies?

Mon conseil est d'utiliser la fonction que vous venez de créer et de profiler votre code. Si vous trouvez que cette fonction est vraiment un point chaud puis le corriger , mais pas avant.


2
Bonne réponse, l'optimisation prématurée est la racine de tout mal.
Kev

33
C'est une mauvaise forme de s'appuyer sur des exceptions qui ne sont pas exceptionnelles. C'est une mauvaise habitude dans laquelle je ne voudrais pas que quiconque prenne. Et je ne voudrais surtout pas le faire dans une routine de bibliothèque où les gens auront confiance que cela fonctionne et bien.
Ian Boyd le

Anonyme, votre question initiale indiquait la performance comme la raison pour laquelle vous vouliez éviter les exceptions. Si ce n'est pas le cas, vous devriez peut-être peaufiner votre question.
AnthonyWJones

6
L'exception doit être utilisée dans les cas EXCEPTIONNELS signifiant: non géré par le développeur. Je suis un opposant à la manière de gérer les erreurs de Microsoft «toutes exceptionnelles». Règles de programmation défensives. Merci aux développeurs de framework Microsoft, envisagez d'ajouter un 'TryParse' à la classe Guid.
Mose le

14
en réponse à mon propre commentaire => Guid.TryParse a été ajouté au framework 4.0 --- msdn.microsoft.com/en-us/library/ ... --- merci MS pour une telle réaction rapide;)
Mose

39

Dans .NET 4.0, vous pouvez écrire comme suit:

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

3
Cela devrait vraiment être l'une des meilleures réponses.
Tom Lint

21

Je voudrais au moins le réécrire comme:

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

Vous ne voulez pas dire "GUID invalide" sur SEHException, ThreadAbortException ou tout autre élément fatal ou non lié.

Mise à jour : à partir de .NET 4.0, un nouvel ensemble de méthodes est disponible pour Guid:

Vraiment, ceux-ci devraient être utilisés (ne serait-ce que pour le fait qu'ils ne sont pas implémentés "naïvement" en utilisant try-catch en interne).


13

L'interopérabilité est plus lente que de simplement attraper l'exception:

Dans la bonne voie, avec 10000 Guids:

Exception:    26ms
Interop:   1,201ms

Sur le chemin malheureux:

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

C'est plus cohérent, mais c'est aussi toujours plus lent. Il me semble que vous feriez mieux de configurer votre débogueur pour qu'il ne casse que sur les exceptions non gérées.


"votre débogueur pour ne casser que sur les exceptions non gérées" Pas une option.
Ian Boyd

1
@Ian Boyd - Si vous utilisez une des éditions VS (y compris Express), il est une option. msdn.microsoft.com/en-us/library/038tzxdw.aspx .
Mark Brackett

1
je voulais dire que ce n'est pas une option réalisable. Comme, "l'échec n'est pas une option." Il est une option, mais que je ne vais pas utiliser.
Ian Boyd

9

Eh bien, voici la regex dont vous aurez besoin ...

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

Mais ce n'est que pour les débutants. Vous devrez également vérifier que les différentes parties telles que la date / heure sont dans des plages acceptables. Je ne peux pas imaginer que cela soit plus rapide que la méthode try / catch que vous avez déjà décrite. J'espère que vous ne recevez pas autant de GUID invalides pour justifier ce type de vérification!


Euh, les GUID IIRC générés à partir d'un horodatage sont généralement considérés comme une mauvaise idée et l'autre type (type 4) est totalement aléatoire
BCS

5

pour des raisons de convivialité - le débogueur apparaît

Si vous optez pour l'approche try / catch, vous pouvez ajouter l'attribut [System.Diagnostics.DebuggerHidden] pour vous assurer que le débogueur ne s'arrête pas même si vous l'avez défini pour casser lors du lancer.


4

S'il est vrai que l'utilisation d'erreurs est plus coûteuse, la plupart des gens croient que la majorité de leurs GUID seront générés par ordinateur, donc un TRY-CATCHn'est pas trop cher car il ne génère que des coûts sur le CATCH. Vous pouvez vous le prouver par un simple test des deux (utilisateur public, pas de mot de passe).

Voici:

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

J'ai eu une situation similaire et j'ai remarqué que la chaîne invalide n'avait presque jamais 36 caractères. Donc, sur la base de ce fait, j'ai un peu changé votre code pour obtenir de meilleures performances tout en restant 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;
    }
}

1
Guid accepte plus que la forme de chaîne en pointillés dans son ctor. Les GUID peuvent avoir des accolades avec des tirets ou être exempts de tirets ou d'accolades. Ce code générera de faux négatifs lorsqu'il est utilisé par ces formes de chaîne alternatives mais aussi parfaitement valides.
Chris Charabaruk le

1
Pour suivre, les longueurs valides pour les GUID sous forme de chaîne sont respectivement 32, 36 et 38 - hexadécimal pur, tirets et accolades avec tirets.
Chris Charabaruk le

1
@Chris, votre point est valide, mais l'idée de @JBrooks de vérifier la cohérence du GUID potentiel avant de se lancer dans try / catch a du sens, en particulier si une entrée suspecte est courante. Peut-être quelque chose comme if (value == null || value.Length <30 || value.length> 40) {value = Guid.Empty; return false;}
bw

1
En effet, ce serait mieux, même si je garderais la gamme plus serrée, 32..38 plutôt que 30..40.
Chris Charabaruk

2

Autant que je sache, il n'y a pas quelque chose comme Guid.TryParse dans mscrolib. Selon la source de référence, le type Guid a un constructeur méga-complexe qui vérifie toutes sortes de formats de guid et essaie de les analyser. Il n'y a pas de méthode d'assistance que vous pouvez appeler, même par réflexion. Je pense que vous devez rechercher des parseurs Guid tiers ou écrire le vôtre.


2

Exécutez le GUID potentiel via un RegEx ou un code personnalisé qui effectue une vérification de cohérence pour vous assurer que le strig ressemble au moins à un GUID et se compose uniquement de caractères valides (et peut-être qu'il semble correspondre au format global). S'il ne réussit pas le contrôle de cohérence, renvoyez une erreur - cela éliminera probablement la grande majorité des chaînes invalides.

Ensuite, convertissez la chaîne comme vous l'avez fait ci-dessus, en récupérant toujours l'exception pour les quelques chaînes non valides qui passent par la vérification de cohérence.

Jon Skeet a fait une analyse pour quelque chose de similaire pour analyser Ints (avant TryParse était dans le Framework): vérifier si une chaîne peut être convertie en Int32

Cependant, comme AnthonyWJones l'a indiqué, vous ne devriez probablement pas vous inquiéter à ce sujet.


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

"-" "{" "}" ("et") "ne sont pas des caractères hexadécimaux valides, mais sont valides dans une chaîne GUID.
Preston Guillot

2
et ce code fonctionnera parfaitement bien si la chaîne de guidage d'entrée contient ces caractères non hexadécimaux
rupello

1
  • Obtenir un réflecteur
  • copy'n'paste .ctor de Guid (chaîne)
  • remplacez chaque occurrence de "throw new ..." par "return false".

Le ctor de Guid est à peu près une regex compilée, de cette façon vous obtiendrez exactement le même comportement sans surcharge de l'exception.

  1. Cela constitue-t-il une ingénierie inverse? Je pense que oui, et en tant que tel, cela pourrait être illégal.
  2. Sera cassé si le formulaire GUID change.

Une solution encore plus cool serait d'instrumenter dynamiquement une méthode, en remplaçant «lancer nouveau» à la volée.


1
J'ai essayé de voler le code de ctor, mais il fait référence à de nombreuses classes privées internes pour effectuer son travail de support. Croyez-moi, c'était ma première tentative.
Ian Boyd

1

Je vote pour le lien GuidTryParse posté ci-dessus par Jon ou une solution similaire (IsProbablyGuid). Je vais en écrire un comme ceux de ma bibliothèque de conversion.

Je pense qu'il est totalement nul que cette question soit si compliquée. Le mot clé "is" ou "as" conviendrait parfaitement SI un Guid pouvait être nul. Mais pour une raison quelconque, même si SQL Server est d'accord avec cela, .NET ne l'est pas. Pourquoi? Quelle est la valeur de Guid.Empty? C'est juste un problème stupide créé par la conception de .NET, et cela me dérange vraiment lorsque les conventions d'un langage se renversent. La réponse la plus performante à ce jour a été d'utiliser COM Interop parce que le Framework ne le gère pas correctement? "Cette chaîne peut-elle être un GUID?" devrait être une question à laquelle il est facile de répondre.

Se fier à l'exception levée est OK, jusqu'à ce que l'application soit sur Internet. À ce stade, je viens de me préparer à une attaque par déni de service. Même si je ne suis pas «attaqué», je sais que certains yahoo vont faire un singe avec l'URL, ou peut-être que mon service marketing enverra un lien malformé, et alors mon application devra subir un impact assez lourd en termes de performances qui POURRAIT apporter sur le serveur parce que je n'ai pas écrit mon code pour gérer un problème qui NE DEVRAIT PAS arriver, mais nous savons tous QU'IL ARRIVERA.

Cela brouille un peu la ligne sur "Exception" - mais en fin de compte, même si le problème est rare, si cela peut arriver suffisamment de fois dans un court laps de temps pour que votre application se bloque en réparant les captures de tout cela, alors je pense que lancer une exception est mauvaise forme.

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

Avec une méthode d'extension en C #

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.