c# 4.0新特性一览
终于静下心来仔细听了一遍Anders Hejlsberg(Visual Studio组的TECHNICAL FELLOW,C#的设计者之一)在PDC08上讲的“The Future of C#”(http://channel9.msdn.com/pdc2008/TL16/)。
回顾C#发展的历史,C#1.0完全是模仿Java,并保留了C/C++的一些特性如struct,新学者很容易上手;C#2.0加入了泛型,也与Java1.5的泛型如出一辙;C#3.0加入了一堆语法糖,并在没有修改CLR的情况下引入了Linq,简直是神来之笔,虽然很多项目出于各种各样如性能之类的原因没有采用,但非常适合小型程序的快速开发,减轻了程序员的工作量,也提高了代码的可读性;C#4.0增加了动态语言的特性,从里面可以看到很多javascript、python这些动态语言的影子。虽然越来越偏离静态语言的道路,但从另一个角度来说,这些特性也都是为了提高程序员的生产力。至于被接受与否,还是让时间来说话吧。
PS:这里面还有一点版本号的小插曲——VS2008所对应的.Net Framework是3.5,C#是3.0,CLR是2.0,及其混乱,MS终于下决心在VS2010中把这三个版本号都统一成了4.0,于是CLR3不知所终……
Dynamically Typed Object
C#4.0加入了dynamic关键字,可以申明一个变量的static类型为dynamic(有点绕口)。
在3.0及之前,如果你不知道一个变量的类型,而要去调用它的一个方法,一般会用到反射:
object calc = GetCalculator(); Type calcType = calc.GetType(); object res = calcType.InvokeMember("Add", BindingFlags.InvokeMethod, null, new object[] { 10, 20 }); int sum = Convert.ToInt32(res);
有了dynamic,就可以把上面代码简化为:
dynamic calc = GetCalculator(); int sum = calc.Add(10, 20);
使用dynamic的好处在于,可以不去关心对象是来源于COM, IronPython, HTML DOM或者反射,只要知道有什么方法可以调用就可以了,剩下的工作可以留给runtime。下面是调用IronPython类的例子:
ScriptRuntime py = Python.CreateRuntime(); dynamic helloworld = py.UseFile("helloworld.py"); Console.WriteLine("helloworld.py loaded!");
这里有一个demo:把一段javascript代码拷到C#文件中,将var改成dynamic,function改成void,再改一下构造函数的调用方式(new type()改为win.New.type()),去掉javascript中的win.前缀(因为这已经是C#的方法了),就可以直接运行了。
dynamic的实现是基于IDynamicObject接口和DynamicObject抽象类。而动态方法、属性的调用都被转为了GetMember、Invoke等方法的调用。
public abstract class DynamicObject : IDynamicObject { public virtual object GetMember(GetMemberBinder info); public virtual object SetMember(SetMemberBinder info, object value); public virtual object DeleteMember(DeleteMemberBinder info); public virtual object UnaryOperation(UnaryOperationBinder info); public virtual object BinaryOperation(BinaryOperationBinder info, object arg); public virtual object Convert(ConvertBinder info); public virtual object Invoke(InvokeBinder info, object[] args); public virtual object InvokeMember(InvokeMemberBinder info, object[] args); public virtual object CreateInstance(CreateInstanceBinder info, object[] args); public virtual object GetIndex(GetIndexBinder info, object[] indices); public virtual object SetIndex(SetIndexBinder info, object[] indices, object value); public virtual object DeleteIndex(DeleteIndexBinder info, object[] indices); public MetaObject IDynamicObject.GetMetaObject(); }
Named and optional parameters
这似乎不是什么很难实现或很新颖的特性,只要编译器的支持就可以(VB很早就支持了)。估计加入的原因是群众的呼声太高了。
带有可选参数方法的声明:
public StreamReader OpenTextFile( string path, Encoding encoding = null, bool detectEncoding = true, int bufferSize = 1024);
命名参数必须在最后使用:
OpenTextFile("foo.txt", Encoding.UTF8, bufferSize: 4096);
顺序不限:
OpenTextFile(bufferSize: 4096, path: "foo.txt", detectEncoding: false);
Improved COM Interoperability
在C#中在调用COM对象如office对象时,经常需要写一堆不必要的参数:
object fileName = "Test.docx"; object missing = System.Reflection.Missing.Value; doc.SaveAs(ref fileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
4.0中就可以直接写成:
doc.SaveAs("Test.docx");
C#4.0对COM交互做了下面几方面的改进:
- Automatic object -> dynamic mapping
- Optional and named parameters
- Indexed properties
- Optional “ref” modifier
- Interop type embedding (“No PIA”)
对第1点和第5点的简单解释如下:
在COM调用中,很多输入输出类型都是object,这样就必须知道返回对象的确切类型,强制转换后才可以调用相应的方法。在4.0中有了dynamic的支持,就可以在导入这些COM接口时将变量定义为dynamic而不是object,省掉了强制类型转换。
PIA(Primary Interop Assemblies)是根据COM API生成的.Net Assembly,一般体积比较大。在4.0中运行时不需要PIA的存在,编译器会判断你的程序具体使用了哪一部分COM API,只把这部分用PIA包装,直接加入到你自己程序的Assembly里面。
Co- and Contra-Variance
实在是不知道怎么翻译这两个词。
(感谢Ariex,徐少侠,AlexChen的提示,应翻译为协变和逆变,http://msdn.microsoft.com/zh-cn/library/ms173174(VS.80).aspx)
在C#中,下面的类型转换是非法的:
IList<string> strings = new List<string>(); IList<object> objects = strings;
因为你有可能会这样做,而编译器的静态检查无法查出错误:
objects[0] = 5;
string s = strings[0];
4.0中在声明generic的Interface及Delegate时可以加in及out关键字,如:
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); } public interface IEnumerator<out T> : IEnumerator { bool MoveNext(); T Current { get; } }
public interface IComparer<in T> { public int Compare(T left, T right); }
out关键字的意思是说IEnumerable<T>中T只会被用在输出中,值不会被改变。这样将IEnumerable<string>转为IEnumerable<object>类型就是安全的。
in的意思正好相反,是说IComparer<T>中的T只会被用在输入中,这样就可以将IComparer<object>安全的转为IComparer<string>类型。
前者被称为Co-Variance, 后者就是Contra-Variance。
.Net4.0中使用out/in声明的Interface:
System.Collections.Generic.IEnumerable<out T> System.Collections.Generic.IEnumerator<out T> System.Linq.IQueryable<out T> System.Collections.Generic.IComparer<in T> System.Collections.Generic.IEqualityComparer<in T> System.IComparable<in T>
Delegate:
System.Func<in T, …, out R> System.Action<in T, …> System.Predicate<in T> System.Comparison<in T> System.EventHandler<in T>
Compiler as a Service
4.0中增加了与编译器相关的API,这样就可以将字符串作为代码动态编译执行,跟javascript好像。
Video的最后,Anders做了一个很酷的demo,大概只用了二三十行代码,就实现了在控制台中直接执行C#语句,定义并调用函数,动态创建windows form,添加button等功能,看起来完全不逊色于Python,Ruby之类语言的控制台。
沉寂了n年之后,CLR终于要出新版本了,这回Jeffrey Richter大侠没有借口不出新版的CLR via C#了吧:)
Reference:
- 视频: http://channel9.msdn.com/pdc2008/TL16/
- PPT:http://mschnlnine.vo.llnwd.net/d1/pdc08/PPTX/TL16.pptx
- 示例代码及文档(New features in C# 4.0):http://code.msdn.microsoft.com/csharpfuture
引用地址:http://www.cnblogs.com/palo/archive/2009/03/01/1400949.html
这是个小例子用来演示使用object关键字的好处和问题。
object obj = 10;
Console.WriteLine(obj.GetType());
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。
// 一个编译错误, 因为
// 在编译时obj的类型是System.Object。
// obj = obj + 10;
// 你需要显式转换obj到期望的类型。
obj = (int)obj + 10;
// 然而, 这样并不表示你真的安全。
// 你可能转换到一个错误的类型
// 而编译器并不会发现。
// 这里你会在运行时得到一个异常,
// 因为obj是int类型,不是string类型。
// obj = (string)obj + 10;
// 如果你将其转换到一个错误的数值类型,
// 你也会得到一个运行时异常
// 虽然有隐式转换的语法在这里。
// obj = (double)obj + 10;
你可以看到,尽管obj存储一个int值,如果没有转换,编译器仍然不会让你执行任何数学操作。它看起来好像是帮助你确定是否你真有一个int类型,但它不是。你可以转换到一个完全不同的类型但编译器却检查不出。结果便是,你得到一个运行时异常。
所以你不得不执行显示转换,这并不保证什么,仅仅因为如果不转换,编译器不会让你运行。
新的dynamic关键字来了。它告诉编译器不要强制附加规则在你的代码上。
dynamic dyn = 10;
Console.WriteLine(dyn.GetType());
// 跟"object"一样.
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。
// 没有编译错误,因为
// 编译器不会在编译时
// 试图识别dynamic对象的类型。
dyn = dyn + 10;
// 同样,这个操作会在所有数值或
// 其他支持“+”操作符的类型上成功运算。
dyn = 10.0;
dyn = dyn + 10;
dyn = "10";
这是一个object和dynamic间的主要区别----用dynamic相当于你告诉编译器此对象的类型只在运行时确定,编译器不会试图干预。最终,你可以写更少的代码。但我要强调一下,相对于使用原有的object关键字,这样做不会增加任何危险。同样,也不会减少任何危险,所以当操作任何对象需要类型检查技术时(如反射),则使用dynamic对象会更好。dyn = dyn + 10;
接下来通常会出现如下问题:“既然dynamic对象可以是任意对象并且编译器不会检查它是什么,这是否意味着你可以传递一个dynamic对象给我的信任方法/系统并使它崩溃?”
让我们假设拥有一个简单方法。
public static void Print(string arg)
{
Console.WriteLine(arg);
}
现在我们看看你能怎么传递dynamic对象给它。
dynamic dyn = 10;
// 你将于运行时在这里得到一个异常.
你可以看见,虽然编译器允许你传递一个dynamic对象给你的方法,但你的方法永远不会得到这个对象如果它是个错误的类型。在方法实际执行之前已经有一个异常抛出。只有当它包含一个恰当值时,你才能传递dynamic对象给你的方法,在这里是string类型。Print(dyn);
dynamic dyn = "10";
再次强调,这跟你使用object关键字相比不会有太多行为上的区别。Print(dyn);
// 不会编译通过.
//Print(obj);
// 编译, 但这里在运行时会有个异常。
//Print((string)obj);
// 这里的代码会正常工作,因为现在obj是个string型,
// 但你不需要转换。
obj = "10";
有人说(int)obj这种写法并不难理解,为什么要再来个dynamic呢?好吧,有些情况下你不得不执行较多的转换操作,这使得你的代码很难理解。还有些情况下简单的转换并不能达到目的,你需要调用反射方法,比如InvokeMember或GetProperties。一个很好的例子便是COM互操作,这就是为什么它要修改以使用新dynamic特性(更多信息点这里“how-to”)。Print((string)obj);
同时,用dynamic关键字和dynamic语言运行时让很多以前不可能实现或难以实现的方案变得可行,包括与动态语言互操作。我在之前的博客里强调过这两个情形: Introducing ExpandoObject 和Creating Wrappers with DynamicObject.
你可能想去MSDN walkthrough看看,它展示你该怎么从C#和Visual Basic调用IronPython库,一个真的很酷的介绍,Using Dynamic Languages to Build Scriptable Applications,作者Dino Viehland。另一个好的介绍展示一些例子并解释设计原则背后是这个特性Dynamic Binding in C# 4,作者Mads Torgersen。
结论是我们不需要担心会有人能用dynamic特性破坏你的代码。它没有更多(同时,也没有更少)的危险相对于object关键字。
所以,如果你经常使用object关键字并且不得不执行一堆转换"并且/或者"使用反射来调用对象的方法或属性,你可能应该留意一下dynamic关键字。在很多时候它比object更方便并且编写更少的代码。
原文没有提到var关键字,相信很多人会在这时想到var。
var在C#3.0中产生,其在MSDN中的定义:在方法范围中声明的变量可以具有隐式类型 var。隐式类型的本地变量是强类型变量(就好像您已经声明该类型一样),但由编译器确定类型。
使用var关键字申明变量,编译器会根据分配的实例类型确定此变量的类型,即类型推断。
var va = 10;
Console.WriteLine(va.GetType());
// 输出 System.Int32 因为
// 这是这个对象里存储的值的类型。
// 而va的编译时类型也已经被编译器确定为int型。
// 没有编译错误,因为
// va在第一句赋值时
// 便被编译器确定其类型是int。
va = va + 10;
// 编译错误,因为
// va变量已经确定是int型,而10.0是double型,
// 且double型向int型不可隐式转换。
跟object比,var属于强类型;// va = 10.0;
跟dynamic比,var并无任何运行时的动态特性,仅仅是编译器在编译时反向推断其类型,同直接用其编译时类型声明变量功能上是一样的。它仅仅是编译器提供的语法糖。dynamic可不是语法糖,dynamic是需要运行时支持的。
VAR 是3.5新出的一个定义变量的类型
其实也就是弱化类型的定义
VAR可代替任何类型
编译器会根据上下文来判断你到底是想用什么类型的
至于什么情况下用到VAR 我想就是你无法确定自己将用的是什么类型
就可以使用VAR 类似 OBJECT
但是效率比OBJECT高点
使用var定义变量时有以下四个特点:
1. 必须在定义时初始化。也就是必须是var s = “abcd”形式,而不能是如下形式:
var s;
s = “abcd”;
2. 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了。
3. var要求是局部变量。
4. 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
var表示“变量的类型是在编译时决定的”,但是dynamic表示“变量的类型是在运行时决定的”。因此,dynamic与var具有截然不同的含义。
var让你在初始化变量时少输入一些字,编译器会根据右值来推断出变量的类型。dynamic更厉害,它告诉编译器,根本就别理究竟是啥类型,运行时再推断不迟。
var只能用于局部变量的定义,你不能把类的属性定义成 var,也不能把方法的返回值类型或者是参数类型定义成var。dynamic就没有这些局限了。
dynamic类型并没有跳过类型校验,只是延迟到了运行时。如果在运行时,检测到类型不兼容,照样会抛出异常。
你可能在以下情况下使用dynamic:
1.COM对象
2.动态语言(如IronPython,IronRuby等)对象
3.反射对象
4.C# 4.0中动态创建的对象
浙公网安备 33010602011771号