Являются ли делегаты более легкими, чем классы? - PullRequest
8 голосов
/ 19 февраля 2011

Я пытался разобрать исполняемый файл, созданный на C #, но не смог прийти к выводу.Что я хотел бы знать, так это то, что если для CLR делегаты c # являются действительно специальными объектами или просто сахаром компилятора?

Я спрашиваю это, потому что я реализую язык, который компилируется в C #, имне гораздо интереснее составлять анонимные функции как классы, чем как делегаты.Но я не хочу использовать дизайн, о котором позже пожалею, так как он может быть тяжелее для памяти (я думаю о PermGen в Java, на котором я буду основывать свои вопросы. Хотя я знаю, что для CLR такого нет).

Спасибо!

- отредактируйте

, чтобы быть немного более понятным, я хотел бы знать, есть ли (и каковы) различия между:

void Main()
{
    Func<int, int, int> add = delegate(int a, int b) {return a + b;};
}

и, например,

class AnonFuncion__1219023 : Fun3
{
    public override int invoke(int a, int b)
    {
        return a + b;
    }
}

- правка

Я думаю, что может быть большая разница между:

class Program
{
    static int add(int a, int b)
    {
        return a + b;
    }

    static void Main()
    {
        Func<int, int, int> add = Program.add;
    }
}

и

class Function__432892 : Fun3
{
    public override int invoke(int a, int b)
    {
        return Program.add(a, b);
    }
}

Я где-то читал, что синтаксис Func<int, int, int> add = Program.add; является всего лишь сахаром для Func<int, int, int> add = delegate(int a, int b) { return Program.add; };.Но я действительно не знаю, правда ли это.Я также мог видеть, что компилятор C # уже кэширует все эти экземпляры, поэтому они создаются только один раз.Я мог бы сделать то же самое с моим компилятором.

Ответы [ 5 ]

7 голосов
/ 19 февраля 2011

Я удивлен, что вы не могли прийти к выводу, разобрав исполняемый файл. Давайте посмотрим на что-то действительно простое:

        using System;

        class A
        {
          int _x;

          public A(int x)
          {
            _x = x;
          }

          public void Print(int y)
          {
            Console.WriteLine(_x + y);
          }
        }

        interface IPseudoDelegateVoidInt
        {
          void Call(int y);
        }


        class PseudoDelegateAPrint : IPseudoDelegateVoidInt
        {
          A _target;
          public PseudoDelegateAPrint(A target)
          {
            _target = target;
          }

          public void Call(int y)
          {
            _target.Print(y);
          }
        }



        class Program
        {
          delegate void RealVoidIntDelegate(int x);
          static void Main()
          {
            A a = new A(5);
            IPseudoDelegateVoidInt pdelegate = new PseudoDelegateAPrint(a);
            RealVoidIntDelegate rdelegate = new RealVoidIntDelegate(a.Print);
            pdelegate.Call(2);
            rdelegate(2);
          }
        }

Если мы разберем это, мы получим

        //  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.1
        //  Copyright (c) Microsoft Corporation.  All rights reserved.



        // Metadata version: v4.0.30319
        .assembly extern mscorlib
        {
          .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
          .ver 4:0:0:0
        }
        .assembly del
        {
          .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
          .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
                                                                                                                     63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )       // ceptionThrows.
          .hash algorithm 0x00008004
          .ver 0:0:0:0
        }
        .module del.exe
        // MVID: {87A2A843-A5F2-4D40-A96D-9940579DE26E}
        .imagebase 0x00400000
        .file alignment 0x00000200
        .stackreserve 0x00100000
        .subsystem 0x0003       // WINDOWS_CUI
        .corflags 0x00000001    //  ILONLY
        // Image base: 0x0000000000B60000


        // =============== CLASS MEMBERS DECLARATION ===================

        .class private auto ansi beforefieldinit A
               extends [mscorlib]System.Object
        {
          .field private int32 _x
          .method public hidebysig specialname rtspecialname 
                  instance void  .ctor(int32 x) cil managed
          {
            // Code size       17 (0x11)
            .maxstack  8
            IL_0000:  ldarg.0
            IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
            IL_0006:  nop
            IL_0007:  nop
            IL_0008:  ldarg.0
            IL_0009:  ldarg.1
            IL_000a:  stfld      int32 A::_x
            IL_000f:  nop
            IL_0010:  ret
          } // end of method A::.ctor

          .method public hidebysig instance void 
                  Print(int32 y) cil managed
          {
            // Code size       16 (0x10)
            .maxstack  8
            IL_0000:  nop
            IL_0001:  ldarg.0
            IL_0002:  ldfld      int32 A::_x
            IL_0007:  ldarg.1
            IL_0008:  add
            IL_0009:  call       void [mscorlib]System.Console::WriteLine(int32)
            IL_000e:  nop
            IL_000f:  ret
          } // end of method A::Print

        } // end of class A

        .class interface private abstract auto ansi IPseudoDelegateVoidInt
        {
          .method public hidebysig newslot abstract virtual 
                  instance void  Call(int32 y) cil managed
          {
          } // end of method IPseudoDelegateVoidInt::Call

        } // end of class IPseudoDelegateVoidInt

        .class private auto ansi beforefieldinit PseudoDelegateAPrint
               extends [mscorlib]System.Object
               implements IPseudoDelegateVoidInt
        {
          .field private class A _target
          .method public hidebysig specialname rtspecialname 
                  instance void  .ctor(class A target) cil managed
          {
            // Code size       17 (0x11)
            .maxstack  8
            IL_0000:  ldarg.0
            IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
            IL_0006:  nop
            IL_0007:  nop
            IL_0008:  ldarg.0
            IL_0009:  ldarg.1
            IL_000a:  stfld      class A PseudoDelegateAPrint::_target
            IL_000f:  nop
            IL_0010:  ret
          } // end of method PseudoDelegateAPrint::.ctor

          .method public hidebysig newslot virtual final 
                  instance void  Call(int32 y) cil managed
          {
            // Code size       15 (0xf)
            .maxstack  8
            IL_0000:  nop
            IL_0001:  ldarg.0
            IL_0002:  ldfld      class A PseudoDelegateAPrint::_target
            IL_0007:  ldarg.1
            IL_0008:  callvirt   instance void A::Print(int32)
            IL_000d:  nop
            IL_000e:  ret
          } // end of method PseudoDelegateAPrint::Call

        } // end of class PseudoDelegateAPrint

        .class private auto ansi beforefieldinit Program
               extends [mscorlib]System.Object
        {
          .class auto ansi sealed nested private RealVoidIntDelegate
                 extends [mscorlib]System.MulticastDelegate
          {
            .method public hidebysig specialname rtspecialname 
                    instance void  .ctor(object 'object',
                                         native int 'method') runtime managed
            {
            } // end of method RealVoidIntDelegate::.ctor

            .method public hidebysig newslot virtual 
                    instance void  Invoke(int32 x) runtime managed
            {
            } // end of method RealVoidIntDelegate::Invoke

            .method public hidebysig newslot virtual 
                    instance class [mscorlib]System.IAsyncResult 
                    BeginInvoke(int32 x,
                                class [mscorlib]System.AsyncCallback callback,
                                object 'object') runtime managed
            {
            } // end of method RealVoidIntDelegate::BeginInvoke

            .method public hidebysig newslot virtual 
                    instance void  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
            {
            } // end of method RealVoidIntDelegate::EndInvoke

          } // end of class RealVoidIntDelegate

          .method private hidebysig static void  Main() cil managed
          {
            .entrypoint
            // Code size       45 (0x2d)
            .maxstack  3
            .locals init (class A V_0,
                     class IPseudoDelegateVoidInt V_1,
                     class Program/RealVoidIntDelegate V_2)
            IL_0000:  nop
            IL_0001:  ldc.i4.5
            IL_0002:  newobj     instance void A::.ctor(int32)
            IL_0007:  stloc.0
            IL_0008:  ldloc.0
            IL_0009:  newobj     instance void PseudoDelegateAPrint::.ctor(class A)
            IL_000e:  stloc.1
            IL_000f:  ldloc.0
            IL_0010:  ldftn      instance void A::Print(int32)
            IL_0016:  newobj     instance void Program/RealVoidIntDelegate::.ctor(object,
                                                                                  native int)
            IL_001b:  stloc.2
            IL_001c:  ldloc.1
            IL_001d:  ldc.i4.2
            IL_001e:  callvirt   instance void IPseudoDelegateVoidInt::Call(int32)
            IL_0023:  nop
            IL_0024:  ldloc.2
            IL_0025:  ldc.i4.2
            IL_0026:  callvirt   instance void Program/RealVoidIntDelegate::Invoke(int32)
            IL_002b:  nop
            IL_002c:  ret
          } // end of method Program::Main

          .method public hidebysig specialname rtspecialname 
                  instance void  .ctor() cil managed
          {
            // Code size       7 (0x7)
            .maxstack  8
            IL_0000:  ldarg.0
            IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
            IL_0006:  ret
          } // end of method Program::.ctor

        } // end of class Program


        // =============================================================

        // *********** DISASSEMBLY COMPLETE ***********************
        // WARNING: Created Win32 resource file C:\Users\logan\del.res

Как видите, RealVoidIntDelegate становится "просто" другим классом. Они назвали это Invoke вместо Call, и они не использовали интерфейс, но никаких специальных инструкций не было, это та же самая основная идея.

Чтобы немного прояснить идею «легковесности», делегаты не легче, чем классы, потому что данный делегат является классом. Особенно в случае буквального синтаксиса функции, они действительно не могут быть намного легче. Однако для случая «вызовите этот метод для этого объекта с этими аргументами» вызов и создание данного делегата, вероятно, будет легче, чем делегат, выращенный в домашних условиях при компиляции в C #.

3 голосов
/ 19 февраля 2011

Здесь нет большой загадки.Ченнелинг Джон Скит ...

Если вы посмотрите на 6.5.3 спецификации C # , вы увидите несколько примеров того, как компилятор обрабатывает анонимные делегаты.Краткое резюме:

IF анонимный метод не захватывает внешние переменные,
THEN он может быть создан как статический метод для типа вложения.

IF анонимный метод ссылается на члены типа включения, например, this.x
THEN он может быть создан как метод экземпляра для типа включения.

IF анонимный метод захватывает локальную переменную,
THEN он может быть создан как вложенный тип во включающем типе с переменными экземпляра, которые соответствуют захваченным переменными метод экземпляра, соответствующий типу делегата.

Последний пример более сложный, и на него проще взглянуть на код.Посмотрите сами, и я думаю, что это устранит все догадки.

3 голосов
/ 19 февраля 2011

Делегаты - это классы. Компилятор .NET создает тип для каждого делегата, а код должен создавать экземпляр делегата.

В любом случае разница в производительности будет незначительной, если вы не пишете одно из очень редких приложений, где важна каждая нано секунда.

1 голос
/ 19 февраля 2011

Помимо метаданных, действительно нет такой вещи, как класс, когда все становится JIT скомпилирован. Представление объекта во время выполнения представляет собой массив байтов большого размера достаточно, чтобы хранить все поля класса и это базовые классы плюс заголовок объекта, который хранит хеш-код, числовой идентификатор типа и указатель на общий v-таблица, содержащая адреса переопределений виртуальных функций.

Делегат - это объект, класс которого является производным от System.Delegate. Он хранит массив пар указателей объектов и функций.

Анонимная функция - это функция без имени. Тем не менее, они также обычно ассоциируется с объектом, называемым замыканием, который содержит все параметры и локальные объекты, определенные в содержащем методе, который создал «анонимный делегат», который указывает на анонимную функцию. (На самом деле он обычно содержит только те переменные, к которым осуществляется доступ).

В любом случае перемещение переменных стека в кучу позволяет анонимному делегату пережить свой определяющий кадр стека.

Самый простой способ связать анонимную функцию с ее замыканием - это сделать его методом сгенерированного компилятором класса замыкания.

Итак, если у вас есть лексические замыкания, вы должны (по крайней мере, в некоторых случаях) использовать класс для реализации анонимных функций.

Если у вас нет лексических замыканий, вы можете просто выдать «анонимную функцию» как обычная функция с именем, сгенерированным компилятором, рядом с методом, который ее объявил. Это также полезная оптимизация в тех случаях, когда язык поддерживает лексические замыкания, но в этом нет необходимости.

В общем случае анонимные функции бесполезны без поддержки замыканий, поэтому я бы не стал включать их в ваш язык без поддержки замыканий.

1 голос
/ 19 февраля 2011

Кажется, что между ними очень мало различий, верхний фрагмент эффективно компилируется во что-то, почти идентичное тому, что есть в нижнем фрагменте.

...