C#4.0和VS2010新特性

VS2010被认为将是续写Visual Studio 6 的扛鼎之作。整个IDE不仅是使用了WPF重构,而且使用了最新的NET Framework 4作为强大的后援支撑。从上至下可圈可点。下面我们就来看一看VS2010在哪些方面引人注目——

 

1WPF重构界面:整个VS2010 IDE全部使用WPF重构,因此与Win7外观紧密集成,而且实现了先前所有NET版本所不能实现的一些功能——比如代码的无极缩放(打开一个项目应该可以看到左下角的显示比率,默认100%;这样您不必切换字体大小了,可以手动输入百分比,可以下拉选择,当然更可以直接Ctrl+鼠标滚轮快捷方式进行调整)。

 

2)快速搜索: 

I)如果想寻找某个类(方法等)在何处调用,直接选中这个方法(类名),IDE会自动在当前打开的文档中使用淡紫色圈出所有的这个类(方法)名称。

II)快捷键“Ctrl+逗号”呼出搜索类和方法框,直接输入类名(不区分大小写,可以使用Pascal输入形式)自动列出所有的类和方法、属性等。

 

3)架构体系查看: 

要想知道某个项目究竟有哪些文件,它们之间的调用关系等,在VS2010易如反掌——您所要做的只是打开架构浏览器(位于View菜单下的Architecture Explorer),然后就可以通过点击Solution View或者Class View查看当前项目(或者整个解决方案)的类、方法等。还可以通过文本框输入进行检索(点击一个漏斗图标,检索方式同“快速检索”)。您更可以使用Ctrl+A的方式选中全部的类(方法),点击“Architecture Explorer”左边第一个按钮,自动创建生成关联图。

当然,你想要知道某个方法在哪些地方被调用了,可以在该方法上右键,选择“Call Hierarchy”(显示层次关系)即可。

 

4)第三方程序的测试: 

您可以在完全不知道第三方的程序情况下对其测试,这个是一个重大的突破。首先您创建一个Test Project,右键加入Coded UI Test文件,打开后选中“Record actions(录制行为)”那个选项,然后打开一个第三方的程序(比如画图板等),你随随便便做一些操作(在此之前务必按下右下角的录制动作的按钮),然后等到完毕之后再次点击那个停止记录的按钮,然后点击右边那个“Generate Codes”(生成代码)就可以生成代码,您可以对这些代码进行调试了。

 

5)可选参数和命名话参数(C#): 

早些时候如果你想省略某些函数的参数,您不得不定义多次重载该函数以便获得这些函数的不同参数形式。在VB.NET中自带参数省略的功能,但是C#的程序员只能望尘莫及。现在不必了!C#也完全可以这么做,为您少些诸多重载函数打开方便之门。比如:DoTask (string taskName, bool Repeat=false) {……},但是可缺省参数必须在最后定义,例子中把Repeat移到taskName前是绝对不允许的,而且缺省参数的赋值必须是const类型(要不是写死的,要么是const变量,不能是其它的)

与此同时,VS2010中还支持乱序给参数赋值——什么意思?如果某个函数有多个参数,你只要(函数名:数值)这种方式,您就可以随心所欲给任何函数参数赋值了。

假如有一个接口

 

 

6)协变和反变(Co-variant & Crop-variant)

这是VS2010新增的一个内容,用于在编译的时候确认是否允许不同类型的泛型接口之间是否存在转换的问题。

为了了解“协变”和“反变”的概念,我们先看一个例子:

假设我们定义了一个接口和若干类:

class Father

    {

        public virtual void Say()

        {

            Console.WriteLine("Father");

        }

    }

 

    class Son : Father

    {

        public override void Say()

        {

            Console.WriteLine("Son");

        }

    }

   class Son2 : Father

    {

        public override void Say()

        {

            Console.WriteLine("Son2");

        }

    }

Interface  InAndOut<T, V>

             {

             void Input(V value);

            T Output();

}

class Program<T,V>:InAndOut<T,V>

    {

        private object value = default(V);

 

        public T Output()

        {

            return (T)value;

        }

        public void Input(V tv)

        {

            value = tv;

        }

    }

又假如我们已经实例化两个接口的实例:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

 

现在我们令:iobj1= iobj2,可行吗?

 

            乍一看似乎可行——为什么呢?因为联想到右边的两个子类Son会被自动隐式转化成其父类Father。就好像是Father f = new Son()一样可以(注意:我们把子类隐式转化成父类成为“协变”,反之成为“反变”)。而且,根据接口定义,输入方向是接受一个Son(实际存储在iobj2中,被隐式转成Father),输出的时候还是存储的Son被隐式转化成Father输出。

            这种思考逻辑固然没有错,但是它有一个前提条件——即从iobj1输入方向看,必须是Son到Father,输出的话也必须是Son到Father!但是泛型仅仅是一个定义,谁能够保证在类中Father或者Son一定是输入(或者是输出)参数呢?如果我们改变成以下的形式呢?

class Program<T,V>:InAndOut<T,V>

    {

        private object value = default(T);

 

        public V Output()

        {

            return (V) value;

        }

        public void Input(T tv)

        {

            value = tv;

        }

    }

            这样就出现了问题——首先,iobj1指向iobj2(接受一个Father的参数,此时如果我的Father输入的是Son2,那么实际转化到Son的时候就发生异常了;同样地,输出因为是Son2不能转化成Son,因此发生异常)。

这个问题就是因为输入输出方向不明确所导致的。如果我们强制是一开始就给出的输入(输出方向)。即V只能作为输入,T只能作为输出就可以了。

推而广之,假设存在两个泛型TV,假设VTVT的子类)。那么泛型接口之间转换的一般规则是:输出类型是父类,“输出”一般是协变;反之,输入类型是子类,一般是反变。约束这种输入输出泛型的规则就是:输出线的接口加关键词out,输入加in如下所示:

Interface  InAndOut<out T, in V>

             {

             void Input(V value);

            T Output();

}

            那么你输入以下代码,就可以输出结果:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

            这种规则本质上是编译器的行为理解。但是不一定就是正确结果,考察下列例子:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son2, Father> iobj2 = new Program < Son2, Father >();

这一段代码照样可以通过编译,但是运行仍旧报异常。为什么呢?因为“输入端”和“输出端”尽管都符合了隐式转换的条件,但是你注意:把一个Son对象存储到iobj2的时候,iboj2的输出要求是Son2,而不是Son! 因此要保证运行正确,必须做到这样:

InAndOut<Father, Son> iobj1 = new Program < Father, Son >();

InAndOut<Son, Father> iobj2 = new Program < Son, Father >();

 

输入端接受Son,能够隐式转成蓝色的Father,蓝色Father存储的子类对象同样必须可以转化成Son(即一个协变的东西必须能够支持其反变;反之,一个反变的泛型必须支持其协变泛型,这就是著名的“协变反变类型”,简称“协-反变类型”)。

 

证明:(红色的Son开始):隐式转化成Father(协变),然后蓝色的Father(其中存储Son)强制转化成绿色的Son(反变)。

同理,(黑色的Father开始):黑色Father返回的内容(存储Son)可以强制转化成蓝色Father的内容(反变),同时可以隐式转化成绿色Son(协变)。我觉得可以使用对角线规则验证(猜想,对于任意的泛型A,B,C,D):

            InAndOut<A,B>

                                                (B既可以转成D,也可以转成C;输出A包含的内容

                                                   可以转化成D,也可以转化成C)

            InAndOut<C, D>

 

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)

                        );

 

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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值