随笔 - 26  文章 - 0  评论 - 20  0

VS2010之所以那么强大,究其原因是其背后有着强大的C#4.0作为后台支撑。和以往的所有版本相比,C#4.0的动态性大大增强——dynamic就是一个非常明显的例子:

 

(一)dynamic初探: 

            以前因为某些特殊原因,需要动态的调用外部类(假设这个类是实现了某个带有参数的接口函数的),通常我们只能用反射了。示例代码如下:

Assembly asm = Assembly.LoadFile(“xxxxx”)

       asm.CreateInstance("MyAssembly.ClassName").GetType().InvokeMember("Say", BindingFlags.InvokeMethod, null, asm.CreateInstance("MyAssembly.ClassName "), new string[] { "aaa" });

 

这里顺便简略说一下反射流程:首先通过绝对路径加载某个NET的dll文件,然后创建该assembly中某个class的instance(该class必须有无参构造函数),获取其类型之后动态调用其函数Say,“BindingFlags.InvokeMethod”表明是一个普通类方法,“null”的地方是传递一个参数名的,和指明最后的string[]中的一串values内容一一匹配的……可见使用反射调用函数是很痛苦的一件事情。

现在呢?您根本不需要那么麻烦了!因为C#的dynamic会为您做好一切的,下面就是见证奇迹的时刻——

Assembly asm = Assembly.LoadFile("xxxxx");

dynamic dfun = asm.CreateInstance("MyAssembly.ClassName");

dfun.Say("Hello!");

 

            注意到咖啡色的代码了么——什么?dynamic竟然可以智能感知出动态加载的那个类的方法Say?其实不然:当你按下这个点的时候,IDE是没有智能感知的,但是如果你知道这个类是有这个方法(因为接口给了其一个契约,必须实现接口中的方法;而接口的方法是公开的),你就可以完全不理会智能感知,照样写,照样编译通过运行。神奇吧!

            看到这里,你就不会认为dynamic和var是“差不多”的概念了(var无非是根据赋值的类型编译器自己判断;且var不能作为函数返回值类型,但是dynamic可以)。

            或许有人会疑问:dynamic可以完全替代类似像简单工厂、抽象工厂一类的东西了咯?我的理解是——不对!从上面的定义中可以得知:dynamic必须首先获取对象实例,然后动态反射是它做的事情;如果完全取代反射,实例也获取不到,如何反射呢?真是“巧妇难为无米之炊”啊!

            说道dynamic可以作为返回值,下面给出一个例子:

class DynamicClass

    {

        public int Num1 { get; set; }

        public int Num2 { get; set; }

 

        public DynamicClass(int n1, int n2)

        {

            Num1 = n1;

            Num2 = n2;

        }

 

        public dynamic DynamicAction

        { get; set; }

    }

 

            主函数注意咖啡色部分:

            static void Main(string[] args)

        {

            DynamicClass t = new DynamicClass(1, 2);

            t.DynamicAction = new Func<int, int, double>((x, y) => x + y);

            Console.WriteLine(t.DynamicAction.Invoke(t.Num1,t.Num2));

        }

 

            道理很简单:因为dynamic类型可以赋值任何东西(包括匿名委托),所以我创建了一个匿名委托给它。然后调用计算结果(匿名委托的调用使用Invoke,可以省略)。

            但是……dynamic不仅仅可以动态反射类方法和属性,还可以“空中楼阁”般动态地去创建一个类方法和属性,并且赋值,相信吗?这是第二话。

 

(二)神奇的ExpandoObject类和自定义动态类扩展: 

dynamic在第一话中已经展示它动态根据赋值类型直接自动完成反射的强大功能。现在又是一个新奇迹的诞生——

static void Main(string[] args)

        {

            dynamic d = new ExpandoObject();

            d.Name = "ServiceBoy";

            d.Action = Func<string>(()=>d.Name;);

            Console.WriteLine(d.Action());

        }

            初看这个代码只是简单的读写Name属性,毫无稀奇可言。但是你注意哦——你到MSDN——或者你索性new ExpandoObject().Name 试试看,有Name和Action这个属性吗?——没有啊,真的没有!嘿,奇了怪了,既然没有,为什么你可以凭空“捏造出一个属性”,而且可以给属性赋值,并且读取属性内容呢?

            俗话说的好——天下没有白给的食——微软这个类意在向我们揭露一个惊天的大秘密,那就是你可以自定义dynamic类,让这个类跟随你的要求动态的改变自己(比如增加一个新属性等)。我们可以参照MSDN,给出一个自定义的ExpandoObject:

public class SimpleDynamic : DynamicObject

    {

        Dictionary<string, object> Properties = new Dictionary<string, object>();

        Dictionary<string, object[]> Methods = new Dictionary<string, object[]>();

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object  result)

        {

            if (!Methods.Keys.Contains(binder.Name))

            {

                Methods.Add(binder.Name, null);

            }

 

            if (args != null)

            {

                Methods[binder.Name] = args;

            }

 

            StringBuilder sbu = new StringBuilder();

 

            foreach (var item in args)

            {

                sbu.Append(item);

            }

 

            result = sbu.ToString();

            return true;

 

        }

 

        public override bool TrySetMember(SetMemberBinder binder, object value)

        {

            if (!Properties.Keys.Contains(binder.Name))

            {

                Properties.Add(binder.Name, value.ToString());

            }

            return true;

        }

 

        public override bool TryGetMember(GetMemberBinder binder, out object result)

        {

            return Properties.TryGetValue(binder.Name, out result);

        }

}

        首先说明这个例子的作用:随意增加不重复的属性并赋值(取值),并且让你随意创建或者调用(带参或无参)函数进行输入(输出)。

        分析一下这个类的主要特点:

        一般地,任何一个类——如果需要动态为自身添加属性、方法等的,就必须实现IDynamicObjectProvidor接口或者是DynamicObject虚类(之所以用虚类的原因是“各取所需”的缘故,DynamicObject类都通过虚方法virtual去“实现”了接口中所有的方法,只要继承了这个类,读者可以根据需要“任意”动态覆盖你要的方法)。这里介绍三个最常见的方法:

  • 如果需要支持动态创建写属性,必须覆盖TrySetMember,其方法介绍如下:

参数名称

作用说明

binder:SetMemberBinder类型

用于获取动态创建赋值属性的时候“属性名”等一些常见信息(如示例中Name获取动态赋值的那个属性)。

value:object类型

用于获取设置动态属性的那个值。

  • 如果需要支持动态创建读属性,必须覆盖TryGetMember,其参数作用和TrySetMember大致相当,只是反作用(用于获取某个已有属性的内容,并且反向传递给object作为输出结果,注意TryGetMember的value是一个out类型)。同时,这个函数多出一个result类型,用于返回已有属性的存储的值(NULL抛出异常,被认为是错误的)。

 

  • 如果需要动态调用函数并输出结果,必须覆盖TryInvokeMember方法,此函数比较复杂:

 

参数名称

作用说明

binder:InvokeMemberBinder类型

用于获取动态创建函数时候一些与函数相关的属性:

(比如Name是函数名,其中还有一个CallInfo内嵌属性,您还可以获得ArgumentNames(C#4.0中最新的可选参数的名称,通过其ArgumentNameCount获取可选参数名的总个数))。

 

Args:object[]类型

获取动态给函数赋的值。

result:object类型

返回动态函数执行的结果,Null表示异常。

 

根据以上表格,对照不难读懂我的示例代码——现在假设你是这样调用的:

1)

dynamic d = new SimpleDynamic();

d.Name = “Serviceboy”;

Console.WriteLine(d.Name);

首先创建了一个d的动态类型,然后当赋值给Name的时候,因为Name是属性,所以触发了“TrySetMember”函数,该函数自动检查是否已经存在这个属性名,如果不存在,则将其添加进入一个Dictionary中并将对应赋予的值传递进去保存起来。当使用输出的时候,同样地,TryGetMember被触发,系统检测是否预先创建过这个值,如果没有,则抛出异常;存在的话,取出对应的存储value并返回给系统。

2)

dynamic d = new SimpleDynamic();

Console.WriteLine(d.Say(“Hello!”));

            首先创建了一个d的动态类型,当动态创建一个方法的时候,系统检测是否包含这个方法名,不包含将添加这个方法名到Dictionary保存,接着检查参数是否为空,不为空把参数赋值给那个函数名作为Key的Dictionary中保存,最后使用StringBuilder串起来赋值给result作为输出。

下面给出一个比较复杂的例子——自定义的XML创建器(仿Jeffery Zhao):

public class XmlCreator : DynamicObject

    {

        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

        {

 

            //如果除了第一个节点是字符串,后面都是XElement对象,表明此节点是父节点

            if (args[1] is XElement)

            {

                XElement root = new XElement(args[0].ToString());

                //把子节点添加到父节点

                for (int i = 1; i < args.Length; ++i)

                {

                    root.Add(args[i]);

                }

                result = root;

               

            }

                //否则是子节点

            else

            {

                //拷贝所有属性到数组:

                string[] attributes = new string[binder.CallInfo.ArgumentNames.Count];

                for (int i = 0; i < binder.CallInfo.ArgumentNames.Count; i++)

                {

                    attributes[i] = binder.CallInfo.ArgumentNames[i];

                }

 

                //拷贝所有属性值到数组:

                string[] values = new string[args.Length - 1];

                for (int i = 1; i < args.Length; ++i)

                {

                    values[i - 1] = args[i].ToString();

                }

 

                XElement subelement = new XElement(args[0].ToString());

 

                //属性名称获取

                for (int i = 0; i < attributes.Length; ++i)

                {

                    subelement.SetAttributeValue(attributes[i], values[i]);

                }

                result = subelement;

            }

            return result != null;

        }

}

该函数功能是:输出任意同时带有属性的节点,同时可以嵌套——比如:

dynamic xmlCreator = new  XmlCreator();

XElement ele = xmlCreator.CreateElement(“Books”,

xmlCreator(“Book”,name:C#,price:100.50)

                        );

 

            大家可以自己想一想是怎么一个原理哦。

posted on 2010-04-16 16:11 maledong 阅读(...) 评论(...) 编辑 收藏