艾伟:C#4.0初探:dynamic 关键字

简介: C#新增了dynamic关键字,正因为这一个小小的关键字,C#动态特性向前迈进了一大步。dynamic是一个类型关键字,声明为dynamic的类型与"静态类型"(这里的静态类型是指编译时确定的类型,下同)相比最大的特点它是"动态类型",它会运行时尝试调用方法,这些方法的存在与否不是在编译时检查的,而是在运行时查找,如果方法存在并且参数正确,会正常调用,否则会抛出Microsoft.CSharp.RuntimeBinder.RuntimeBinderException异常。

C#新增了dynamic关键字,正因为这一个小小的关键字,C#动态特性向前迈进了一大步。
dynamic是一个类型关键字,声明为dynamic的类型与"静态类型"(这里的静态类型是指编译时确定的类型,下同)相比最大的特点它是"动态类型",它会运行时尝试调用方法,这些方法的存在与否不是在编译时检查的,而是在运行时查找,如果方法存在并且参数正确,会正常调用,否则会抛出Microsoft.CSharp.RuntimeBinder.RuntimeBinderException异常。

看一个最简单的示例:

using System;

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()方法。

class Me
{
    
public string Blog { get ; set ; }

    
public string GetName()
    {
        
return " Zhenxing Zhou " ;
    }
}


用反射调用GetName()方法:

Assembly a = Assembly.GetExecutingAssembly();
object instance = a.CreateInstance( " Xianfen.Net.TestDynamic.Me " );
Type type
= instance.GetType();
MethodInfo mi
= type.GetMethod( " GetName " );
object result = mi.Invoke(instance, null );


同样的dynamic调用:

dynamic myInfo = new Me();
string result = myInfo.GetName();


dynamic类型与反射相比能进行的操作要少的多。
目前dynamic类型对属性调用是不可用的,但我们知道,属性生成IL时,对属性的读或写会生成对应的在属性名前加上get_或set_前缀生成相应的方法,尝试调用两个方法来访问属性:

dynamic myInfo = new Me();
myInfo.set_Blog(
" http://www.xianfen.net/ " );
string result = myInfo.get_Blog();


会抛出异常,提示找不到get/set_Blog方法。这点比较遗憾,同样,对有参属性的访问也是不行的。
反射还可以访问私有方法字段以及其它类型成员及取得类型及类型成员的信息等。

dynamic类型的效率
效率问题应该是大家很关心的,我的感觉:不要对动态语言有很高的效率抱有太大的希望,但另一方面,算法的设计对效率的影响非常大,功能与性能经常存在一个平衡点。
要分析其效率,就要看看编译后内部都干了些啥,方法是写些简单的代码,查看IL。
代码:

using System;

namespace Xianfen.Net.TestDynamic
{
    
class Program
    {
        
static void Main()
        {
            dynamic d
= " str " ;
            d.ToString();
        }
    }
}


对应的IL代码:

.class private auto ansi beforefieldinit Program
    
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` 1 0> [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,代码清晰多了:

01 . internal class Program
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)对值类型进行的操作
如下代码:

using System;

namespace Xianfen.Net.TestDynamic
{
    
class Program
    {
        
static void Main()
        {
            dynamic d
= 5 ;
            d.ToString();
        }
    }
}

反编译代码:
internal class Program
{
    
// 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)编译器生成的缓存类
代码如下:
using System;

namespace Xianfen.Net.TestDynamic
{
    
class Program
    {
        
static void Main()
        {
            dynamic d
= 5 ;
            d.ToString();
            d.ToString();
        }
    }
}

反编译的代码:
internal class Program
{
    
// 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方法,但编译器生成了两份缓存。

如果是在循环中:
代码:
using System;

namespace Xianfen.Net.TestDynamic
{
    
class Program
    {
        
static void Main()
        {
            dynamic d
= 5 ;

            
for ( int i = 0 ; i < 100 ; i ++ )
            {
                d.ToString();
            }
        }
    }
}

反编译代码:
internal class Program
{
    
// 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
目录
相关文章
|
1月前
|
C#
30.C# 关键字 this初步学习
30.C# 关键字 this初步学习
15 1
|
1月前
|
C#
28.c#关键字base初步学习
28.c#关键字base初步学习
11 0
|
1月前
|
C#
27.c#关键字sealed修饰类
27.c#关键字sealed修饰类
12 0
|
2月前
|
SQL 开发框架 .NET
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
EntityFramework数据持久化复习资料3、C#拓展方法与yield关键字使用
22 0
|
3月前
|
开发框架 .NET 编译器
C# 9.0中的静态匿名函数:引入static关键字的新用法
【1月更文挑战第15天】C# 9.0为匿名函数带来了一个新的修饰符static,允许开发者明确指定匿名函数不会捕获其包含作用域中的任何变量。这一特性增强了代码的性能和可读性,同时减少了因不小心捕获变量而导致的潜在错误。本文将详细探讨C# 9.0中静态匿名函数的语法、使用场景以及它们如何影响代码的性能和安全性。
|
1月前
|
存储 Java C++
31.C#:关键字static
31.C#:关键字static
15 1
|
1月前
|
C#
29.C#关键字throw初步学习
29.C#关键字throw初步学习
16 0
|
2月前
|
存储 编译器 C#
C#关键字常见面试题
C#关键字常见面试题
|
7月前
|
存储 编译器 C#
C#关键字相关面试题
C#关键字相关面试题
|
3月前
|
C# 数据安全/隐私保护 开发者
C# 9.0中的Init关键字:Init-only Setters的新篇章
【1月更文挑战第12天】本文介绍了C# 9.0中引入的Init关键字,该关键字允许创建仅在对象初始化时可设置属性的setter。通过Init-only setters,开发者能够更加灵活地控制对象属性的赋值时机,提高代码的可维护性和安全性。文章详细解释了Init关键字的使用方法、适用场景以及与传统setter的区别,并探讨了其在实际开发中的潜在影响。