Почему C# реализует анонимные методы и замыкания как методы экземпляра, а не как статические методы?

Поскольку я не совсем эксперт по языкам программирования, я хорошо знаю, что это может быть глупым вопросом, но, насколько я могу судить, C# обрабатывает анонимные методы и замыкания, превращая их в методы экземпляров анонимного вложенного класса [1]. создание экземпляра этого класса и указание делегатов на эти методы экземпляра.

Похоже, что этот анонимный класс может быть создан только один раз (или я ошибаюсь?), Так почему бы не сделать анонимный класс статичным?


[1] На самом деле, похоже, что есть один класс для замыканий и один для анонимных методов, которые не захватывают никаких переменных, что я не совсем понимаю для обоснования.

1 ответ

Решение

Я хорошо знаю, что это может быть глупый вопрос

Это не.

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> где лямбда замкнута по переменным типов T и U. Опишите кодовый код, который должен произойти сейчас.

Упражнение. Теперь предположим, что у нас есть M, возвращающий кортеж делегатов, один из которых ()=>x + y а другое существо (int newY)=>{ y = newY; }, Опишите кодовый код для двух делегатов.

Упражнение: теперь предположим M(int y) возвращает тип Func<int, Func<int, int>> и мы вернемся a => b => this.x + y + z + a + b, Опишите кодоген.

Упражнение: предположим, что лямбда закрыта над обоими this и местный делает base не виртуальный звонок. Это незаконно base по соображениям безопасности вызов из кода внутри типа, не входящего непосредственно в иерархию типов виртуального метода. Опишите, как создать проверяемый код в этом случае.

Упражнение: Положите их все вместе. Как вы делаете codegen для нескольких вложенных лямбда-выражений с лямбда-выражениями getter и setter для всех локальных элементов, параметризованных универсальными типами в области класса и метода, которые делают base звонки? Потому что это проблема, которую нам действительно пришлось решить.

Другие вопросы по тегам