Je veux essayer de convertir une chaîne en Guid, mais je ne veux pas compter sur la capture d’exceptions (
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 puisque le guid peut être enveloppé de parenthèses, enveloppé d'une accolade, aucun enveloppé, rend la tâche difficile.
De plus, je pensais que certaines valeurs Guid n'étaient pas valides (?)
Mise à jour 1
ChristianK a eu une bonne idée de ne capturer que FormatException
, plutôt que tous. Modification de l'exemple de code de la question pour inclure une suggestion.
Mise à jour 2
Pourquoi s'inquiéter des exceptions jetées? Est-ce que je m'attends vraiment si souvent à avoir des GUID invalides?
La réponse est oui. C'est pourquoi j'utilise TryStrToGuid - Je attend l'attente de données incorrectes.
Exemple 1 Des 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 en vérifiant si le texte après la dernière . 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'utilise 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 invalides bloquent les ressources de 2 à 3 ordres de grandeur plus élevés que nécessaire.
Exemple 3 Je pourrais analyser une expression de recherche entrée par un utilisateur.
S'ils saisissent des GUID, je souhaite les traiter spécialement (par exemple, rechercher spécifiquement cet objet ou mettre en surbrillance et mettre en forme ce terme de recherche spécifique dans le texte de la réponse.)
Mise à jour 3 - Critères de performance
Testez la conversion de 10 000 bons Guids et de 10 000 mauvais 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
p.s. Je ne devrais pas avoir à justifier une question.
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);
}
}
Conclusion: si vous avez besoin de vérifier si une chaîne est un guide et que vous vous souciez des performances, utilisez COM Interop.
Si vous devez convertir un guid dans la représentation de chaîne en un guid, utilisez
new Guid(someString);
Une fois que .net 4.0 est disponible, vous pouvez utiliser Guid.TryParse()
.
Vous n'allez pas aimer ça, mais qu'est-ce qui vous fait penser qu'attraper l'exception va être plus lent?
Combien de tentatives infructueuses d’analyser un GUID attendez-vous par rapport à celles qui ont réussi?
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 alors corrigez-le mais pas avant.
Dans .NET 4.0, vous pouvez écrire comme suit:
public static bool IsValidGuid(string str)
{
Guid guid;
return Guid.TryParse(str, out guid);
}
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 non valide" dans SEHException, ThreadAbortException ou d'autres éléments fatals ou non liés.
Mise à jour : à partir de .NET 4.0, un nouvel ensemble de méthodes est disponible pour Guid:
En réalité, 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).
Interop est plus lent que la capture de l'exception:
Sur le bon chemin, avec 10.000 Guids:
Exception: 26ms
Interop: 1,201ms
Dans 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 n'interrompe que les exceptions non gérées.
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 commencer. Vous devrez également vérifier que les différentes parties, telles que la date et l'heure, se situent dans les limites 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 recevrez pas autant de GUID non valides pour justifier ce type de vérification!
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 se casse pas même si vous l'avez configuré pour le lancer.
Bien que soit vrai que l’utilisation d’erreurs coûte plus cher, la plupart des gens pensent que la majorité de leurs GUID seront générés par ordinateur, donc un TRY-CATCH
_ n'est pas trop cher puisqu'il génère uniquement des coûts sur le CATCH
. Vous pouvez le prouver par un simple test de 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;
}
J'ai eu une situation similaire et j'ai remarqué que la chaîne invalide n'a presque jamais été longue de 36 caractères. Donc, basé sur ce fait, j'ai légèrement modifié 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;
}
}
Exécutez le potentiel GUID si un RegEx ou un code personnalisé effectuant un contrôle de cohérence) permet de s'assurer que la strig ressemble au moins à un GUID et ne contient que des caractères valides). (et peut-être que cela semble correspondre au format global.) S'il échoue, le test d'intégrité renvoie une erreur, ce qui éliminera probablement la grande majorité des chaînes non valides.
Convertissez ensuite la chaîne comme vous l’avez indiqué ci-dessus, en prenant toujours l’exception pour les quelques chaînes non valides qui passent le contrôle de cohérence.
Jon Skeet a analysé quelque chose de similaire pour analyser Ints (avant que TryParse ne soit dans le Framework): Vérification de la possibilité de convertir une chaîne en Int32
Cependant, comme AnthonyWJones a indiqué que vous ne devriez probablement pas vous inquiéter à ce sujet.
Autant que je sache, il n'y a pas quelque chose comme Guid.TryParse dans mscrolib. Selon Reference Source, le type Guid a un constructeur méga-complexe qui vérifie tous les types de formats de guidage et tente de les analyser. Vous ne pouvez pas appeler de méthode d'assistance, même par réflexion. Je pense que vous devez rechercher des analyseurs tiers de Guid ou écrire le vôtre.
bool IsProbablyGuid(string s)
{
int hexchars = 0;
foreach(character c in string s)
{
if(IsValidHexChar(c))
hexchars++;
}
return hexchars==32;
}
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.
Une solution encore plus froide consisterait à instrumenter de manière dynamique une méthode en remplaçant "lancer le nouveau" à la volée.
Je vote pour le lien GuidTryParse posté ci-dessus par Jon ou une solution similaire (IsProbablyGuid). Je vais en écrire un comme celui de ma bibliothèque de conversion.
Je pense que c'est totalement nul que cette question soit si compliquée. Le mot clé "est" ou "en tant que" conviendrait parfaitement SI un Guid pouvait être null. Mais pour quelque raison que ce soit, même si SQL Server convient, mais pas .NET. Pourquoi? Quelle est la valeur de Guid.Empty? C’est juste un problème idiot créé par la conception de .NET, et cela m’embête vraiment lorsque les conventions d’une langue s’appliquent. La réponse la plus performante à ce jour a été l'utilisation de COM Interop car 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.
Compter sur l'exception, c'est correct, jusqu'à ce que l'application passe sur Internet. À ce stade, je viens de me préparer à une attaque par déni de service. Même si je ne me fais pas "attaquer", je sais que certains utilisateurs de Yahoo vont utiliser l'URL, ou peut-être que mon service marketing enverra un lien malformé, et que mon application subira un coup assez lourd en termes de performances qui pourrait amener sur le serveur parce que je n’ai pas écrit mon code pour traiter un problème qui NE DEVRAIT PAS se produire, mais nous savons tous que ça va arriver.
Cela brouille un peu la ligne sur "Exception" - mais au fond, même si le problème est peu fréquent, si cela peut se produire suffisamment de fois en un temps record pour que votre application se bloque en utilisant toutes les captures, je pense que le lancement d'une exception est mauvaise forme.
TheRage3K
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
Avec une méthode d'extension en C #
public static bool IsGUID(this string text)
{
return Guid.TryParse(text, out Guid guid);
}
si TypeOf ctype (myvar, Object) est Guid alors .....