185

我想尝试将字符串转换为 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 可以通过将 GUID 附加到文件夹名称来指定命名空间扩展。我可能正在解析文件夹名称,检查最后的. 是一个 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 个好的 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

ps 我不应该证明一个问题的合理性。

4

19 回答 19

109

性能基准

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 互操作。

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

new Guid(someString);
于 2008-11-13T19:00:03.017 回答
91

一旦 .net 4.0 可用,您就可以使用Guid.TryParse().

于 2009-12-31T04:58:34.490 回答
67

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

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

我的建议是使用您刚刚创建的函数并分析您的代码。如果您发现此功能确实是热点请修复它,但不要在此之前修复它。

于 2008-09-19T19:41:21.003 回答
42

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

public static bool IsValidGuid(string str)
{
    Guid guid;
    return Guid.TryParse(str, out guid);
}
于 2011-06-30T10:18:11.950 回答
22

我至少会将其重写为:

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

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

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

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

于 2008-09-26T04:49:46.363 回答
14

互操作比捕获异常要慢:

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

Exception:    26ms
Interop:   1,201ms

在不愉快的道路上:

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

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

于 2008-11-14T17:02:10.183 回答
10

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

^[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 来保证这种类型的检查!

于 2008-09-19T19:46:19.870 回答
6

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

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

于 2009-05-27T17:54:49.447 回答
5

虽然使用错误确实更昂贵,但大多数人认为他们的大部分 GUID 将由计算机生成,因此 aTRY-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;
  }
于 2008-09-19T19:52:20.213 回答
5

我遇到了类似的情况,我注意到无效字符串几乎没有 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;
    }
}
于 2009-07-26T19:56:40.260 回答
2

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

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

Jon Skeet 对解析 Ints 的类似内容进行了分析(在 TryParse 在框架中之前): 检查字符串是否可以转换为 Int32

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

于 2008-09-19T19:41:07.760 回答
2

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

于 2008-09-19T19:48:06.293 回答
1
 bool IsProbablyGuid(string s)
    {
        int hexchars = 0;
        foreach(character c in string s)
        {
           if(IsValidHexChar(c)) 
               hexchars++;          
        }
        return hexchars==32;
    }
于 2008-09-19T19:50:27.770 回答
1
  • 获取反射器
  • 复制'n'粘贴 Guid 的 .ctor(String)
  • 用“return false”替换每次出现的“throw new ...”。

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

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

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

于 2009-05-27T17:59:13.150 回答
1

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

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

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

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

狂暴3K

于 2009-08-22T23:21:18.247 回答
0

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

于 2009-05-23T14:20:55.867 回答
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
于 2009-08-20T11:54:44.483 回答
0

使用 C# 中的扩展方法

public static bool IsGUID(this string text)
{
    return Guid.TryParse(text, out Guid guid);
}
于 2017-07-20T20:29:54.777 回答
0

从字符串返回 Guid 值。如果 Guid 值无效,则返回 Guid.Empty。无法返回 Null 值,因为 Guid 是结构类型

    /// <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;
        }
    }
于 2021-11-08T17:51:10.223 回答