52

Consider these functions:

static void Take(object o)
{
    Console.WriteLine("Received an object");
}

static void Take(int i)
{
    Console.WriteLine("Received an integer");
}

When I call the Take function this way:

var a = (object)2;
Take(a);

I get :Received an object

But if call it like:

dynamic b = (object) 2;
Take(b);

I get:Received an integer

Both parameters (a & b) are cast to object. But why compiler has this behavior?

Henrik Andersson
  • 45,354
  • 16
  • 98
  • 92
  • 2
    http://blogs.msdn.com/b/csharpfaq/archive/2010/01/25/what-is-the-difference-between-dynamic-and-object-keywords.aspx – Peyman Tahmasebi Jun 25 '13 at 10:37
  • 1
    `a` has compile-time type `object`. That's an ordinary **static** C# type, so the **binding** to a specific overload happens at compile-time based on the compile-time type. Since that's `object`, it's an easy choice. On the other hand, `b` has compile-time type `dynamic`. The boxing to `object` is redundant. Remember that `dynamic` is equal to `object`, only with binding deferred until runtime. The entire idea with `dynamic` is to not keep track of the type compile-time, and then wait with the binding till runtime. Since the runtime type is `Int32`, again it's easy. – Jeppe Stig Nielsen Jun 30 '13 at 18:54

6 Answers6

55

var is just a syntactic sugar to let the type to be decided by the RHS.

In your code:

var a = (object)2;

is equivalent to:

object a = (object)2;

You get an object, since you boxed 2 to an object.

For dynamic, you might want to have a look at Using Type dynamic. Note that The type is a static type, but an object of type dynamic bypasses static type checking, that is, the type you specified of:

dynamic b = (object) 2;

is bypassed, and the real type of it is resolved at runtime.


For how it's resolved at runtime, I believe it's much more complicated than you can imagine ..

Say you have the following code:

public static class TestClass {
    public static void Take(object o) {
        Console.WriteLine("Received an object");
    }

    public static void Take(int i) {
        Console.WriteLine("Received an integer");
    }

    public static void TestMethod() {
        var a=(object)2;
        Take(a);

        dynamic b=(object)2;
        Take(b);
    }
}

and I put the full IL(of debug configuration) at the rear of my answer.

For these two lines:

var a=(object)2;
Take(a);

the IL are only:

IL_0001: ldc.i4.2
IL_0002: box [mscorlib]System.Int32
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: call void TestClass::Take(object)

But for these two:

dynamic b=(object)2;
Take(b);

are from IL_000f to IL_007a of TestMethod. It doesn't call the Take(object) or Take(int) directly, but invokes the method like this way:

object b = 2;
if (TestClass.<TestMethod>o__SiteContainer0.<>p__Site1 == null)
{
    TestClass.<TestMethod>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Take", null, typeof(TestClass), new CSharpArgumentInfo[]
    {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
    }));
}
TestClass.<TestMethod>o__SiteContainer0.<>p__Site1.Target(TestClass.<TestMethod>o__SiteContainer0.<>p__Site1, typeof(TestClass), b);

The full IL of TestClass:

.class public auto ansi abstract sealed beforefieldinit TestClass
    extends [mscorlib]System.Object
{
    // Nested Types
    .class nested private auto ansi abstract sealed beforefieldinit '<TestMethod>o__SiteContainer0'
        extends [mscorlib]System.Object
    {
        .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Fields
        .field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> '<>p__Site1'

    } // end of class <TestMethod>o__SiteContainer0


    // Methods
    .method public hidebysig static 
        void Take (
            object o
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 13 (0xd)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldstr "Received an object"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ret
    } // end of method TestClass::Take

    .method public hidebysig static 
        void Take (
            int32 i
        ) cil managed 
    {
        // Method begins at RVA 0x205e
        // Code size 13 (0xd)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldstr "Received an integer"
        IL_0006: call void [mscorlib]System.Console::WriteLine(string)
        IL_000b: nop
        IL_000c: ret
    } // end of method TestClass::Take

    .method public hidebysig static 
        void TestMethod () cil managed 
    {
        // Method begins at RVA 0x206c
        // Code size 129 (0x81)
        .maxstack 8
        .locals init (
            [0] object a,
            [1] object b,
            [2] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000
        )

        IL_0000: nop
        IL_0001: ldc.i4.2
        IL_0002: box [mscorlib]System.Int32
        IL_0007: stloc.0
        IL_0008: ldloc.0
        IL_0009: call void TestClass::Take(object)
        IL_000e: nop
        IL_000f: ldc.i4.2
        IL_0010: box [mscorlib]System.Int32
        IL_0015: stloc.1
        IL_0016: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_001b: brtrue.s IL_0060

        IL_001d: ldc.i4 256
        IL_0022: ldstr "Take"
        IL_0027: ldnull
        IL_0028: ldtoken TestClass
        IL_002d: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
        IL_0032: ldc.i4.2
        IL_0033: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
        IL_0038: stloc.2
        IL_0039: ldloc.2
        IL_003a: ldc.i4.0
        IL_003b: ldc.i4.s 33
        IL_003d: ldnull
        IL_003e: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
        IL_0043: stelem.ref
        IL_0044: ldloc.2
        IL_0045: ldc.i4.1
        IL_0046: ldc.i4.0
        IL_0047: ldnull
        IL_0048: call class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::Create(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
        IL_004d: stelem.ref
        IL_004e: ldloc.2
        IL_004f: call class [System.Core]System.Runtime.CompilerServices.CallSiteBinder [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.Binder::InvokeMember(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, string, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
        IL_0054: call class [System.Core]System.Runtime.CompilerServices.CallSite`1<!0> class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
        IL_0059: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_005e: br.s IL_0060

        IL_0060: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_0065: ldfld !0 class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>>::Target
        IL_006a: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>> TestClass/'<TestMethod>o__SiteContainer0'::'<>p__Site1'
        IL_006f: ldtoken TestClass
        IL_0074: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
        IL_0079: ldloc.1
        IL_007a: callvirt instance void class [mscorlib]System.Action`3<class [System.Core]System.Runtime.CompilerServices.CallSite, class [mscorlib]System.Type, object>::Invoke(!0, !1, !2)
        IL_007f: nop
        IL_0080: ret
    } // end of method TestClass::TestMethod

} // end of class TestClass
Community
  • 1
  • 1
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • 1
    For the overload resolution, there's a good explanation on wiki -- [C Sharp 4.0](http://en.wikipedia.org/wiki/C_Sharp_4.0). – Ken Kin Jun 25 '13 at 09:16
  • For the compiler generated names, have a look of Mr. Lippert's answer: http://stackoverflow.com/questions/2508828/where-to-learn-about-vs-debugger-magic-names/2509524#2509524 – Ken Kin Jun 25 '13 at 11:44
  • 1
    The complex overload resolution is the same for `dynamic` versus a usual non-dynamic type like object. The only difference is that for `dynamic`, this complex algorithm to decide on an overload, happens not until runtime, based on the actual runtime type (like the `GetType()` output). With a non-dynamic type, this same binding happens at compile-time, based on the _declared_ type (compile-time type) of the variable, or expression. – Jeppe Stig Nielsen Jun 30 '13 at 19:03
15

dynamic:

  1. dynamic is a Dynamically typed
  2. Dynamically typed - This means the type of variable declared is decided by the compiler at run time.

var:

  1. var is a Statically typed
  2. Statically typed – This means the type of variable declared is decided by the compiler at compile time.

By this, you see that Overload resolution occurs at run time for dynamic.

So variable b holds as int

dynamic b = (object) 2;
Take(b);

That's the reason why Take(b); calls Take(int i)

static void Take(int i)
    {
        Console.WriteLine("Received an integer");
    }

But in the case of var a = (object)2, variable a holds as 'object'

var a = (object)2;
Take(a);

That's the reason why Take(a); calls Take(object o)

static void Take(object o)
    {
        Console.WriteLine("Received an object");
    }
Siva Charan
  • 17,940
  • 9
  • 60
  • 95
  • 8
    Your explanation of `dynamic` is not quite right: the type of the variable *is* `object`. However, when that variable is used in an expression, that will cause the compiler to be invoked at run-time, and to perform evaluation of the expression based on the run-time types present (roughly speaking.) – dlev Jun 25 '13 at 07:21
  • 4
    Is it me or does this not actually answer the question? – Pero P. Jun 25 '13 at 07:31
  • @ppejovic Nope it's not just, you, I read it 10 times, not seeing the explanation, but maybe I'm just an imbecile ... – Dimitar Dimitrov Jun 25 '13 at 07:38
  • Number 2 is on runtime cast to integer, because of the nature of dynamic. It seems that dynamic ignores explicit cast. – jnovacho Jun 25 '13 at 07:51
  • We understand it calls `Take(int i)` the question is -> why ? – Dimitar Dimitrov Jun 25 '13 at 07:57
  • @DimitarDimitrov: I have already stated that "Overload resolution occurs at run time" – Siva Charan Jun 25 '13 at 07:59
  • @DimitarDimitrov: What is the difficulty in understand? – Siva Charan Jun 25 '13 at 08:00
  • @SivaCharan I saw that, however since overload resolution is occurring during run time, why isn't it resolving to `object` instead ? I don't see how your example explains it, mate – Dimitar Dimitrov Jun 25 '13 at 08:00
  • @DimitarDimitrov: Sorry for misunderstood. I got your point, now I have updated the answer. – Siva Charan Jun 25 '13 at 08:07
  • @SivaCharan Why does `dynamic b = (object)2;` hold an `int` ? – Dimitar Dimitrov Jun 25 '13 at 08:09
  • 2
    @DimitarDimitrov: An object instance can be of only one type at runtime. Not just `dynamic` but also `object a = (object)2; a.GetType();` will give you `System.Int32` at _runtime_. It is just that the first overload resolution was done by compiler at compile time thats why it resolves to `object` overload. – YK1 Jun 25 '13 at 08:20
  • @YK1 Thanks, I get it now, `a.GetType()` resolving to `System.Int32` at runtime clicked :) – Dimitar Dimitrov Jun 25 '13 at 08:25
  • 2
    `the compiler at run time` how does the compiler could be invoked at run time, it might be the C# VM or C# interpreter that resolves the type of that dynamic variable. – NeeL Jun 26 '13 at 07:45
2

The boxed integer argument resolution happens at compile time. Here is the IL:

IL_000d:  box        [mscorlib]System.Int32
IL_0012:  stloc.0
IL_0013:  ldloc.0
IL_0014:  call       void ConsoleApp.Program::Take(object)

You can see it is resolved to the object overload at compile time itself.

When you use dynamic - the runtime binder comes into picture. dynamic not only can resolve to managed C# objects, but also to non-managed objects like COM objects or JavaScript objects given a runtime binder exists for those objects.

Instead of showing IL, I'll show decompiled code (easier to read):

   object obj3 = 2;
        if (<Main>o__SiteContainer0.<>p__Site1 == null)
        {
            <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Take", null, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsStaticType | CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
        }
        <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, typeof(Program), obj3);

You see that Take method is resolved at runtime by the runtime binder and not by compiler. So, it will resolve it to the actual type.

YK1
  • 7,327
  • 1
  • 21
  • 28
1

If you take a look on C# specification:

1.6.6.5 Method overloading

Method overloading permits multiple methods in the same class to have the same name as long as they have unique signatures. When compiling an invocation of an overloaded method, the compiler uses overload resolution to determine the specific method to invoke.

And:

7.5.4 Compile-time checking of dynamic overload resolution

For most dynamically bound operations the set of possible candidates for resolution is unknown at compiletime. In certain cases, however the candidate set is known at compile-time:

  • Static method calls with dynamic arguments

  • Instance method calls where the receiver is not a dynamic expression

  • Indexer calls where the receiver is not a dynamic expression

  • Constructor calls with dynamic arguments

In these cases a limited compile-time check is performed for each candidate to see if any of them could possibly apply at run-time

So, in your first case, var is not dynamic, overload resolution will find overload method at compile-time.

But in your second case, you are calling static method with dynamic arguments, overload resolution will find overload method at the run-time instead

cuongle
  • 74,024
  • 28
  • 151
  • 206
  • I think the question is when its bound at runtime.. why doesn't the explicit cast to object take effect? – Simon Whitehead Jun 25 '13 at 07:48
  • 1
    @SimonWhitehead: I'd consider this answer is helpful about the overload resolution which is not shown obviously in other answers, even mine. – Ken Kin Jun 25 '13 at 09:09
1

To help understand type resolution in your case for dynamic variables.

  • First put a break point at the line:

    Take(b); //See here the type of b when u hover mouse over it, will be int

which clearly means that the code: dynamic b = (object)2 does not convert 2 to an object when assigned to a dynamic variable and b remains an int

  • Next, comment out your overload of Take(int i) method and then put a break point on line Take(b) (same: type of b is still int) but when you run it, you will see printed value is: Recieved object.

  • Now, change your dynamic variable call to below code:

    Take((object)b); //It now prints "Received an object"

  • Next, change your call to below code and see what is returned:

    dynamic b = (long)2;

    Take(b); // It now prints Received an object because there is no method overload that accepts a long and best matching overload is one that accepts anobject.

This is because: best matching type is resolved for the dynamic variables according to the value it holds at runtime and also best matching overload method to be called is resolved at runtime for dynamic variables .

S2S2
  • 8,322
  • 5
  • 37
  • 65
0

In first case var means object so Take(object o) is called. In second case you use dynamic type and despite that you've boxed your int it still has information about it's type -int so the best matching method is called.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132