是等效于 C# 中的静态 Func 成员的静态函数
本文关键字:静态 Func 成员 静态函数 | 更新日期: 2023-09-27 18:32:05
看起来静态方法与静态 Func 字段相同。我是否错过了什么,或者它们本质上是可以互换的(相同的足迹等)?
静态属性的结尾与其他两个示例相同,只是它包含"get"访问器的(最小)开销。
也许问得有点毫无意义和肚脐眼...但我喜欢了解"幕后"发生的事情,即使它不是立即相关的。
可以肯定的是:我不打算将所有静态方法切换到lambda表达式(并让我的同事发疯)。但是,可能有一些合理的方案,其中静态变量比编写方法更有意义。或者可能相反:说服某人使用静态方法而不是lambda表达式来使代码更具可读性或其他任何东西
另外,我很好奇是否有更好的方法来调查这类问题。
我的测试
我将这个简单的例子放到 LINQPad 中(v4.57.02,"在启用 /optimize+ 的情况下编译"):
void Main()
{
var hold = "this is the thing. it returns true";
var held = "this is the one that returns false";
Console.WriteLine(One.Check(hold));
Console.WriteLine(One.Check(held));
Console.WriteLine(Two.Check(hold));
Console.WriteLine(Two.Check(held));
}
// Define other methods and classes here
class One
{
public static bool Check(string value)
{
return value != null && value.Contains('.');
}
}
class Two
{
public static Func<string, bool> Check = v => v != null && v.Contains('.');
}
。它为两者生成了相同的 IL。本质上:
XXX.Check:
IL_0000: ldarg.0
IL_0001: brfalse.s IL_000C
IL_0003: ldarg.0
IL_0004: ldc.i4.s 2E
IL_0006: call System.Linq.Enumerable.Contains
IL_000B: ret
IL_000C: ldc.i4.0
IL_000D: ret
XXX..ctor:
IL_0000: ldarg.0
IL_0001: call System.Object..ctor
IL_0006: ret
我想不出任何情况,该方法的代码生成(显式编写的方法或 lambda 主体)会有所不同。C# 编译器中可能存在一个奇怪的(或设计使然)的极端情况,但这不是必需的。之所以会出现这种极端情况,是因为在发出程序集之前必须将 lambda 降低到方法中。这个降低阶段可能会引入(希望是无关紧要的)变化。
关于 lambda 代码生成需要注意的要点是 lambda 可以关闭变量。这要求生成的方法成为生成类上的实例方法。在这里,您正在编写的 lambda 的形式永远不会导致这种情况。
在最近的 C# 编译器版本中,生成的方法是一个虚拟类型的实例方法。这使得委托调用更快(这是不直观的,因为在这种情况下,更多的参数更快)。
调用这种"假"静态方法是委托调用,.NET JIT 没有优化它的工具。(例如,它可以在运行时将调用站点专用于已知的委托目标。JVM 对虚拟调用执行此操作。它直接通过虚拟呼叫内联。热点 JIT 非常先进。这意味着您有间接呼叫开销和松散内联以及所有后续优化。
如果没有必要,则永远不应该这样做。如果要在运行时插入不同的方法,这可能会很有用。或者,也许这些静态Func
变量可以充当缓存。或者,可以在调试器中重新连接它们。
任何此类静态属性的get
访问器都应该完全零成本,因为微小的方法是可靠地内联的。
另一个轻微的性能缺点是增加了类型加载和初始化时间。此外,更多的对象徘徊在周围,减慢了所有未来的G2集合。
嗯,显而易见的答案是不,他们不是。
Two.Check = v => { throw new Exception(); };
为方法生成的 IL 代码是相同的,因为你仍在定义相同的实现,但更改了它的引用方式。
即使Check
readonly
,您仍然可以这样做:
class Two
{
public static readonly Func<string, bool> Check;
static Two()
{
if (Helper.IsBlueMoon && Helper.IsFirst)
{
Check = v => { throw new Exception(); };
} else {
Check = v => v != null && v.Contains('.');
}
}
}
另一个需要注意的重要事项是,使用 Func<string, bool>
时不命名参数,并且不能使用 XML 文档注释