为什么C#将匿名方法和闭包实现为实例方法,而不是静态方法?

时间:2017-02-10 18:57:41

标签: c# oop programming-languages

由于我不完全是编程语言方面的专家,我很清楚这可能是一个愚蠢的问题,但最好我可以告诉C#通过将它们变成匿名嵌套类的实例方法来处理匿名方法和闭包[1],实例化这个类,然后将委托指向那些实例方法。

看来这个匿名类只能被实例化一次(或者我错了吗?),那么为什么不让匿名类变为静态呢?

[1]实际上,看起来有一个闭包类和一个不捕获任何变量的匿名方法,我不完全理解其中的任何一个。

1 个答案:

答案 0 :(得分:28)

  

我很清楚这可能是一个愚蠢的问题

不是。

  

C#通过将匿名方法和闭包设置为匿名嵌套类的实例方法,实例化此类,然后将委托指向这些实例方法来处理匿名方法和闭包。

C#有时会

  

看来这个匿名类只能被实例化一次(或者我错了吗?),那么为什么不让匿名类变为静态呢?

如果这是合法的,C#会让你更好。它根本不构成封闭类。它使匿名函数成为当前类的静态函数。

是的,你错了。如果您只能分配一次委托,C# 就可以逃脱它。

(严格来说,这并不完全正确;有一些模糊的情况没有实现这种优化。但在大多数情况下都是如此。)

  

实际上,看起来有一个闭包类和一个不捕获任何变量的匿名方法,我不完全理解其中的任何一个。

你已经把手指放在你不太了解的事情上。

让我们看一些例子:

class C1
{
  Func<int, int, int> M()
  {
    return (x, y) => x + y;
  }
}

这可以生成为

class C1
{
  static Func<int, int, int> theFunction;
  static int Anonymous(int x, int y) { return x + y; }
  Func<int, int, int> M()
  {
    if (C1.theFunction == null) C1.theFunction = C1.Anonymous;
    return C1.theFunction;
  }
}

不需要新班级。

现在考虑:

class C2
{
  static int counter = 0;
  int x = counter++;
  Func<int, int> M()
  {
    return y => this.x + y;
  }
}

你知道为什么用静态函数不能生成这个吗? 静态函数需要访问this.x ,但静态函数中的 this 在哪里?没有一个。

所以这个必须是一个实例函数:

class C2
{
  static int counter = 0;
  int x = counter++;
  int Anonymous(int y) { return this.x + y; }
  Func<int, int> M()
  {
    return this.Anonymous;
  }
}

此外,我们不能再将代理缓存在静态字段中;你知道为什么吗?

练习:委托可以缓存在实例字段中吗?如果不是,那么是什么阻止了这种合法性呢?如果是,那么反对实施这种“优化”的一些论点是什么?

现在考虑:

class C3
{
  static int counter = 0;
  int x = counter++;
  Func<int> M(int y)
  {
    return () => x + y;
  }
}

这不能作为C3的实例函数生成;你知道为什么吗?我们需要能够说:

var a = new C3();
var b = a.M(123);
var c = b(); // 123 + 0
var d = new C3();
var e = d.M(456);
var f = e(); // 456 + 1
var g = a.M(789);
var h = g(); // 789 + 0

现在,代理人不仅需要知道this.x的值,还需要知道传入的y的值。那必须存储某处,所以我们将它存储在一个字段中。但它不能是C3的字段,因为那么我们如何告诉b使用123和g使用789作为y的值?它们具有C3的相同实例,但y有两个不同的值。

class C3
{
  class Locals
  {
    public C3 __this;
    public int __y;
    public int Anonymous() { return this.__this.x + this.__y; }
  }
  Func<int> M(int y)
  {
    var locals = new Locals();
    locals.__this = this;
    locals.__y = y;
    return locals.Anonymous;
  }
}

练习:现在假设我们C4<T>使用泛型方法M<U>,其中lambda关闭T和U类型的变量。描述必须发生的codegen现在

练习:现在假设我们让M返回一个代表元组,一个是()=>x + y,另一个是(int newY)=>{ y = newY; }。描述两位代表的代码。

练习:现在假设M(int y)返回Func<int, Func<int, int>>类型,我们返回a => b => this.x + y + z + a + b。描述codegen。

练习:假设lambda关闭this并且本地执行base非虚拟呼叫。出于安全原因,从不直接在虚方法的类型层次结构中的类型内的代码执行base调用是非法的。描述在这种情况下如何生成可验证的代码。

练习:把它们放在一起。你如何使用getter和setter lambdas为所有本地人做多个嵌套lambda的codegen,由类和方法范围的泛型类型参数化,进行base调用?因为这是我们实际需要解决的问题