2017-02-10 3 views
11

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

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


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

+0

@ HansPassant: Я в замешательстве - вы говорите, что вызовы делегатов к методам экземпляра * быстрее * чем делегировать вызовы статическим методам и что это является основанием для того, чтобы скрытые классы имели поля экземпляра, а не статические ? –

+2

Дело Ганса в том, что нет «статических классов». Статический класс - это просто класс, который (1) абстрактный, (2) запечатан, (3) имеет только статические методы. С точки зрения CLR это просто обычный класс. Оптимизация: (1) можно кэшировать делегат и повторно использовать его? и (2) можно ли избежать выделения класса замыкания? См. Мой ответ для случаев, когда эти оптимизации возможны. –

ответ

23

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

Это не так.

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

Смежные вопросы