Ответ 1
Я хорошо знаю, что это может быть глупый вопрос
Это не так.
С# обрабатывает анонимные методы и замыкания, введя их в методы экземпляра анонимного вложенного класса, создавая экземпляр этого класса, а затем указывая делегаты на эти методы экземпляра.
С# иногда это делает.
Похоже, что этот анонимный класс может быть однажды создан только один раз (или я ошибаюсь в этом?), так почему бы не сделать анонимный класс статическим?
В тех случаях, когда это будет законным, С# делает вас лучше. Это не делает класс закрытия вообще. Это делает анонимную функцию статической функцией текущего класса.
И да, вы ошибаетесь в этом. В случаях, когда вы можете уйти с распределением делегата только один раз, С# уходит с ним.
(Это, строго говоря, не совсем верно, есть некоторые неясные случаи, когда эта оптимизация не реализована, но по большей части она есть.)
На самом деле, он выглядит как один класс для замыканий и один для анонимных методов, которые не фиксируют какие-либо переменные, что я не совсем понимаю обоснование для.
Вы положили пальцем на то, что не понимаете.
Посмотрим на некоторые примеры:
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 для нескольких вложенных lambdas с getter и setter lambdas для всех локальных пользователей, параметризованных родовыми типами в области класса и метода, которые выполняют вызовы base
? Потому что проблема, которую мы на самом деле должны были решить.