C#4.0初探:dynamic 关键字
C#新增了dynamic关键字,正因为这一个小小的关键字,C#动态特性向前迈进了一大步。
dynamic是一个类型关键字,声明为dynamic的类型与"静态类型"(这里的静态类型是指编译时确定的类型,下同)相比最大的特点它是"动态类型",它会运行时尝试调用方法,这些方法的存在与否不是在编译时检查的,而是在运行时查找,如果方法存在并且参数正确,会正常调用,否则会抛出Microsoft.CSharp.RuntimeBinder.RuntimeBinderException异常。
看一个最简单的示例:
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = Console.Out;
dynamic a;
a = new Int32();
int b = a;
a++;
a--;
d.WriteLine("http://www.xianfen.net/");
d.WriteLine(d.GetType());
d.writeln("test"); //抛出Microsoft.CSharp.RuntimeBinder.RuntimeBinderException异常
}
}
}
对dynamic类型的操作只能有以下几种:
·赋值
·方法调用
·自增
·自减
·接受"静态类型"的构造器创建的对象
与关键字var的比较
从表面上看,dynamic与var关键字的用法很像,但实质上有本质区别。
var关键字被称为:隐含类型局部变量(Local Variable Type Inference),var只能用作局部变量,不能用于字段、参数等;声明的同时必须初始化;初始化时类型就已经明确了,并且不能再被赋值不能进行隐式类型转换的类型的数据;编译时编译器会对var定义的变量进行类型推断,这时变量的类型已经被确定。
dynamic可用于类型的字段,方法参数,方法返回值,可用于泛型的类型参数等;可以赋值给或被赋值任何类型并且不需显式的强制类型转换,因为这些是运行时执行的,这要得益于dynamic类型的动态特性。
与反射的比较
首先能看到的是,dynamic与反射相比,执行相同操作所需的代码少的多。
如调用类Me中的GetName()方法。
{
public string Blog { get; set; }
public string GetName()
{
return "Zhenxing Zhou";
}
}
用反射调用GetName()方法:
object instance = a.CreateInstance("Xianfen.Net.TestDynamic.Me");
Type type = instance.GetType();
MethodInfo mi = type.GetMethod("GetName");
object result = mi.Invoke(instance, null);
同样的dynamic调用:
string result = myInfo.GetName();
dynamic类型与反射相比能进行的操作要少的多。
目前dynamic类型对属性调用是不可用的,但我们知道,属性生成IL时,对属性的读或写会生成对应的在属性名前加上get_或set_前缀生成相应的方法,尝试调用两个方法来访问属性:
myInfo.set_Blog("http://www.xianfen.net/");
string result = myInfo.get_Blog();
会抛出异常,提示找不到get/set_Blog方法。这点比较遗憾,同样,对有参属性的访问也是不行的。
反射还可以访问私有方法字段以及其它类型成员及取得类型及类型成员的信息等。
dynamic类型的效率
效率问题应该是大家很关心的,我的感觉:不要对动态语言有很高的效率抱有太大的希望,但另一方面,算法的设计对效率的影响非常大,功能与性能经常存在一个平衡点。
要分析其效率,就要看看编译后内部都干了些啥,方法是写些简单的代码,查看IL。
代码:
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = "str";
d.ToString();
}
}
}
对应的IL代码:
extends [mscorlib]System.Object
{
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: call instance void [mscorlib]System.Object::.ctor()
L_0006: ret
}
.method private hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 9
.locals init (
[0] object d,
[1] class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo[] CS$0$0000)
L_0000: ldstr "str"
L_0005: stloc.0
L_0006: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/o__SiteContainer0::<>p__Site1
L_000b: brtrue.s L_003f
L_000d: ldc.i4.0
L_000e: ldstr "ToString"
L_0013: ldtoken Xianfen.Net.TestDynamic.Program
L_0018: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
L_001d: ldnull
L_001e: ldc.i4.1
L_001f: newarr [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo
L_0024: stloc.1
L_0025: ldloc.1
L_0026: ldc.i4.0
L_0027: ldc.i4.0
L_0028: ldnull
L_0029: newobj instance void [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags, string)
L_002e: stelem.ref
L_002f: ldloc.1
L_0030: newobj instance void [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder::.ctor(valuetype [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpCallFlags, string, class [mscorlib]System.Type, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [mscorlib]System.Type>, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [Microsoft.CSharp]Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo>)
L_0035: call class [System.Core]System.Runtime.CompilerServices.CallSite`10> [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>>::Create(class [System.Core]System.Runtime.CompilerServices.CallSiteBinder)
L_003a: stsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/o__SiteContainer0::<>p__Site1
L_003f: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/o__SiteContainer0::<>p__Site1
L_0044: ldfld !0 [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>>::Target
L_0049: ldsfld class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>> Xianfen.Net.TestDynamic.Program/o__SiteContainer0::<>p__Site1
L_004e: ldloc.0
L_004f: callvirt instance void [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>::Invoke(!0, !1)
L_0054: ret
}
.class abstract auto ansi sealed nested private beforefieldinit o__SiteContainer0
extends [mscorlib]System.Object
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()
.field public static class [System.Core]System.Runtime.CompilerServices.CallSite`1<class [mscorlib]System.Action`2<class [System.Core]System.Runtime.CompilerServices.CallSite, object>> <>p__Site1
}
}
可以看出生成的IL代码确实不美观,不过大体能看出端倪。为了方便查看,用Reflector查看,把反编译结果设置为.net2.0,代码清晰多了:
02. {
03. // Methods
04. private static void Main()
05. {
06. object d = "str";
07. if (<Main>o__SiteContainer0.<>p__Site1 == null)
08. {
09. <Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.
10. Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program),
11. null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
12. }
13. <Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d);
14. }
15. // Nested Types
16. [CompilerGenerated]
17. private static class <Main>o__SiteContainer0
18. {
19. // Fields
20. public static CallSite<Action<CallSite, object>> <>p__Site1;
21. }
22. }
06行先把赋值给dynamic的值赋给object类型,检查编译器生成的静态类o__SiteContainer0的静态字段<>p__Site1是否为null,如果是,则对其赋值,赋值的内容在这里不详细研究。然后调用<>p__Site1进行操作。
这里会发现两个问题:赋值给dynamic的值赋给object类型,对于值类型会不会执行同样的操作,会执行装箱操作吗;编译器生成的静态类o__SiteContainer0的静态字段<>p__Site1应该是缓存作用。这两个问题稍后验证。
1)对值类型进行的操作
如下代码:
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
d.ToString();
}
}
}
反编译代码:
{
// Methods
private static void Main()
{
object d = 5;
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.
Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program),
null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d);
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
}
}
可见确实对值类型进行了装箱操作,效率可想而知。
2)编译器生成的缓存类
代码如下:
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
d.ToString();
d.ToString();
}
}
}
反编译的代码:
{
// Methods
private static void Main()
{
object d = 5;
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d);
if (<Main>o__SiteContainer0.<>p__Site2 == null)
{
<Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site2.Target(<Main>o__SiteContainer0.<>p__Site2, d);
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
public static CallSite<Action<CallSite, object>> <>p__Site2;
}
}
代码调用了ToString方法,但编译器生成了两份缓存。
如果是在循环中:
代码:
namespace Xianfen.Net.TestDynamic
{
class Program
{
static void Main()
{
dynamic d = 5;
for (int i = 0; i < 100; i++)
{
d.ToString();
}
}
}
}
反编译代码:
{
// Methods
private static void Main()
{
object d = 5;
for (int i = 0; i < 100; i++)
{
if (<Main>o__SiteContainer0.<>p__Site1 == null)
{
<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, object>>.Create(new CSharpInvokeMemberBinder(CSharpCallFlags.None, "ToString", typeof(Program), null, new CSharpArgumentInfo[] { new CSharpArgumentInfo(CSharpArgumentInfoFlags.None, null) }));
}
<Main>o__SiteContainer0.<>p__Site1.Target(<Main>o__SiteContainer0.<>p__Site1, d);
}
}
// Nested Types
[CompilerGenerated]
private static class <Main>o__SiteContainer0
{
// Fields
public static CallSite<Action<CallSite, object>> <>p__Site1;
}
}
可见在循环中,相同的操作做了一次缓存;但非循环环境下,调用一次会缓存一次,猜测原因是,重复调用一个方法的次数不会太多,并且很多情况准确查找起来比较困难。
(以上代码在VS2010Beta1下测试通过)
URL: http://www.xianfen.net/
Author: Zhenxing Zhou