Sunday, May 06, 2007

LINQ,扩展方法(Extension Methods)

3.2         扩展方法(Extension Methods


Lambda表达式是查询体系(query architecture)的一个重要部分,扩展方法(Extension methods)是另一个重要组成部分。扩展方法结合了“duck typing”(一种可替代任何类型的类型)的弹性(flexibility)特点,使得动态语言(dynamic languages,如JavaScript)更加流行(popular),同时又有静态型语言(statically typed languages,如C++Java)的优秀性能(performance)和编译时验证(compile-time validation)的特点。通过扩展方法,第三方(third parties)可以增加使用了新方法的类型(type with new methods)的公共合同(public contract),使得独立型的开发者(individual type authors)还能提供他们对这些方法的自己专门的实现(specialized implementation)。


 


扩展方法定义在静态类(static classes)里,就像静态方法(static methods)一样,但是它们在CLR元数据(CLR metadata)里却被标记为[System.Runtime.CompilerServices.Extension]。程序语言都被鼓励(encourage)为扩展方法提供一种直观的语法(direct syntax)。在 C# 语言里,扩展方法是由 this 指针(this modifer)标示(indicate)的,它必须应用于(applied to)扩展方法的第一个参数。下面让我们来看看一段最简单的查询操作符(query operator)的定义代码:


 


namespace System.Query {
  
using System;
  
using System.Collections.Generic;

  
public static class Sequence {
    
public static IEnumerable<T> Where<T>(
             
this IEnumerable<T> source,
                  Func
<T, bool> predicate) {

      
foreach (T item in source)
        
if (predicate(item))
          yield 
return item;
    }
  }
}


扩展方法的第一个参数的类型标示(indicate)扩展的内容所应用(the extension applies to)的类型。在上面的例子中,扩展方法 Where 扩展自 IEnumerable<T> 类型。因为 Where 是一个静态方法(static method),所以我们可以像其他静态方法一样直接调用它(invoke it directly):


 


IEnumerable<string> expr = Sequence.Where(names, 
                                          s 
=> s.Length < 6);

 


然而,扩展方法的一个独特(unique)的特点是我们还可以使用实例的语法(using instance syntax)来调用它:


 


IEnumerable<string> expr = names.Where(s => s.Length < 6);

 


扩展方法是基于扩展方法的活动范围内(in scope)在编译时(compile-time)决定的(resolved)。当引入(import)一个命名空间(namespace)时(在 C# 里使用 using 语句,在VB 里使用 Import 语句),所有的扩展方法是由从 namespace 带入活动范围(brought into scope)的静态类(static classes)定义的。


 


标准查询操作符(standard query operators)在 System.Query.Sequence 类型里定义成扩展方法。当检查标准查询操作符时,你将注意到它们中所有的方法根据(in terms of)接口IEnumerable<T> 来定义的(除了 OfType,将在后面介绍它)。这意味着每一个兼容 IEnumerable<T> 接口的信息源(information source)在 C# 语言里通过简单地添加如下的 using 语句就可以获取标准查询操作符:


 


using System.Query; // makes query operators visible

 


用户如果想用一个指定的类型(specific type)来替换标准查询操作符,可以在有一致标识(compatible signatures)的指定的类型上定义他们自己的相同名字的方法(same-named methods),另一种方法是通过扩展指定的类型来(extend the specific type)定义相同名字的方法。用户如果想完全避免(eschew)使用标准查询操作符,可以简单地将 System.Query 置于活动范围之外,并且为 IEnumerable<T> 接口写出他们自己的扩展方法就可以了。


 


按照分析规则,扩展方法被赋予了最低优先权(lowest priority),它们只是在没有相适配(suitable)的目标类型(target type)和它们的基类型(base types)时才被使用。这个规则允许用户自定的类型(user-defined types)提供他们自己的查询操作符,比标准查询操作符更优先使用(take precedence over)。例如,考虑用户自定义 collection 的实现如下:


 


public class MySequence : IEnumerable<int> {
  
public IEnumerator<int> GetEnumerator() {
    
for (int i = 1; i <= 10; i++
      yield 
return i; 
  }

  IEnumerator IEnumerable.GetEnumerator() {
    
return GetEnumerator(); 
  }

  
public IEnumerable<int> Where(Func<intbool> filter) {
    
for (int i = 1; i <= 10; i++
      
if (filter(i)) 
        yield 
return i;
  }
}

 


通过给出这个类的定义,下面的程序:


 


MySequence s = new MySequence();
foreach (int item in s.Where(n => n > 3))
    Console.WriteLine(item);

 


将使用 MySequence.Where 的实现,而不是扩展方法,因为类实体方法(instance methods)比扩展方法(extension methods)更优先使用。


 


前面的提及(mentioned earlier)了属于标准操作符(standard operator)一员但不是扩展自基于IEnumerable<T> 接口(IEnumerable<T>-based)的信息源的 OfType 类型,下面让我们来看看 OfTyoe 查询操作符:


 


public static IEnumerable<T> OfType<T>(this IEnumerable source) {
  
foreach (object item in source) 
    
if (item is T) 
      yield 
return (T)item;
}

 


OfType 即接受基于IEnumerable<T> 接口的信息源,也接受那些在 .NET Framework 1.0 中出现的非参数化的 IEnumerable 接口(non-parameterized IEnumerable interface)。OfType 操作符允许用户在标准的 .NET collections (classic .NET collections)上应用标准查询操作符,就像下面的程序:


 


// "classic" cannot be used directly with query operators
IEnumerable classic = new OlderCollectionType();

// "modern" can be used directly with query operators
IEnumerable<object> modern = classic.OfType<object>();

 


在这个例子中,变量 modern 产生了与变量 classic 一样的顺序的数据列表(same sequence of values),但是不同的是它的类型兼容最新的 IEnumerable<T> 代码(modern IEnumerable<T> code),包括标准查询操作符。


 


OfType 操作符对新的信息源也是有用的,因为它允许从基于类型的信息源(source based on type)中过滤出数据(filtering values from)。当要制作新的顺序的时候(producing the new sequence),OfType 简单的忽略(omits)原有序列的成员(members of the original sequence)就可以了,这是与类型实参(type argument)不相符的。分析下面的程序,目标是将 string 字符串数据从一个有不同种类数据的数组(heterogeneous array)中分解出来:


 


object[] vals = { 1"Hello"true"World"9.1 };
IEnumerable
<string> justStrings = vals.OfType<string>();

 


当在 foreach 语句中列举(enumerate)变量 justStrings 中的数据时,将得到两个依次为“Hello”和“World”的 string 字符串序列(a sequence of two strings )。


 


待续, 错误难免,请批评指正,译者Naven 2005-10-22


No comments: