【问题标题】:Test if string is a guid without throwing exceptions?测试字符串是否是一个没有抛出异常的 guid?
【发布时间】:2010-09-11 09:59:03
【问题描述】:

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

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

换句话说就是代码:

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

不适合。

我会尝试使用正则表达式,但由于 guid 可以用括号括起来、用大括号括起来、不括起来,所以很难。

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


更新 1

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


更新 2

为什么要担心抛出的异常?我真的经常期待无效的 GUID 吗?

答案是是的。这就是我使用 TryStrToGuid 的原因 - 我我期待错误的数据。

示例 1 Namespace extensions can be specified by appending a GUID to a folder name。我可能正在解析文件夹名称,检查最后 . 之后的文本是否是 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 我可能正在运行一个频繁使用的网络服务器,想要检查一些回发数据的有效性。我不希望无效数据占用比所需资源高 2-3 个数量级的资源。

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

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


更新 3 - 性能基准

测试转换 10,000 个好的 Guid 和 10,000 个坏的 Guid。

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

附言我不应该为一个问题辩护。

【问题讨论】:

  • 为什么这是一个社区维基?
  • 你是对的;您应该不必须证明一个问题的合理性。但是,我感兴趣地阅读了理由(因为它与我在这里阅读本文的原因非常相似)。所以,谢谢你的充分理由。
  • @Jeff 可能是因为 OP 已对其进行了 10 多次编辑 - 请参阅 meta on community wiki
  • 请继续在此页面上查找使用 Guid.TryParse 或 Guid.TryParseExact 的解决方案。使用 .NET 4.0 + 上述解决方案并不是最优雅的
  • @dplante 当我最初在 2008 年问这个问题时,没有4.0。这就是为什么问题和公认的答案是这样的原因。

标签: c# string parsing guid


【解决方案1】:

通过 RegEx 或一些执行完整性检查的自定义代码运行潜在的 GUID,以确保字符串至少看起来像 GUID 并且仅包含有效字符(并且可能看起来适合整体格式)。如果它没有通过完整性检查,则返回错误 - 这可能会清除绝大多数无效字符串。

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

Jon Skeet 对解析 Ints 的类似内容进行了分析(在 TryParse 出现在框架中之前):Checking if a string can be converted to Int32

但是,正如AnthonyWJones 所指出的,您可能不应该担心这一点。

【讨论】:

    【解决方案2】:

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

    与成功的相比,您预计解析 GUID 的尝试失败次数是多少?

    我的建议是使用您刚刚创建的函数并分析您的代码。如果你发现这个功能真的是热点然后修复它而不是之前。

    【讨论】:

    • 好答案,过早优化是万恶之源。
    • 依赖非异常的异常是一种糟糕的形式。这是一个我不想让任何人养成的坏习惯。而且我特别不想在人们相信它运作良好的图书馆例行程序中这样做。
    • 匿名,您最初的问题将性能作为您想要避免异常的原因。如果不是这样,那么也许你应该调整你的问题。
    • Exception 应该用在 EXCEPTIONNAL 情况下'意思是:不是由开发者管理的。我反对微软管理错误的“所有例外”方式。防御性编程规则。请 Microsoft 框架开发人员考虑将“TryParse”添加到 Guid 类。
    • 回应我自己的评论 => Guid.TryParse 已添加到框架 4.0 --- msdn.microsoft.com/en-us/library/… --- 感谢 MS 如此快速的反应 ;)
    【解决方案3】:

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

    ^[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)是完全随机的
    【解决方案4】:

    据我所知,mscrolib 中没有类似 Guid.TryParse 的东西。根据参考资料,Guid 类型有一个超级复杂的构造函数,它检查各种 guid 格式并尝试解析它们。没有可以调用的辅助方法,即使通过反射也是如此。我认为您必须搜索 3rd 方 Guid 解析器,或编写自己的。

    【讨论】:

      【解决方案5】:
       bool IsProbablyGuid(string s)
          {
              int hexchars = 0;
              foreach(character c in string s)
              {
                 if(IsValidHexChar(c)) 
                     hexchars++;          
              }
              return hexchars==32;
          }
      

      【讨论】:

      • "-" "{" "}"(" 和 ")" 不是有效的十六进制字符,但在 guid 字符串中有效。
      • 如果输入的 guid 字符串包含那些非十六进制字符,此代码将运行良好
      【解决方案6】:

      虽然 确实使用错误会更昂贵,但大多数人认为他们的大部分 GUID 将由计算机生成,因此 TRY-CATCH 并不会太昂贵,因为它只会生成CATCH 的费用。您可以通过对two(用户公开,无密码)的简单测试来证明这一点。

      给你:

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

      【讨论】:

        【解决方案7】:

        我至少会把它改写成:

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

        您不想在 SEHException、ThreadAbortException 或其他致命或不相关的东西上说“无效的 GUID”。

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

        真的,应该使用这些(如果只是因为它们不是“天真地”在内部使用 try-catch 实现的)。

        【讨论】:

          【解决方案8】:

          性能基准

          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。

          如果您需要将字符串表示形式的 guid 转换为 Guid,请使用

          new Guid(someString);
          

          【讨论】:

          • 您是在打开还是关闭调试器的情况下运行这些程序的?在不附加调试器的情况下,异常抛出的性能提高了几倍。
          • 谢谢。我正要自己问这个问题。很高兴我找到了你的答案。
          • 我从上面创建了一个名为 PInvoke.cs 的新文件,其名称空间为 PInvoke 代码 sn-p,但我无法让代码工作。当我调试时,我看到 CLSIDFromString 的结果总是否定的。我尝试将调用行更改为: int hresult = PInvoke.ObjBase.CLSIDFromString(Guid.NewGuid().ToString(), out value);但它仍然总是消极的。我做错了什么?
          【解决方案9】:

          互操作比仅仅捕获异常要慢:

          在幸福的道路上,有 10,000 个向导:

          Exception:    26ms
          Interop:   1,201ms
          

          在不愉快的道路上:

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

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

          【讨论】:

          • “你的调试器只在未处理的异常上中断”不是一个选项。
          • @Ian Boyd - 如果您使用任何 VS 版本(包括 Express),它是一个选项。 msdn.microsoft.com/en-us/library/038tzxdw.aspx.
          • 我的意思是这不是一个可行的选择。比如,“失败不是一种选择。”它是一个选项,但我不会使用它。
          【解决方案10】:

          如果 TypeOf ctype(myvar,Object) 是 Guid 那么.....

          【讨论】:

            【解决方案11】:

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

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

            【讨论】:

              【解决方案12】:
              • 获取反射器
              • 复制'n'粘贴 Guid 的 .ctor(String)
              • 将每次出现的“throw new ...”替换为“return false”。

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

              1. 这是否构成逆向工程?我认为确实如此,因此可能是非法的。
              2. 如果 GUID 表单发生更改,将会中断。

              更酷的解决方案是动态检测方法,通过动态替换“throw new”。

              【讨论】:

              • 我尝试从 ctor 窃取代码,但它引用了许多内部私有类来执行其支持工作。相信我,那是我的第一次尝试。
              【解决方案13】:

              我也遇到过类似的情况,我注意到无效字符串几乎没有 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;
                  }
              }
              

              【讨论】:

              • Guid 接受的不仅仅是其 ctor 中的虚线字符串形式。 GUID 可以有带破折号的花括号,也可以没有破折号或大括号。当这些替代但也是完全有效的字符串形式使用时,此代码将产生误报。
              • 接下来,字符串形式的 GUID 的有效长度分别为 32、36 和 38——纯十六进制、虚线和带破折号的大括号。
              • @Chris,你的观点是正确的,但是@JBrooks 在进入 try/catch 之前检查预期 GUID 的想法是有道理的,特别是在可疑输入很常见的情况下。也许像 if( value==null || value.Length 40 ) {value=Guid.Empty;return false;}
              • 确实,这样会更好,虽然我会保持范围更窄,32..38 而不是 30..40。
              【解决方案14】:
              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
              

              【讨论】:

                【解决方案15】:

                我投票支持Jon 上面发布的 GuidTryParse 链接或类似的解决方案 (IsProbablyGuid)。我将为我的转换库写一个类似的。

                我认为这个问题必须如此复杂,这完全是蹩脚的。如果 Guid 可以为空,“is”或“as”关键字就可以了。但是出于某种原因,即使 SQL Server 可以接受,.NET 也不能。为什么? Guid.Empty 的值是多少?这只是 .NET 的设计造成的一个愚蠢的问题,当一种语言的约定超越自身时,它真的让我很烦恼。迄今为止表现最好的答案是使用 COM 互操作,因为框架不能很好地处理它? “这个字符串可以是 GUID 吗?”应该是一个容易回答的问题。

                依赖抛出的异常是可以的,直到应用程序上线。那时我只是为拒绝服务攻击做好了准备。即使我没有受到“攻击”,我也知道一些雅虎会使用 URL 进行欺骗,或者我的营销部门可能会发送一个格式错误的链接,然后我的应用程序必须遭受相当严重的性能打击,这可能会带来关闭服务器,因为我没有编写代码来处理不应该发生的问题,但我们都知道会发生。

                这有点模糊了“异常”的界限 - 但最重要的是,即使问题不常见,如果它可以在短时间内发生足够多的时间以使您的应用程序崩溃为所有这些捕获提供服务,那么我认为抛出异常是错误的形式。

                TheRage3K

                【讨论】:

                  【解决方案16】:

                  .net 4.0 可用后,您可以使用Guid.TryParse()。

                  【讨论】:

                  • 一种更快的方法是使用 Guid.TryParseExact() 方法。
                  • 如果解析 Guid 字符串是您的应用程序中最慢的部分,那么您是幸运的。
                  【解决方案17】:

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

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

                  【讨论】:

                  • 这确实应该是最佳答案之一。
                  【解决方案18】:

                  在 C# 中使用扩展方法

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

                  【讨论】:

                    【解决方案19】:

                    从字符串返回 Guid 值。如果 Guid 值无效,则返回 Guid.Empty。由于 Guid 是 struct 类型,无法返回 Null 值

                        /// <summary>
                        /// Gets the GUID from string.
                        /// </summary>
                        /// <param name="guid">The GUID.</param>
                        /// <returns></returns>
                        public static Guid GetGuidFromString(string guid)
                        {
                            try
                            {
                                if (Guid.TryParse(guid, out Guid value))
                                {
                                    return value;
                                }
                                else
                                {
                                    return Guid.Empty;
                                }
                            }
                            catch (Exception)
                            {
                                return Guid.Empty;
                            }
                        }
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2021-02-26
                      • 1970-01-01
                      • 2020-12-21
                      • 2015-01-26
                      • 2019-06-11
                      • 1970-01-01
                      相关资源
                      最近更新 更多