测试 string 是否为 guid 而不引发异常?

我想尝试将字符串转换为 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-I来预测错误数据的原因。

例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 我可能在解析用户输入的搜索表达式。

enter image description here

如果它们输入 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

另外,我不需要为一个问题辩护。

78377 次浏览

通过正则表达式或一些定制代码运行可能的 GUID,进行健全性检查,以确保字符串至少看起来像一个 GUID,并且只包含有效的字符(也许它似乎符合整体格式)。如果它没有通过完整性检查,返回一个错误-这可能会剔除绝大多数无效字符串。

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

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

然而,正如 Anthony WJones所指出的那样,您可能不应该担心这个问题。

您可能不会喜欢这样做,但是是什么让您认为捕获异常会更慢呢?

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

我的建议是使用刚刚创建的函数并分析代码。如果你发现这个函数真的是一个热点 那么修复它,但不是之前。

这是你需要的正则表达式。

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

据我所知,没有什么能比得上吉德。在 mscrolib 中尝试解析。根据 Reference Source,Guid 类型有一个超级复杂的构造函数,它检查各种 Guid 格式并尝试解析它们。没有可以调用的助手方法,即使是通过反射。我认为您必须搜索第三方 Guid 解析器,或者编写自己的解析器。

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

虽然 的确使用错误更昂贵,但是大多数人相信他们的 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;
}

我至少会把它改写成:

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

你不想说“无效的 GUID”在 SEHException,Thread堕胎异常或其他致命的或不相关的东西。

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

实际上,应该使用这些方法(如果仅仅是因为它们没有在内部使用 try-catch“天真地”实现的话)。

表现基准

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,并且您关心性能,请使用 COMInterop。

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

new Guid(someString);

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

在快乐之路上,有10000名游击队员:

Exception:    26ms
Interop:   1,201ms

在不幸的道路上:

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

它更加一致,但也一直更慢。在我看来,最好将调试器配置为只在未处理异常时中断。

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

出于易用性的原因-调试器弹出

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

  • 去拿反光镜
  • 复制粘贴 Guid 的. ctor (字符串)
  • 用“ return false”替换每次出现的“ throw new...”。

Guid 的 ctor 基本上是一个已编译的正则表达式,这样就可以得到完全相同的行为,而不会增加异常的开销。

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

甚至更冷的解决方案将是动态仪器的一种方法,通过替换“扔新的”在飞行。

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

我支持上面由 乔恩发布的 GuidTryParse 链接或类似的解决方案(IsProbablyGuid)。我将为我的转换库编写一个类似的代码。

我认为这个问题如此复杂是完全没有说服力的。如果 Guid 可以为 null,那么“ is”或“ as”关键字就可以了。但出于某种原因,即使 SQLServer 对此没有意见,。NET 不是。为什么?Guid 的值是什么。空的?这只是一个愚蠢的问题。NET,当一种语言的约定自行其是的时候,它真的让我感到困扰。到目前为止,表现最好的答案是使用 COMInterop,因为框架不能很好地处理它?“这个字符串可以是 GUID 吗?”应该是一个容易回答的问题。

依赖抛出的异常是可以的,直到应用程序上网。那个时候,我只是设计了一个分布式拒绝服务攻击攻击。即使我没有被“攻击”,我也知道一些雅虎会篡改网址,或者我的营销部门会发送一个错误的链接,然后我的应用程序不得不承受相当大的性能损失,这可能会导致服务器瘫痪,因为我没有编写我的代码来处理一个不应该发生的问题,但是我们都知道会发生。

这有点模糊了“异常”的界限——但是底线是,即使问题不常发生,如果它在很短的时间内发生的次数足以让你的应用程序崩溃,导致所有的捕获,那么我认为抛出一个异常是不好的形式。

The Rage3K

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

在.NET 4.0中,你可以这样写:

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

使用 C # 中的扩展方法

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

从字符串返回 Guid 值。如果 Guid 值无效,则返回 Guid。空的。因为 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;
}
}