回顾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!");
dynamic也可以用在变量的传递中,runtime会自动选择一个最匹配的overload方法。
这里有一个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交互做了下面几方面的改进:
1. Automatic object -> dynamic mapping
2. Optional and named parameters
3. Indexed properties
4. Optional “ref” modifier
5. 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
实在是不知道怎么翻译这两个词。
在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好像。