Я хорошо знаю, что это может быть глупый вопрос
Это не так.
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, но где это в статической функции? Нет.
Так что это один должен быть функция экземпляра:
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;
}
}
Это не может быть сформирован как функция экземпляра С3; вы видите, почему? Мы должны быть в состоянии сказать:.
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. Охарактеризуйте CodeGen, который должен произойти в настоящее время.
Упражнение: Теперь предположим, что мы имеем 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 lambdas для всех локальных пользователей, параметризованных родовыми типами в области класса и метода, которые совершают вызовы base
? Потому что вот в чем проблема, мы на самом деле должны были решить.
@ HansPassant: Я в замешательстве - вы говорите, что вызовы делегатов к методам экземпляра * быстрее * чем делегировать вызовы статическим методам и что это является основанием для того, чтобы скрытые классы имели поля экземпляра, а не статические ? –
Дело Ганса в том, что нет «статических классов». Статический класс - это просто класс, который (1) абстрактный, (2) запечатан, (3) имеет только статические методы. С точки зрения CLR это просто обычный класс. Оптимизация: (1) можно кэшировать делегат и повторно использовать его? и (2) можно ли избежать выделения класса замыкания? См. Мой ответ для случаев, когда эти оптимизации возможны. –