【问题标题】:Is a static function equivalent to a static Func member in C#?静态函数是否等同于 C# 中的静态 Func 成员?
【发布时间】:2016-05-03 17:12:14
【问题描述】:

看起来静态方法静态Func字段相同。我错过了什么,还是它们本质上可以互换(相同的足迹等)?

静态属性最终与其他两个示例相同,只是它包含“get”访问器的(最小)开销。

也许问起来有点毫无意义和直视肚脐……但我喜欢了解“幕后”发生的事情,即使它并不立即相关。

可以肯定的是:我不打算将所有静态方法都转换为 lambda 表达式(这会让我的同事发疯)。但是,在某些合理的情况下,静态变量比编写方法更有意义。或者可能相反:说服某人使用静态方法而不是 lambda 表达式来使代码更具可读性或其他什么

另外,我很好奇是否有更好的方法来调查这类问题

我的测试

我把这个简单的例子放到 LINQPad 中(v4.57.02,“compile with /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

【问题讨论】:

  • 你的字段不是readonly,所以我可以Two.Check = string.IsNullOrEmpty;
  • @JeppeStigNielsen 我错过了“= v”部分……需要更多咖啡。
  • @JeppeStigNielsen 好点。不确定这是一个特性还是一个错误:) 在实践中,我确信有人会用一个属性来包装它(嗯……“在实践中”我会使用一种方法,但你知道)
  • 你可以做Two.Check.GetInvocationList();Console.WriteLine(Two.Check);等等。另一个可以参与方法组转换,如Predicate&lt;string&gt; variable = One.Check;

标签: c# methods lambda static static-methods


【解决方案1】:

嗯,显而易见的答案是不,他们不是。

Two.Check = v => { throw new Exception(); };

为方法生成的 IL 代码是相同的,因为您仍在定义相同的实现,但改变了它的引用方式。

即使 Checkreadonly,您仍然可以这样做:

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&lt;string, bool&gt;时参数没有命名,你不能使用XML Documentation Comments

【讨论】:

  • 哦,对了。它是一个委托,它的关系不同……即使实际的实现是相同的。类似于这样,类似于@JeppeStigNielsen 在 OP cmets 中得到的结果
【解决方案2】:

我想不出方法(显式编写的方法或 lambda 主体)的代码生成会有所不同的任何情况。 C# 编译器中可能存在奇怪(或设计使然)的极端情况,但这不是必需的。可能会出现这种极端情况,因为必须在发出程序集之前将 lambda 降低到方法中。这个降低阶段可能会引入(希望是无关紧要的)变化。

关于 lambda 代码生成的主要注意点是 lambda 可以关闭变量。这要求生成的方法成为生成类的实例方法。在这里,您编写的 lambda 形式永远不会导致这种情况。

在最近的 C# 编译器版本中,生成的方法是虚拟类型上的实例方法。这使得委托调用更快(这是不直观的,因为在这种情况下更多的参数会更快)。

调用这种“假”静态方法是一个委托调用,而 .NET JIT 没有工具可以优化它。 (例如,它可以将调用站点专门化为已知的委托目标。JVM 为虚拟调用执行此操作。它直接通过虚拟调用内联。Hotspot JIT 非常先进。)这意味着您有间接调用开销和松散的内联以及所有后续优化。

如果没有必要,永远不要这样做。如果您想在运行时插入不同的方法,它可能会很有用。或者,也许这些静态的Func 变量可以充当缓存。或者,您可以在调试器中重新连接它们。

任何此类静态属性的get 访问器都应该是完全零成本的,因为微小的方法是可靠内联的。

另一个轻微的性能缺陷是增加了类型加载和初始化时间。此外,更多的物品会拖慢所有未来的 G2 收藏。

【讨论】:

    猜你喜欢
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 2021-09-10
    • 2011-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多