测试字符串是否为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; } }
不适合。
我会尝试使用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
p.s.我不必证明一个问题。
解决方案
通过RegEx或者一些进行完整性检查的自定义代码运行潜在的GUID,以确保strig至少看起来像GUID,并且仅由有效字符组成(并且可能看起来符合整体格式)。如果它没有通过健全性检查,则返回一个错误,该错误可能会淘汰掉绝大多数无效字符串。
然后像上面一样转换字符串,仍然捕获通过健全性检查的少数无效字符串的异常。
Jon Skeet对解析Ints的类似操作进行了分析(在TryParse进入Framework之前):检查是否可以将字符串转换为Int32
但是,正如AnthonyWJones指出的那样,我们可能不必为此担心。
我们不会喜欢这样,但是是什么让我们觉得捕获异常的速度会变慢?
与成功的GUID相比,我们期望多少次失败的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中没有类似Guid.TryParse的东西。根据参考资料,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; }
我们不想在SEHException,ThreadAbortException或者其他致命或者不相关的内容上说"无效的GUID"。
更新:从.NET 4.0开始,有一组适用于Guid的新方法:
Guid.TryParse
Guid.TryParseExact
确实,应该使用那些(如果仅出于事实,它们不是在内部使用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,并且在意性能,请使用COM Interop。
如果需要将String表示形式的guid转换为Guid,请使用
new Guid(someString);
Interop比捕获异常要慢:
在幸福的道路上,有10,000吉德:
Exception: 26ms Interop: 1,201ms
在不幸的道路上:
Exception: 1,150ms Interop: 1,201ms
它更一致,但也始终较慢。在我看来,最好将调试器配置为仅在未处理的异常时中断。
如果TypeOf ctype(myvar,Object)被引导,则.....
for usability reasons - the debugger pops up
如果我们尝试使用try / catch方法,则可以添加[System.Diagnostics.DebuggerHidden]属性,以确保即使将调试器设置为在抛出时中断,调试器也不会中断。
- 获取反射器
- 复制'n'paste Guid的.ctor(String)
- 用" return false"替换每次出现的" throw new ..."。
Guid的ctor几乎是一个已编译的正则表达式,这样我们将获得完全相同的行为,而不会产生异常开销。
- 这构成逆向工程吗?我认为确实如此,因此可能是非法的。
- 如果GUID格式更改,将中断。
甚至更酷的解决方案是通过动态替换" throw new"来动态地检测一种方法。
我也有类似的情况,我注意到无效字符串几乎从来没有长过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
我投票给Jon在上面发布的GuidTryParse链接或者类似的解决方案(IsProbablyGuid)。我将为我的转换库编写类似的内容。
我认为这个问题如此复杂完全是totally脚。如果Guid可以为null,则" is"或者" as"关键字就很好。但是由于某些原因,即使SQL Server可以,.NET也不行。为什么? Guid.Empty的值是多少?这只是.NET设计所造成的一个愚蠢的问题,当一种语言的约定逐渐浮出水面时,这确实使我感到烦恼。到目前为止,性能最好的答案一直是使用COM Interop,因为Framework无法优雅地处理它吗? "此字符串可以是GUID吗?"应该是一个容易回答的问题。
直到应用程序可以上网之前,依靠抛出的异常是可以的。到那时,我只是为拒绝服务攻击做好了准备。即使我没有受到"攻击",我也知道有些雅虎会使用URL,或者我的市场部门可能会发送格式错误的链接,然后我的应用程序可能会遭受性能的严重影响关闭服务器,因为我没有编写代码来处理应该不会发生的问题,但是我们都知道会发生。
这使" Exception"上的内容稍微模糊了一点,即使最常见的问题是很少出现的问题,如果它可能在很短的时间内发生足够的时间,导致应用程序崩溃,无法为所有捕获的内容提供服务,那么我认为抛出异常是不好的形式。
愤怒3K
一旦.net 4.0可用,我们可以使用Guid.TryParse()
。