首先祝大家元旦快乐(有朋友祝福我新年快乐,但总觉得没那个味道,我心中的新年快乐一直都是春节快乐的意思,不过听Happy New Year又能接受~~)。这篇文章我主要给大家介绍一个C# Team很少提及的一个关于C# 4.0的新特性--索引属性。文章主要从索引属性是什么,为什么需要它,为什么C#又不完全支持它,目前C#索引属性的实现以及怎样使用它四个方面进行介绍。

什么是索引属性

望文生义,索引属性就是指能根据索引值返回指定结果的属性,它是访问属性的原子操作,如someObject.someProperty[index]。索引化属性跟普通属性差不多,唯一不同的是它多了一个额外的表示索引值的参数。这个新的特性加入到C# 4.0中比较晚,它在VS 2010 Beta1中都还没有,是在Beta2中才加入的新特性。

为什么我们需要它?

如果你经常跟COM打交道,你应该应该会遇到类似下面的代码(这段代码还是Scott Hanselman 在Beta1发布时候写的例子:):

var excel = new Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();
excel.get_Range("A1").Value2 = "Process Name";
excel.get_Range("B1").Value2 = "Memory Usage";

这段代码还仅仅是一部分,实际上你可能会碰到更难缠的代码。e.g.

myObject.set_IndexedProperty(myObject.get_IndexedProperty(index) + 1)。它看起来真的很臃肿,为什么我们不可以这样来调用呢?myObject.IndexedProperty[index]++。
作为一个存在已久的模块,COM里面有很多很多像上面这种属性操作方法,它也使得程序员写出的与COM交互的代码看起来臃肿不堪。为了解决这种状况,C# 4.0中提出了很多改进C#与COM交互的方式,
dynamic是其中一种,而这里C# Team则通过索引属性改善上面提到的这些状况,它使得程序员写出的代码更为简洁。

为什么C#不支持定义索引属性

然而,尽管索引属性可以简化代码,但是,目前C#对它的支持却极其有限,它只能用于C#调用COM中的对象才会出现,你不可以在C#中声明和调用索引属性。并且其实现并未从编译器或CLR中实现,而仅仅是通过语法糖来简单实现而已。这又是为什么呢?

C# Team给出的答案是C#中对象不同的职责的分离,理由如下:

属性是用来获取对象的,它从属于其父对象。

索引值是用在属性返回的对象中枚举它的,它应该从属于返回的对象而不是属性的父对象。

鉴于这种原因,C#一直不打算在C# 4.0 中实现这个特性,这个新的特性无论是在C# 4.0 language specification还是C# Future page都没有被提到,它看起来更像是C# Team对不得不与COM交互的某种暂时性的妥协而已。实际上照C# Team的想法,他们其实早就给出了他们认为是正确的索引属性的使用方式,读者如果有看VS 2008 目录下的Sample文件夹的话,你可以看到里面有个专门的indexedproperty的例子,在2010 beta2中的Sample中它则变成了Indexers_2,不过两者代码是一样的。另外,MSDN上这篇文章也给出了例子,这里我就不给出详细实现,而给出一个类似的例子。首先我们定义一个带索引器的集合类e.g.

public SomeCollection 
{
    public int this[int index] { get{//bla} set{//bla} }
 }
 public class IndexerTest 
{
    public SomeCollection somePropertys{ get; set; }
    public int[] otherPropertys{get;set;}
 }

然后你可以这样使用:

class Program
{
      static void main(){
         IndexTest it=new IndexTest();
         int a=it.somePropertys[0];
          int b=it.otherPropertys[0];
       }
}

从这些代码不难看出C# Team对索引属性的观点,他们认为索引器的职责应该属于属性返回的对象的,而不是属于该属性的父对象,父对象无法通过索引值来控制属性返回值。你添加到属性上的索引其实是对集合或者数组操作,而不是由IndexTest在自己内部控制。从这个观点上将,我们就不难理解为什么C# 4.0仅仅支持COM的索引属性了。

索引属性的实现

目前的C# 4.0并不支持声明索引属性,它仅仅在COM中使用,在用户引用COM对象的时候,编译器会从COM类型中(标有tdImport标志的类型)导入所有看起来像索引属性的成员,然后提供给用户。通过这种方式,用户现在可以通过属性索引器语法形式来访问这些成员。下面我们根据上面的例子来看看具体过程。

当编译器碰到索引属性的情况时,它会绑定.号左边的对象即it,然后它会获取it的对象类型即IndexTest,接着,它会去IndexTest类中查找所有名称为somePropertys的成员。

为了向后兼容,编译器优先考虑的是属性名匹配,而不去考虑这个属性返回值是不是真的支持索引属性。从这个角度来看,索引属性有点像扩展方法,只有当正常的方法匹配都失效的时候,它们才会被用到。举例来说吧,如果编译器在IndexTest中找到了一个普通的属性名字叫做somePropertys,它仍认为这个属性就是用户想要调用的,即使这个属性somePropertys并没有实现索引器或不是一个数组。接着,当编译器发现这个属性的返回值并不支持索引器,这时候匹配失败,编译器会报错。

当编译器找不到正常的属性跟待查找的名字一致时候,它会去检查IndexTest是不是COM类型,如果是的话,它会用与重载函数类似的匹配方式在所有符合条件的索引属性中找到匹配的成员,这时候它查找的其实是get_属性名(index)这种格式,如果能找到,编译器则把匹配的方法当做索引化属性调用。e.g. 上例中的索引属性其实IL代码类似IndexTest.get_somePropertys(index).

索引属性的使用

说到底,其实索引属性只是个语法糖而已,编译器和CLR跟以前比并没有什么变化,虽然如此,不过它确实能简化我们代码的书写。所有以前你在与COM交互时候调用get_X()或者set_X()的方法,现在都可以通过X[]了,e.g.

// C# 3.0
excel.get_Range("A1").set_Value(Type.Missing, "ID");
// C# 4.0
excel.Range["A1"].Value = "ID";
还是上面提到的Scott Hanselman在Beta1发布时候写的例子,在Beta2之后我们可以更简单了:
var excel = new Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();
excel.Range["A1"].Value = "Process Name";
excel.Range["B1"].Value = "Memory Usage";

代码是不是简化很多?虽然上面代码本质上是一样的,不过它确实看起来更加简洁。如果你需要经常与COM打交道,相信你应该会很喜欢索引属性的。另外,目前索引属性不仅支持静态代码,还支持动态代码。

总结

由于C# Team并不想在C#中加入索引属性这个特性支持(尽管VB支持),你可以参考关于Anders大大的这篇采访视频, 它是在后期由于要求对COM有更好的支持才被迫加上的。所以这个特性并没有在编译器或CLR内部做什么大的改变,它仅仅是个语法糖,并且实现得很有限。首先,它只能用在COM中,在普通的C#代码中你无法声明和定义这个特性,所以这也导致它的使用范围相当有限,可能很多人甚至都不会接触到;其次,它只是让用户看起来像调用索引属性,实际上则是通过get_X和set_X方法获取属性。最后,为了向后兼容,使得以前旧的代码仍能正常工作,你也可以选择继续使用get_X()和set_X()方法来操作属性。

希望本文能让你对索引属性有所了解!

参考文章:

1. Com interop in C# 4.0: Indexed Properties 作者: samng, 地址:http://blogs.msdn.com/samng/archive/2009/11/03/com-interop-in-c-4-0-indexed-properties.aspx

posted on 2010-01-02 08:14  jujusharp  阅读(5128)  评论(9编辑  收藏  举报