Extension Method 和lambda

深入理解C# 3.x的新特性(2):Extension Method - Part I

一、Prototype in JavaScript

为了说明Extension method到底是为了解决怎样的问题,我首先给出一个类似的、大家都比较熟悉的应用:JavaScript 中的Prototype。

比如我们在JS通过function定义了一个Vector class,代表一个2维向量。

function  Vector (x,y) {      this.x = x;      this.y = y; }

现在我们需要在不改变Vector定义的前提下,为之添加相关的进行向量运算的Method。比如我们现在需要添加一个进行两个向量相加运算的adds方法。在JS中,我们很容易通过Prototype实现这一功能:

Vector.prototype.adds  =   function (v) {     if(v instanceof Vector)      {                return new Vector(this.x+v.x, this.y + v.y);       }       else       {               alert("Invalid Vector object!");       } }

那么,通过添加上面的一段代码,我们完全可以把adds方法作为Vector的一个方法成员。现在我们可以这样的方式来写代码:

var  v  =   new  Vector ( 1 , 2 ); v =  v.adds(v); alert( " x =  "   + v.x  +   " , y =  " + v.y);

Extension Method之于C# 3.0就如同Prototype之于JavaScript。 

二、如何在C# 2.0中解决Type的扩展性

我们一个完全一样的问题从弱类型、解释型的编程语言JavaScript迁移到C#这种强类型、编译型的语言上来。我们先看看在不能借助Extension Method这一新特性的C# 2.0中,我们是如何解决这一问题。

我们先来看看如何对一个Interface进行扩张。假设我们有如下的一个IVector interface的定义:

public   interface  IVector {         double X getset; }         double Y getset; } }

我们希望的是如何对这个Interface进行扩展,为之添加一个Adds Method执行向量相加的运算。我们唯一的解决方案就是直接在这个Interface中添加一个Adds成员:

public   interface  IVector {         double X getset; }         double Y getset; }         IVector Adds(IVector vector); }

由于Interface和实现它的Type的紧 密联系:所以实现了某个Interface的Type必须实现该Interface的所有方法。所以,我们添加了Adds Method,将导致所有实现它的Type的重新定义和编译,在很多情况下,这种代价我们是负担不起的:比如在系统的后期维护阶段,对系统的进行局部和全 部的重新编译,将很有可以导致一个正常运行的系统崩溃。Interface的这种局限性在面向抽象设计和编程中应该得到充分的考虑,这也是我们在很多情况 下宁愿使用Abstract Class的一个主要原因。

上面说到了对Interface的扩展,会出现 必须实现Interface的Type进行改动的风险。我想有人会说,对Class尽心扩展就不会出现这样的情况了吧。不错,Class的继承性确保我们 在Parent class添加的Public/Protect能被Child Class继承。比如:如果Vector是一个Super Class:

public   class  Vector       {         private double _x;         private double _y;         public double X         {             get {return this._x;}             set this._x = value;}         }         public double Y         {             get return this._y;}             set {this._y = value;}         } }

如果我们在Vector Class中添加一个Adds Method,所有的Child Class都不会受到影响。

但是在很多情况下,对于我们需要扩展的 Interface或者是Type,我们是完全不能做任何改动。比如,某个Type定义在一个由第三方提供的Assembly中。在现有的情况下,对于这 样的需求我们将无能为力。我们常用的方法就自己定义的Class去继承这个需要扩展,将需要添加的成员定义在我们自己定义的Class中,如果对于一个 Sealed Class又该如何呢?即便不是Sealed Class,这作用方式也没有完成我们预定的要求:我们要求的是对这个不能变动的Type进行扩展,也就是所这个不能变动的Type的Instance具 有我们添加的对象。

如果你在完全了解Extension Method的前提下听到这样的要求:我们要对一个Type或者Interface进行扩展,却不允许我们修改它。这个要求确实有点苛刻。但是,不能否认 的是,这样需要在现实中的运用是相当广泛的。所以我说,Extension Method在所有提供的新特性中,是具有价值的一个。

三、C# 3.0中如何解决Type的扩展性

理解了我们的具体需要和现有编程语言的局限性后,我们来看看C# 3.0中是如何通过Extension Method解决这个问题的。

简单地说Extension Method是一个定义在Static Class的一个特殊的Static  Method。之所以说这个Static Method特别,是因为Extension Method不但能按照Static Method的语法进行调用,还能按照Instance Method的语法进行调用。

我们还是先来看例子,首先是我们需要进行扩展的Vector Type的定义:

public   class  Vector  {         private double _x;         private double _y;         public double X         {             get {return this._x;}             set this._x = value;}         }         public double Y         {             get return this._y;}             set {this._y = value;}         } }

在不对Vector Class的定义进行更新的前提下,我们把需要添加的Adds方法定义在一个Static Class中:

public   static   class  Extension      {             public static Vector Adds(this Vector p,Vector p1)         {             return new Vector { X = p.X + p1.X, Y = p.Y + p1.Y };         } }

这个Extension Method:Adds是一个Static方法。和一般的Static方法不同的是:在第一个参数前添加了一个this 关键字。这是在C# 3.0中定义Extension Method而引入的关键字。添加了这样一个关键字就意味着在调用该方法的时候这个标记有this的参数可以前置,从而允许我们向调用一般 Instance Method的方式来调用这个Static Method。比如:

class  Program      {         static void Main(string[] args)         {             var v = new Vector { X = 1, Y = 2 };             v = v.Adds(v);             Console.WriteLine("v.X = {0} and v.Y = {1}", v.X, v.Y);         } }

注:this关键字只能用于标记第一个参数。 

通过上面的介绍,我们知道在C# 3.0如何通过定义Extension Method在不对Type作任何修改的前提下对Type进行扩展。 Extension Method:它是定义在一个 Static class中的、第一个Parameter标记为 this关键字的 Static Method。在这一节中,我们来进一步认识Extension Method。 Extension Method本质上是一个Static Method。但是我们往往以 Instance Method的方式进行调用。C# Compiler的作用很明显: 把一个以Instance Method方式调用的Source Code编译成的于对应于传统的Static Method调用的IL Code。 LAMBDA private static bool SomeMethod(int args)         {             return (args > 0);         } ==>             Function<intbool> function2 = delegate(int args)             {                 return args > 0;             };             function2(20);  ===> Function<intbool> function3 = x => x > 0;             function3(20);

在C#里,一个lambda表达式在句法上是写成一个参数列表,随后是 => 符号,随后是表达式在调用时要运算的表达式或者语句块:

params => expression

所以,当我们编写这样的lambda表达式时:

p => p.LastName == "Guthrie" 

我们是想表示,我们在定义的Lambda接受一个参数p,要运行的代码表达式返回p.LastName的值是否等于“Guthrie”。 我们将参数命名为p是不相干的,我也可以很容易地将其命名为o,x,foo,或者我想要的任何名字。

不象匿名方法要求参数类型是明确地指明的,Lambda表达式允许省略参数类型,而允许它们根据用法来推断出类型。譬如,当我编写 p=>p.LastName == "Guthrie" 这个lambda表达式时,编译器推断出p参数属于Person类型,因为当前的Where扩展方法的对象是个范型的List< Person>集合。

框架开发人员可以通过声明他们的Lambda表达式参数是个Expression<T>类型,而不是Func<T>类型来取得这样的结果。这会导致Lambda表达式参数被编译成一个我们可以在运行时拆开和分析的表达式树:

注意上面我是怎么把我们在先前用过的同样的 p=>p.LastName == "Guthrie" Lambda表达式,但这次将其赋值给一个 Expression<Func<Person, bool>> 变量,而不是Func<Person,bool> 变量。编译器不会产生IL,而是会指派一个表达式树对象,然后我作为一个框架开发人员就可以用它来对相应的Lambda表达式进行分析,按我想要的方式对其进行运算(譬如,我可以挑出表达式中的类型,名字和值等)。

在LINQ到SQL的情形下,它会将这个Lambda过滤语句翻译成标准的关系SQL语句,来对数据库进行操作(从逻辑上来说,一个“SELECT * from Products where UnitPrice < 55”语句)。

IQueryable<T> 接口

为帮助框架开发人员建立可查询的数据提供器,LINQ提供了 IQueryable<T> 接口。这个接口实现了标准的LINQ扩展方法查询运算符,提供了一个更便利的方式来实现对一个复杂的表达式树的处理(譬如,象下面这样,我用了3个不同的 扩展方法,2个lambda来从数据库取回10个产品的情形):

static void LambdaExample(List<string> list)  {   var evenNumbers = list.FindAll(i =>(i.Length % 2) == 0); // example of single parameter   foreach(string i in evenNumbers)   {    Console.WriteLine(i);   }  } 多参数的Lambda表达式  Dictionary<string, int> varClothes= new Dictionary<string,int>();    varClothes.Add("Jeans", 20);    varClothes.Add("Shirts", 15);    varClothes.Add("Pajamas", 9);    varClothes.Add("Shoes", 9);    var ClothesListShortage = varClothes.FilterBy((string name,    int count) => name == "Shoes" && count < 10); VS2008亮点:用Lambda表达式进行函数式编程 Functional Programing:函数式编程是种编程典范,它将电脑运算视为函数 (数学)的计算。 和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。 和过程化编程相比,函数式编程里,函数的计算可随时调用。 函数式编程的思想:
int [] a; a.Where(i  =>  i  >   0 ).Select(i  =>  i  *   2 );
这里,Where函数将我们用Lambda表达式传递的运算规则作用于a[]的每一个元素上,然后返回满足条件的所有元素。这就是一种筛选操作。而Select函数则将规则作用于集合的每一个元素后返回新值组成的集合。这是一种映射操作。
Function (a) a  +   1   ' 等于C#的 a => a + 1 Function (a  As   Integer ) a  +   1   ' 显式指定类型 Function (a, b) a  +  b  ' 两个或多个参数
(int x) => x + 1 // 显式类型参数 (y,z) => return y * z; // 隐式类型参数  如您所见,和普通的函数声明相比,它只是没有函数的名字,以及不需要使用Return语句而已。 递归 我们来看一个用Y实现递归的例子:
Dim  fact  =  Y1( Function (self)  Function (n)  If (n  =   0 1 , n  *  self.Invoke(n  -   1 )))
注意,fact这个函数是一个真正的匿名函数,它的内部并没有引用自己的名字,但是它采用不动点算子实现了递归。我们知道Y1作用于任何函数数,都等于将后者再次作用于整个体系上的效果。所以上述代码进行这种规则运算后就等同于:
fact  =   Function (n)  If (n  =   0 1 , n  *  fact(n  -   1 ))
声明同时调用
Dim  result  =  ( Function (a, b) a  +  b)( 1 2 )
java.lang.NullPointerException at com.example.machinepotest.mappertest.tes(mappertest.java:23) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725) at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131) at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140) at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84) at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115) at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105) at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45) at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104) at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135) at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141) at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137) at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139) at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73) at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138) at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
05-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值