委托类型生成的CIL代码是编译时的还是运行时的?

Whether the CIL code generated for a delegate type is compile time or runtime?

假设有一个委托引用了两个方法,即 Add() 和 Sub() 。我要问的是 C# 编译器是在运行时还是在编译时生成等效的 IL 代码?

例如:

public int delegate Dele(int,int);

//methods
int Add(int a,int b)
{
    //...
}
int Sub(int a,int b)
{
    //...
}


//here comes the condition

if(cond)
{
   Del+=Add;
}
else
{
   Del+=Sub;
}

int ans=Del(4,4);

这里编译器是在编译时还是运行时为真假条件生成cil代码?

首先,委托没有 IL。

Del+=Add;

的语法糖
Del += new Dele(Add);

Dele 是委托类型。在引擎盖下,这是一个 class 和一个 Invoke(int, int) 方法(以及一个 BeginInvoke/EndInvoke 对)。

当你打电话时:

int ans=Del(4,4);

它是语法糖:

int ans = Del.Invoke(4, 4);

Invoke 方法没有 IL 代码 - 它被声明为 virtual extern 并由运行时处理。当然,实际调用需要的机器码是JIT生成的。


引用自 CLI spec 第 172 页(强调我的):

Delegates shall be declared sealed, and the only members a delegate shall have are either the first two or all four methods as specified here. These methods shall be declared runtime and managed (§II.15.4.3). They shall not have a body, since that body shall be created automatically by the VES. Other methods available on delegates are inherited from the class System.Delegate in the Base Class Library (see Partition IV).

here whether the compiler generates the cil code for both true and false conditions at compiletime or runtime?

两个执行路径CIL都生成了。您可以通过编译代码示例看到这一点。这个:

public void X(int x)
{
    Dele del;
    if(Condition(x))
    {
        del = new Dele(Add);
        del(1,1);
    }
    else
    {
        del = new Dele(Sub);
        del(2,1);
    }
}

public bool Condition(int i)
{
    return i % 2 == 0;
}

生成以下 IL:

.method public hidebysig 
    instance void X () cil managed 
{
    // Method begins at RVA 0x205c
    // Code size 51 (0x33)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldc.i4.5
    IL_0002: call instance bool C::Condition(int32)
    IL_0007: brfalse.s IL_001e
    IL_0009: ldarg.0
    IL_000a: ldftn instance int32 C::Add(int32, int32)
    IL_0010: newobj instance void C/Dele::.ctor(object, native int)
    IL_0015: ldc.i4.1
    IL_0016: ldc.i4.1
    IL_0017: callvirt instance int32 C/Dele::Invoke(int32, int32)
    IL_001c: pop
    IL_001d: ret
    IL_001e: ldarg.0
    IL_001f: ldftn instance int32 C::Sub(int32, int32)
    IL_0025: newobj instance void C/Dele::.ctor(object, native int)
    IL_002a: ldc.i4.2
    IL_002b: ldc.i4.1
    IL_002c: callvirt instance int32 C/Dele::Invoke(int32, int32)
    IL_0031: pop
    IL_0032: ret
} // end of method C::X

可以看到第一个代码执行从 IL_000a 开始,其中 Add 已加载。第二个可以在 IL_001f 处看到,其中 Sub 已加载。

运行时唯一发生的事情是创建继承自 MulticastDelegate 的委托,其中创建了三个方法:InvokeBeingInvokeEndInvoke, 如 ECMA-335:

Delegates are created by defining a class that derives from the base type System.Delegate (see Partition IV). Each delegate type shall provide a method named Invoke with appropriate parameters, and each instance of a delegate forwards calls to its Invoke method to one or more static or instance methods on particular objects that are delegate-assignable-to (§II.14.6.1) the signature of the delegate. The objects and methods to which it delegates are chosen when the delegate instance is created. In addition to an instance constructor and an Invoke method, delegates can optionally have two additional methods: BeginInvoke and EndInvoke. These are used for asynchronous calls.

// Nested Types
.class nested public auto ansi sealed Dele
    extends [mscorlib]System.MulticastDelegate
{
    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            object 'object',
            native int 'method'
        ) runtime managed 
    {
    } // end of method Dele::.ctor

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

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

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

} // end of class Dele

引用哪个方法只有在调用时才确定,即在运行时。在我们的例子中,是的,它对优化有一点贡献,因为它只被调用了一次,并且在执行路径中总是只有一个 CIL 代码。希望这就是您正在寻找的答案。