Почему 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
звонки? Потому что это проблема, которую нам действительно пришлось решить.