C# 4.0中COM互操作性和方差得到增强
增强的COM互操作性特性
C# 4.0在COM互操作性支持方面提供了很多改进,使得你现在在.NET代码中可以更容易地调用COM对象了,需要执行的类型转换更少了,ref关键字不再需要,也不再需要主互操作程序集或PIA,从本质上讲,C# 4.0提供了大量的新特性,特别是在COM互操作方面更是下足了功夫,在前一篇文章中谈到的动态查询和命名/可选参数的支持,帮助提高了与COM API如Office自动化API互操作的体验。
例如,下面是C# 4.0以前的版本打开一个Word文件的代码:
using Word = Microsoft.Office.Interop.Word; namespace COMInterop { class Program { static void Main(string[] args) { Word.Application wordApplication = new Word.Application() { Visible = true }; object missingValue = System.Reflection.Missing.Value; object readOnlyValue = true; object fileName = @"C:\\DevX.docx"; wordApplication.Documents.Open(ref fileName, ref missingValue, ref readOnlyValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue, ref missingValue,ref missingValue); } } }
在最后的open调用中,你可以看到需要传递大量的可选参数以满足函数调用,随着C# 4.0中可选和命名参数的引入,现在做同样的事情,需要的代码要少得多了。
下面是C# 4.0中用来打开Word文档的代码:
using Word = Microsoft.Office.Interop.Word; namespace COMInterop { class Program { static void Main(string[] args) { Word.Application wordApplication = new Word.Application() {Visible = true}; wordApplication.Documents.Open(@"C:\\DevX.docx", ReadOnly: true); } } }
性能改善
PIA是由COM接口产生的,你可以在你的应用程序代码中使用这个程序集以一种强类型的方式与COM对象互操作,但它们很笨重,很消耗内存,会显著降低应用程序的性能。相反,互操作程序集通过前面的代码编译生成,只包含你的应用程序真正用到的互操作代码,从而大大减少了程序集的大小,提高了应用程序的性能。
动态导入大多数COM函数都是接收并返回变量类型,在PIA中表示为对象,因此,当你使用这些方法时,需要使用适当的强制类型转换,但在C# 4.0中,你可以使用dynamic关键字代替COM函数中的object,因此,现在不必再进行类型转换了。
思考一下下面的代码,你需要使用转换在excel文档中为某个特定单元格设置一个值:
((Excel.Range)excelObj.Cells[5, 5]).Value = "This is sample text";
上面的代码需要使用强制类型转换,但在C# 4.0中,你可以消除掉强制类型转换了,这一切都得感谢dynamic关键字,下面是C# 4.0中的实现方法:
excelObj.Cells[5, 5].Value = "This is sample text";
下面是一个更为完整的例子,使用的是C# 3.0代码保存excel文档:
using Excel = Microsoft.Office.Interop.Excel; namespace COMInterop { class Program { static void Main(string[] args) { var excelApplication = new Excel.Application(); excelApplication.Visible = true; dynamic excelWorkBook = excelApplication.Workbooks.Add( System.Reflection.Missing.Value); Excel.Worksheet wkSheetData = ( Excel.Worksheet)excelWorkBook.ActiveSheet; excelWorkBook.SaveAs("Testfile.xls", System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, Excel.XlSaveAsAccessMode.xlShared, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value, System.Reflection.Missing.Value); } } }
在C# 4.0中,你不再需要使用缺失值和明确的强制类型转换了,下面是在C# 4.0中没有使用缺失值和强制类型转换的代码:
using Excel = Microsoft.Office.Interop.Excel; namespace COMInterop { class Program { static void Main(string[] args) { var excelApplication = new Excel.Application(); excelApplication.Visible = true; dynamic excelWorkBook = excelApplication.Workbooks.Add(); Excel.Worksheet wkSheetData = excelWorkBook.ActiveSheet; excelWorkBook.SaveAs( "Testfile.xls", AccessMode: Excel.XlSaveAsAccessMode.xlShared); } } }
注意:COM有一个和托管代码完全不同的编程模型,为了调用COM函数,C#编译器允许你传递参数值,并会产生临时变量来保存这些值,当函数调用完毕后这些变量就会被丢掉。
方差支持
在C# 4.0中,你可以对泛型类型指定in(仅输入)好out(只返回)参数,这些参数可以作为唯一的输入参数或只作为这种类型的返回值被传递。
C# 4.0中对方差有两方面的支持:协方差和方差。如果你必须使用完全匹配正式类型的名称,那么返回的值或参数是不变的。如果你能够使用更多的衍生类型作为正式参数类型的代替物,那么参数是可变的。如果你能够将返回的类型分配给拥有较少类型的变量,那么返回的值是逆变的。
我这里不涉及任何不变量参数,你也应该从C#早期版本中熟悉了。
协方差
使用一个简单的例子更容易理解协方差,注意string是一个特殊的类型,而object是一个泛型类型,因此string是协变到object的。下面我们来看看C# 4.0中是如何支持协方差的,在C# 4.0中定义的IEnumerable接口如下:
public interface IEnumerable : IEnumerable { IEnumerator GetEnumerator(); } public interface IEnumerator : IEnumerator { bool MoveNext(); T Current { get; } }
在c#的早期版本中,IEnumerable<string>不是IEnumerable<object>。注意前面Ienumerator<out T>定义中的out参数,它表明普通的T只可以在输出位置,否则编译器就会报告错误,T中的接口是协变的,意味着IEnumerable<P>也是 IEnumerable<Q>用P替换Q所得,因此,一个字符串序列也是一个对象序列,因此下面的语句完全有效:
IEnumerable<object> someObj = new List<string>();
下面的例子说明了你如何在C# 4.0中使用协方差:
namespace Test { class Base { //Methods and Properties of the Base Class } class Derived : Base { //Methods and Properties of the Derived Class } class Program { delegate T TestFunction<out T>(); static void Main(string[] args) { TestFunction<Derived> derivedObj = () => new Derived(); TestFunction<Base> baseObj = derivedObj; } } }
逆变性
C# 4.0中泛型接口的类型参数可以在in修饰字中,允许它们只出现在输入位置,例如:
public interface IComparer<in T> { public int Compare(T left, T right); }
因此,比较器既可以比较对象也可以比较字符串,这就叫做逆变性。逆变性的一个例子就是Equals()函数和CompareTo()函数。如果你有一个函数可以比较两个基类的实例,那么你也可以使用它比较两个派生类的实例,你可以在一个类型对象的实例中存储任何函数调用的结果,因为C#中函数返回的类型是逆变的。
下面是前面例子的逆变副本:
namespace Test { class Base { } class Derived : Base { } class Program { delegate void TestDelegate<in T>(T a); static void Main(string[] args) { TestDelegate<Base> baseObj = (obj) => { System.Console.WriteLine(obj); }; TestDelegate<Derived> derivedObj = baseObj; } } }