Considérez ces fonctions:
static void Take(object o)
{
Console.WriteLine("Received an object");
}
static void Take(int i)
{
Console.WriteLine("Received an integer");
}
Lorsque j'appelle la fonction Take
de cette façon:
var a = (object)2;
Take(a);
Je reçois :Received an object
Mais si vous l'appelez comme:
dynamic b = (object) 2;
Take(b);
Je reçois:Received an integer
Les deux paramètres (a
& b
) sont convertis en object
. Mais pourquoi le compilateur a ce comportement?
var
n'est qu'un sucre syntaxique pour laisser le type être décidé par le RHS .
Dans votre code:
var a = (object)2;
est équivalent à:
object a = (object)2;
Vous obtenez un objet, car vous avez mis 2
Dans un objet.
Pour dynamic
, vous voudrez peut-être jeter un œil à tilisation de Type dynamic . Notez que Le type est un type statique, mais un objet de type dynamique contourne la vérification de type statique , c'est-à-dire le type que vous avez spécifié:
dynamic b = (object) 2;
est contourné et son type réel est résolu lors de l'exécution.
Pour la façon dont il est résolu lors de l'exécution , je pense que c'est beaucoup plus compliqué que vous ne pouvez l'imaginer ..
Disons que vous avez le code suivant:
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);
}
}
et j'ai mis l'IL complet (de configuration de débogage) à l'arrière de ma réponse.
Pour ces deux lignes:
var a=(object)2;
Take(a);
les IL ne sont que:
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)
Mais pour ces deux:
dynamic b=(object)2;
Take(b);
sont de IL_000f
à IL_007a
de TestMethod
. Il n'appelle pas directement la Take(object)
ou Take(int)
, mais invoque la méthode comme ceci:
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);
L'IL complet de 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
dynamic
est un Dynamically typed
var
est un Statically typed
Par cela, vous voyez que la résolution de surcharge se produit au moment de l'exécution pour dynamic
.
Donc la variable b
tient comme int
dynamic b = (object) 2;
Take(b);
C'est la raison pour laquelle Take(b);
appelle Take(int i)
static void Take(int i)
{
Console.WriteLine("Received an integer");
}
Mais dans le cas de var a = (object)2
, la variable a
tient comme 'objet'
var a = (object)2;
Take(a);
C'est la raison pour laquelle Take (a); appelle Take(object o)
static void Take(object o)
{
Console.WriteLine("Received an object");
}
La résolution d'argument entier encadré se produit au moment de la compilation. Voici l'IL:
IL_000d: box [mscorlib]System.Int32
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: call void ConsoleApp.Program::Take(object)
Vous pouvez voir qu'il est résolu en surcharge object
au moment de la compilation.
Lorsque vous utilisez dynamic
- le classeur d'exécution apparaît dans l'image. dynamic
non seulement peut être résolu en objets C # gérés, mais également en objets non gérés comme les objets COM ou les objets JavaScript étant donné qu'il existe un liant d'exécution pour ces objets.
Au lieu d'afficher IL, je vais montrer du code décompilé (plus facile à lire):
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);
Vous voyez que la méthode Take
est résolue à l'exécution par le classeur d'exécution et non par le compilateur. Ainsi, il le résoudra au type réel.
Si vous jetez un oeil sur la spécification C #:
1.6.6.5 surcharge de méthode
La surcharge de méthodes permet à plusieurs méthodes dans la même classe d'avoir le même nom tant qu'elles ont des signatures uniques. Lors de la compilation d'un appel d'une méthode surchargée, le compilateur utilise la résolution de surcharge pour déterminer la méthode spécifique à appeler.
Et:
7.5.4 Vérification à la compilation de la résolution de surcharge dynamique
Pour la plupart des opérations liées dynamiquement, l'ensemble des candidats possibles à la résolution est inconnu au moment de la compilation. Dans certains cas, cependant, l'ensemble candidat est connu au moment de la compilation:
Appels de méthode statique avec arguments dynamiques
Appels de méthode d'instance où le récepteur n'est pas une expression dynamique
Indexeur appelle lorsque le récepteur n'est pas une expression dynamique
Appels du constructeur avec des arguments dynamiques
Dans ces cas, une vérification limitée au moment de la compilation est effectuée pour chaque candidat pour voir si l'un d'eux pourrait éventuellement s'appliquer au moment de l'exécution
Donc, dans votre premier cas, var
n'est pas dynamique, la résolution de surcharge trouvera la méthode de surcharge à à la compilation .
Mais dans votre deuxième cas, vous appelez une méthode statique avec des arguments dynamiques , une résolution de surcharge trouvera la méthode de surcharge au moment de l'exécution à la place
Pour aider à comprendre la résolution de type dans votre cas pour les variables dynamiques.
Mettez d'abord un point d'arrêt sur la ligne:
Take(b); //See here the type of b when u hover mouse over it, will be int
ce qui signifie clairement que le code:
dynamic b = (object)2
ne convertit pas 2 en objet lorsqu'il est affecté à une variable dynamique et b reste un int
Ensuite, commentez votre surcharge de la méthode Take(int i)
puis mettez un point d'arrêt sur la ligne Take(b)
(idem: le type de b
est toujours int
) mais lorsque vous l'exécutez, vous verrez que la valeur imprimée est: Objet reçu.
Maintenant, changez votre appel de variable dynamic
en code ci-dessous:
Take((object)b); //It now prints "Received an object"
Ensuite, changez votre appel en code ci-dessous et voyez ce qui est retourné:
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 an
objet.
Ceci est dû au fait que: le meilleur type de correspondance est résolu pour les variables dynamiques en fonction de la valeur qu'il contient au moment de l'exécution et la meilleure méthode de surcharge de correspondance à appeler est résolue au moment de l'exécution pour les variables dynamiques.
Dans le premier cas, var signifie object
donc Take(object o)
est appelé. Dans le second cas, vous utilisez le type dynamic
et malgré le fait que vous ayez encadré votre int
, il contient toujours des informations sur son type -int
, de sorte que la meilleure méthode de correspondance est appelée.