2015-11-02 106 views
0

我有下面的代码,其对任何类型的对象的一个​​空值或空白检查:C# - 最快的方法来做这种类型的空检查?

public static void IfNullOrEmpty(Expression<Func<string>> parameter) 
    { 
    Throw.IfNull(parameter); 
    if (parameter.GetValue().ToString().Length == 0) 
    { 
     throw new ArgumentException("Cannot be empty", parameter.GetName()); 
    } 
    } 

它调用下面的GetValue扩展方法:

public static T GetValue<T>(this Expression<Func<T>> parameter) 
    { 
    MemberExpression member; 
    Expression expression; 

    member = (MemberExpression)parameter.Body; 
    expression = member.Expression; 

    return (T)parameter.Compile()(); 
    } 

我传递在含有表达字符串在这个方法中进行测试。这种方法在我的机器上平均需要2 ms(即使在我测试的另一台机器上速度更慢),如果在整个应用程序中多次调用该方法,该方法会相加。看起来这种方法太慢了。什么是做这种空检查的最快方法?

+0

你可能会计时JIT。对于新手来说,他们应该有一个很大的警告,不要试图去计时,他们总是会犯错。 – Blindy

+0

当我整个分析应用程序时,我看到类似的结果,就好像我只是单独计时代码一样。 – Andrew

+1

为什么你使用表达呢?为什么不只是'功能'? – Enigmativity

回答

1

编译表达式自然需要很多工作。如果这段代码经常运行,我通常会做的事情是我只编译一次表达式并保存编译后的代表以备后用。

有可能保持“正常”缓存但缓存是有效的,你需要一个好的哈希函数,我不看你怎么能有这样的在这里。您需要稍微重构一下代码,以便每个使用GetValue的地方都可以正确访问已编译的委托。没有看到更多的代码,我不能给你任何暗示。

可能有很多原因让您看到以下呼叫速度更快。由于难以哈希,我不期望那一个。更有可能你会看到一个现代CPU的作品,它会做很多猜测来快速运行代码。如果你只运行了相同的表达式,那么CPU可以猜测下一次调用的更多信息,并且运行得更快。总是有GC也要考虑。测试猜测的想法

的一种方法是创建一个具有几个不同的表情大阵。做一个测试,它是按表达式排序的,以及是随机的。如果我的怀疑成立,第一个应该会更快。

+0

如何保存已编译的代表?我可以重复使用相同的委托来传递任何类型的字符串或对象吗? – Andrew

+0

也许把你的方程式放在一个班级里。当存储一个值时,缓存被标记为无效。当您尝试阅读结果并且无效时,请重新计算结果。 –

+0

@Andrew如果你可以在你使用这种方法的地方分享一些代码,我可能会提出一个解决方案。有几种方法,但没有一个足够小以适应每种情况。 –

0

如果我正确地阅读你的代码,你需要一个Expression的唯一原因是,如果检查失败,你将能够提取参数的名字并将它传递给你的异常扔,对吧?如果是这样,这是一个非常陡峭的价格来支付一个稍微更方便的错误消息,(希望)只发生在极少数情况下。

2ms的开销比我期望的略高,但巨大的开销是难以或不可能避免在这里。你本质上是迫使运行时遍历一个表达式树,将它翻译成MSIL,然后通过JIT再次将该MSIL转换并优化为可执行代码,只需执行!= null检查,除非开发人员在某处犯了错误。

你可能会想出某种缓存机制;实体框架通过遍历表达式树并构建它的散列来缓存表达式,并将其用作字典的关键字,其中编译后的表达式作为委托来存储。在每次调用时,它都比通过JIT发送便宜得多,但与简单的!= null检查相比,它的价格仍然要高出数十亿美元(特别是在考虑现代分支预测CPU时) 。

所以在我看来,这种方法是一个不错的主意,但根本不值得,当你考虑成本和替代品是非常痛苦的时候(尤其是新的nameof运营商)。这也是相当脆弱,因为如果我是一个开发谁认为他能做到这一点:

Throw.IfNullOrEmpty(() => clientId + "something")

你投以MemberExpression会失败在那里。

同样,我们有理由有人认为因为在表达式中传递和表达仅仅是因为数据的代码,这将是安全的,做到这一点:

Throw.IfNullOrEmpty(() => parent.Child.MightBeNull.MightAlsoBeNull.ClientID)

这是如果部分遍历表达式树,则完全可能安全地评估该表达式,但在您的示例中,整个表达式将立即编译并执行,并且可能会在此处失败,并显示NullReferenceException

我想这归结于Expression<Func<T>>类型的参数对于使用空检查来说不够严格。你可以做各种奇怪的东西,编译器会非常高兴,但是你会在运行时得到意想不到的结果。

相关问题