为什么委托实例不总是被缓存?
Why aren't delegate instances always cached?
在这种情况下,为什么 C# 编译器不创建缓存 Action(SomeMethod)
的委托实例的代码:
void MyMethod() {
Decorator(SomeMethod);
}
void Decorator(Action a) { a(); }
void SomeMethod() { }
只有当SomeMethod
是静态的时候才会这样做:
static void SomeMethod() { }
编辑:
为了更清楚,我们来看下面的代码:
class A {
public void M1() {
var b = new B();
b.Decorate(M2);
}
public void M2() {
}
}
class B {
public void Decorate(Action a) {
Console.WriteLine("Calling");
a();
}
}
如果你想避免每次调用 M1 时都分配委托,你可以很容易地做到,但它很难看:
using System;
class A {
Action _m2;
public A() {
_m2 = new Action(M2);
}
public void M1() {
var b = new B();
b.Decorate(_m2);
}
public void M2() {
}
}
class B {
public void Decorate(Action a) {
Console.WriteLine("Calling");
a();
}
}
所以我的问题是,编译器无法生成类似代码的原因是什么?我看不到任何副作用。
我并不是说没有理由,从事编译器工作的人可能比我聪明得多。我只是想了解这在哪些情况下不起作用。
无法为实例方法缓存它,因为目标实例是委托的一部分,并且它really 想为缓存使用静态字段。不捕获任何变量等的静态方法调用可以非常便宜地缓存,但是当涉及状态时它会变得更加复杂,并且 this
算作状态。
是的,我想可以使用实例字段来缓存 () => this.SomeMethod()
,但坦率地说,this
作为目标是一种相对罕见的情况,并不能解决一般问题。
但是,它也只针对 lambda 语法执行此操作,即即使 SomeMethod
是 static
Decorator(SomeMethod); // not cached
Decorator(() => SomeMethod()); // cached
你可以看出区别here
这是因为差异是可检测的(不同的对象引用与相同的对象引用)并且 理论上 可能导致使用原始代码(非拉姆达)语法;因此,迄今为止,缓存条款尚未追溯应用于旧语法。兼容性原因。不过,这已经讨论了多年; IMO 它是其中之一,例如对 foreach
L 值捕获的更改,可能会像我们想象的那样在不破坏世界的情况下进行更改。
要查看基于已编辑问题的示例中的理论差异:
using System;
class A
{
static void Main()
{
var obj = new A();
Console.WriteLine("With cache...");
for (int i = 0; i < 5; i++) obj.WithCache();
Console.WriteLine("And without cache...");
for (int i = 0; i < 5; i++) obj.WithoutCache();
}
Action _m2;
B b = new B();
public void WithCache() => b.Decorate(_m2 ??= M2);
public void WithoutCache() => b.Decorate(M2);
public void M2() => Console.WriteLine("I'm M2");
}
class B
{
private object _last;
public void Decorate(Action a)
{
if (_last != (object)a)
{
a();
_last = a;
}
else
{
Console.WriteLine("No do-overs!");
}
}
}
当前输出:
With cache...
I'm M2
No do-overs!
No do-overs!
No do-overs!
No do-overs!
And without cache...
I'm M2
I'm M2
I'm M2
I'm M2
I'm M2
在这种情况下,为什么 C# 编译器不创建缓存 Action(SomeMethod)
的委托实例的代码:
void MyMethod() {
Decorator(SomeMethod);
}
void Decorator(Action a) { a(); }
void SomeMethod() { }
只有当SomeMethod
是静态的时候才会这样做:
static void SomeMethod() { }
编辑:
为了更清楚,我们来看下面的代码:
class A {
public void M1() {
var b = new B();
b.Decorate(M2);
}
public void M2() {
}
}
class B {
public void Decorate(Action a) {
Console.WriteLine("Calling");
a();
}
}
如果你想避免每次调用 M1 时都分配委托,你可以很容易地做到,但它很难看:
using System;
class A {
Action _m2;
public A() {
_m2 = new Action(M2);
}
public void M1() {
var b = new B();
b.Decorate(_m2);
}
public void M2() {
}
}
class B {
public void Decorate(Action a) {
Console.WriteLine("Calling");
a();
}
}
所以我的问题是,编译器无法生成类似代码的原因是什么?我看不到任何副作用。
我并不是说没有理由,从事编译器工作的人可能比我聪明得多。我只是想了解这在哪些情况下不起作用。
无法为实例方法缓存它,因为目标实例是委托的一部分,并且它really 想为缓存使用静态字段。不捕获任何变量等的静态方法调用可以非常便宜地缓存,但是当涉及状态时它会变得更加复杂,并且 this
算作状态。
是的,我想可以使用实例字段来缓存 () => this.SomeMethod()
,但坦率地说,this
作为目标是一种相对罕见的情况,并不能解决一般问题。
但是,它也只针对 lambda 语法执行此操作,即即使 SomeMethod
是 static
Decorator(SomeMethod); // not cached
Decorator(() => SomeMethod()); // cached
你可以看出区别here
这是因为差异是可检测的(不同的对象引用与相同的对象引用)并且 理论上 可能导致使用原始代码(非拉姆达)语法;因此,迄今为止,缓存条款尚未追溯应用于旧语法。兼容性原因。不过,这已经讨论了多年; IMO 它是其中之一,例如对 foreach
L 值捕获的更改,可能会像我们想象的那样在不破坏世界的情况下进行更改。
要查看基于已编辑问题的示例中的理论差异:
using System;
class A
{
static void Main()
{
var obj = new A();
Console.WriteLine("With cache...");
for (int i = 0; i < 5; i++) obj.WithCache();
Console.WriteLine("And without cache...");
for (int i = 0; i < 5; i++) obj.WithoutCache();
}
Action _m2;
B b = new B();
public void WithCache() => b.Decorate(_m2 ??= M2);
public void WithoutCache() => b.Decorate(M2);
public void M2() => Console.WriteLine("I'm M2");
}
class B
{
private object _last;
public void Decorate(Action a)
{
if (_last != (object)a)
{
a();
_last = a;
}
else
{
Console.WriteLine("No do-overs!");
}
}
}
当前输出:
With cache...
I'm M2
No do-overs!
No do-overs!
No do-overs!
No do-overs!
And without cache...
I'm M2
I'm M2
I'm M2
I'm M2
I'm M2