测试字符串是否为GUID而不抛出异常?


180

我想尝试将字符串转换为Guid,但是我不想依赖于捕获异常(

  • 出于性能原因-异常代价高昂
  • 出于可用性原因-调试器弹出
  • 出于设计原因-预期并非例外

换句话说,代码:

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

不适合。

我会尝试使用RegEx,但是由于guid可以用括号括起来,括号括起来,没有任何包裹,因此很难。

另外,我认为某些Guid值无效(?)


更新1

ChristianK有个好主意,那就是只抓FormatException,而不是全部。更改了问题的代码示例以包含建议。


更新2

为什么要担心引发异常?我真的经常会遇到无效的GUID吗?

答案是肯定的。这就是为什么我使用TryStrToGuid -我期待坏数据。

示例1 可以通过将GUID附加到文件夹名称来指定名称空间扩展名。我可能正在解析文件夹名称,检查是否在final后面的文本是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

示例2我可能正在运行一个使用率很高的Web服务器,想要检查某些回发数据的有效性。我不希望无效数据占用资源比需要的资源高2-3个数量级。

示例3我可能正在解析用户输入的搜索表达式。

在此处输入图片说明

如果他们输入GUID,我想对其进行特殊处理(例如,专门搜索该对象,或者在响应文本中突出显示并格式化该特定搜索词。)


更新3-性能基准

测试转换10,000个好向导和10,000个坏向导。

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我不必证明一个问题。


7
为什么这是社区维基?
杰夫2010年

36
你是对的; 您不必辩解一个问题。但是,我感兴趣地阅读了理由(因为这与我在这里阅读的原因非常相似)。因此,感谢您的充分辩解。
bw

2
@Jeff可能是因为OP对它进行了10次以上的编辑-请参阅社区Wiki上的meta
Marijn 2011年

3
请继续在此页面上寻找使用Guid.TryParse或Guid.TryParseExact的解决方案。使用.NET 4.0 +,上述解决方案并不是最优雅的解决方案
dplante 2014年

1
@dplante当我最初在2008年问这个问题时,没有4.0。这就是为什么问题和被接受的答案都是这样。
伊恩·博伊德

Answers:


107

绩效基准

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(最快)答案:

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

底线:如果需要检查字符串是否为Guid,并且在意性能,请使用COM Interop。

如果需要将String表示形式的guid转换为Guid,请使用

new Guid(someString);

8
您是否在调试器打开或关闭的情况下运行它们?无需附加调试器,异常抛出的性能提高了数倍。
Daniel T.

谢谢。我本人将要问这个问题。很高兴我找到了您的答案。
David

我已经从上方创建了一个名为PInvoke.cs的新文件,并使用命名空间PInvoke代码段,但是我无法使代码正常工作。调试时,我看到CLSIDFromString的结果始终为负。我尝试将调用行更改为:int hresult = PInvoke.ObjBase.CLSIDFromString(Guid.NewGuid()。ToString(),出值);但它始终是负面的。我究竟做错了什么?
JALLRED


65

您不会喜欢这样,但是是什么让您认为捕获异常的速度会变慢?

与成功的GUID相比,您期望多少次失败的GUID解析?

我的建议是使用刚刚创建的功能并分析代码。如果您发现此功能确实是一个热点,请先修复它,然后再修复。


2
好的答案,过早的优化是万恶之源。
凯夫(Kev)

33
依靠异常的例外情况是一种糟糕的形式。我不想让任何人进入这是一个坏习惯。而且我尤其不希望在图书馆的例行程序中这样做,因为人们会相信它可以正常工作。
伊恩·博伊德

匿名,您最初的问题表示性能是您希望避免出现异常的原因。如果不是这样,那么也许您应该调整您的问题。
AnthonyWJones

6
在例外情况下应使用例外,意思是:不由开发人员管理。我是Microsoft处理错误的“所有例外”方式的反对者。防御性编程规则。请Microsoft框架开发人员,考虑向Guid类添加“ TryParse”。
Mose 2010年

14
响应于我自己的评论=> Guid.TryParse已被添加到框架4.0 --- msdn.microsoft.com/en-us/library/... --- thxs MS对这种快速反应;)
摩西

39

在.NET 4.0中,您可以编写如下内容:

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

3
这确实应该是最佳答案之一。
Tom Lint

21

我至少将其重写为:

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

您不想在SEHException,ThreadAbortException或其他致命或不相关的内容上说“无效的GUID”。

更新:从.NET 4.0开始,有一组适用于Guid的新方法:

实际上,应该使用它们(如果仅出于事实,它们不是在内部使用try-catch“天真”实现的)。


13

Interop比捕获异常要慢:

在幸福的道路上,有10,000吉德:

Exception:    26ms
Interop:   1,201ms

在不幸的道路上:

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

它更一致,但也始终较慢。在我看来,最好将调试器配置为仅在未处理的异常时中断。


“您的调试器仅在未处理的异常时中断”不是一种选择。
伊恩·博伊德

1
@Ian Boyd-如果您使用任何VS版本(包括Express),则可以选择。msdn.microsoft.com/zh-CN/library/038tzxdw.aspx
Mark Brackett

1
我的意思是这不是一个可行的选择。就像“失败不是一种选择”。这一种选择,但是我不会使用。
伊恩·博伊德

9

好吧,这是您需要的正则表达式...

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

但这只是初学者。您还必须验证各个部分(例如日期/时间)是否在可接受的范围内。我无法想象这比您已经概述的try / catch方法要快。希望您不会收到太多无效的GUID来担保这种检查!


嗯,从时间戳生成的IIRC GUID通常被认为是一个坏主意,而另一种(类型4)则完全是随机的
BCS

5

出于可用性原因-调试器弹出

如果您尝试使用try / catch方法,则可以添加[System.Diagnostics.DebuggerHidden]属性,以确保即使将调试器设置为在抛出时中断,调试器也不会中断。


4

虽然这事实,使用错误比较贵,大多数人认为,他们大多数的GUID将是计算机生成所以TRY-CATCH,因为它仅在产生费用不算太贵CATCH。您可以通过对两者的简单测试来证明自己(用户公共,无密码)。

干得好:

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

我也有类似的情况,我注意到无效字符串几乎永远不会长到36个字符。因此,基于这个事实,我对您的代码做了一些改动,以在保持简洁的同时获得更好的性能。

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不仅在其ctor中接受虚线字符串形式。GUID的花括号可以带有短划线,也可以没有短划线或大括号。当这些代码被那些交替使用但也完全有效的字符串形式使用时,该代码将生成假阴性。
克里斯·查拉巴鲁克

1
为了跟进,字符串形式的GUID的有效长度分别是32、36和38,分别是纯十六进制,虚线和带花括号的。
克里斯·查拉巴鲁克

1
@Chris,您的观点是正确的,但是@JBrooks明智地在尝试进入try / catch之前检查预期的GUID的想法很有意义,尤其是在可疑输入很常见的情况下。也许类似if(value == null || value.Length <30 || value.length> 40){value = Guid.Empty; return false;}
bw 2010年

1
确实,这会更好,尽管我会保持更紧密的范围,即32..38而不是30..40。
克里斯·查拉巴鲁克

2

据我所知,mscrolib中没有类似Guid.TryParse的东西。根据参考资料,Guid类型具有大型复合构造函数,该构造函数会检查各种Guid格式并尝试解析它们。您无法调用任何辅助方法,即使通过反射也是如此。我认为您必须搜索第三方Guid解析器,或者自己编写。


2

通过RegEx或一些进行完整性检查的自定义代码运行潜在的GUID,以确保strig至少看起来像GUID,并且仅由有效字符组成(并且可能看起来符合整体格式)。如果没有通过健全性检查,则返回错误-可能会淘汰掉绝大多数无效字符串。

然后像上面一样转换字符串,仍然捕获通过健全性检查的少数无效字符串的异常。

Jon Skeet对解析Ints的类似操作进行了分析(在TryParse进入Framework之前): 检查是否可以将字符串转换为Int32

但是,正如AnthonyWJones指出的那样,您可能不必为此担心。


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

“ - ”“{”,“}”(”和‘)’是无效的十六进制字符,但在一个GUID串有效。
普雷斯顿GUILLOT

2
如果输入的guid字符串包含那些非十六进制字符,则此代码将运行良好
-rupello

1
  • 获取反射器
  • 复制'n'paste Guid的.ctor(String)
  • 用“ return false”替换每次出现的“ throw new ...”。

Guid的ctor几乎是一个已编译的正则表达式,这样您将获得完全相同的行为,而不会产生异常开销。

  1. 这构成逆向工程吗?我认为确实如此,因此可能是非法的。
  2. 如果GUID格式更改,将中断。

甚至更酷的解决方案是通过动态替换“ throw new”来动态地检测一种方法。


1
我尝试从ctor窃取代码,但是它引用了许多内部私有类来执行其支持工作。相信我,那是我的第一次尝试。
伊恩·博伊德

1

我投票给Jon在上面发布的GuidTryParse链接或类似的解决方案(IsProbablyGuid)。我将为我的转换库编写类似的内容。

我认为这个问题如此复杂完全是la脚。如果Guid可以为null,则“ is”或“ as”关键字就很好。但是由于某些原因,即使SQL Server可以,.NET也不行。为什么?Guid.Empty的值是多少?这只是.NET设计所造成的一个愚蠢的问题,当一种语言惯例逐渐浮出水面时,这确实使我感到烦恼。到目前为止,性能最好的答案一直是使用COM Interop,因为Framework无法优雅地处理它吗?“此字符串可以是GUID吗?” 应该是一个容易回答的问题。

直到应用程序可以上网之前,依靠抛出的异常是可以的。那时,我只是为拒绝服务攻击做好了准备。即使我没有受到“攻击”,我也知道某些雅虎会使用URL,或者我的市场部门可能会发送格式错误的链接,然后我的应用程序可能会遭受相当大的性能损失关闭服务器,因为我没有编写代码来处理应该不会发生的问题,但是我们都知道会发生。

这使“ Exception”上的行模糊了一些,但最重要的是,即使问题很少发生,如果它可能在很短的时间内发生足够的时间,导致您的应用程序崩溃,无法为所有捕获的内容提供服务,那么我认为抛出异常是不良形式。

愤怒3K



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

使用C#中的扩展方法

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.