Artech

Develop every application as an art using the most suitable technologies!

常用链接

统计

积分与排名

网上邻居

我的博文系列

最新评论

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

在C#3.0中,引入了一些列新的特性,比如: Implicitly typed local variable, Extension method,Lambda expression, Object initializer, Anonymous type, Implicitly typed array, Query expression, Expression tree. 个人觉得在这一系列新特性的,最具创新意义的还是Extension method,它从根本上解决了这样的问题:在保持现有Type原封不动的情况下对其进行扩展,你可以在对Type的定义不做任何变动的情况下,为之添加所需的方法成员。在继《深入理解C# 3.0的新特性(1): Anonymous Type 》之后,在这篇文章中,我将介绍我自己对Extension method这个新特性的理解。

一、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的本质:C# Compiler在编译Extension Method时会做怎样处理;在最终被编译成的Assembly中相关的IL具有怎样的特征;Extension Method的优先级,如果有兴趣,可以参考《[原创]深入理解C# 3.0的新特性(2):Extension Method - Part II》,此外在第二部分中,我会给出一个完整的Sample:通过Extension Method定义一个形如LINQ中常见的Operator,完成基于LINQ的查询功能。 

C# 3.x相关内容:
[原创]深入理解C# 3.x的新特性(1):Anonymous Type
[原创]深入理解C# 3.x的新特性(2):Extension Method - Part I
[原创]深入理解C# 3.x的新特性(2):Extension Method - Part II
[原创]深入理解C# 3.x的新特性(3):从Delegate、Anonymous Method到Lambda Expression
[原创]深入理解C# 3.x的新特性(4):Automatically Implemented Property
[原创]深入理解C# 3.x的新特性(5):Object Initializer 和 Collection Initializer

posted on 2007-07-18 01:30 Artech 阅读(4320) 评论(41)  编辑 收藏 网摘 所属分类: E. LINQ

评论

#1楼[楼主] 2007-07-18 01:51 Artech      

坐自己的沙发,让别人坐板凳去吧:)   回复  引用  查看    

#2楼 2007-07-18 07:51 布尔      

这是不是高级语言在向Javascript靠拢呢?我一直很喜欢Javascript的设计   回复  引用  查看    

#3楼 2007-07-18 08:20 allnonsense[未注册用户]

不见CLR层次的分析?   回复  引用    

#4楼 2007-07-18 08:33 kevin

不错,顶.   回复  引用    

#5楼[楼主] 2007-07-18 09:08 Artech      

@allnonsense
CLR的分析放在Pat II。
  回复  引用  查看    

#6楼[楼主] 2007-07-18 09:09 Artech      

@布尔
MS的东西就是这样,别人好的东西都来者不拒。有人说他抄袭,也有人说他兼容并包。
  回复  引用  查看    

#7楼[楼主] 2007-07-18 09:10 Artech      

@kevin
:)
  回复  引用  查看    

#8楼 2007-07-18 09:18 紫色阴影      

@Artech
还是分析下IL代码好,呵呵 介绍性的看得多了
我感觉是编译器做了一些处理吧,实际上还是把p.Adds(p1)编译为
Adds(p,p1)
  回复  引用  查看    

#9楼[楼主] 2007-07-18 09:20 Artech      

@紫色阴影
昨天就写到这里,困了,所以只有将本质的东西放在Part II了:)
  回复  引用  查看    

#10楼 2007-07-18 09:22 紫色阴影      

@Artech
才发现你睡得很晚啊,呵呵 注意休息呢
  回复  引用  查看    

#11楼 2007-07-18 09:26 blindsniper[未注册用户]

Artech你的战斗力不是一般强啊,帖子一般都是在1,2点post的...
还有js里面:
return new Point(this.x+v.x, this.y + v.y);
这个应该是 new Victor(...)吧?
  回复  引用    

#12楼[楼主] 2007-07-18 09:31 Artech      

@blindsniper
@紫色阴影
习惯在晚上写的东西,可以不受干扰。
谢谢@blindsniper的提醒,原来定义的是Point,现在改过来了:)
  回复  引用  查看    

#13楼 2007-07-18 10:20 Nineteen@newsmth[未注册用户]

不看好这种东西,破坏OO封装.而且会给整个系统部署带来非常多的麻烦.   回复  引用    

#14楼 2007-07-18 10:24 刘荣华      

辛苦了
  回复  引用  查看    

#15楼[楼主] 2007-07-18 10:31 Artech      

@Nineteen@newsmth
见仁见智:)
个人觉得Extension会有其广泛的应用。我们在进行系统开发的时候,会经常因为不断变化的需求要对现有的Interface和Type进行扩展,Extension Method允许我们对现有的组建不做任何修改的情况下实现这个需求。对于已经发布的系统,这一点尤为重要。

更重要的是Extension Method成就了LINQ。
  回复  引用  查看    

#16楼[楼主] 2007-07-18 10:31 Artech      

@刘荣华
:)
  回复  引用  查看    

#17楼 2007-07-18 11:23 Nineteen@newsmth[未注册用户]

@Artech
说实话,还真没见过需要对现有Interface和Type进行扩展的需求.如果真出现这样的需求,那么一定是前期设计有问题.而且,高层模块需要某些功能,难道不能通过一些静态的Helper方法实现吗?

就像你说的,Extension Method成就了LINQ,我都怀疑它的出现是不是就是仅仅为了"成就LINQ",呵呵.

说起LINQ这个东东,就更不看好了.
打个比方,两个相同数量级的大集合做join运算,LINQ会使用什么算法?Merge or NestedLoop or HashJoin?显然这俩集合满足特定条件时有不同的选择.
这东西就跟TransactionScope一样,傻瓜是傻瓜了,但是实在是不怎么可用.

当然,作为纯粹的技术,分析分析还是能获取很多知识:),挺感谢你的介绍地说
  回复  引用    

#18楼 2007-07-18 11:23 宋国安      

Linq 中所有的Operator全部是定义的Extension method。

引:(并不一定完全正确)
Extension Method必须定义在普通的 static class内,它更像是静态类型的静态方法,事实上,它确实拥有静态方法所具有的所有功能。连生成的IL代码一模一样.........................
Extension Method的结合顺序,它是自左向右结合的,比如x有两个Extension Method方法A,B,x.A().B()将被编绎为B(A(x)).


原文参考:
http://www.cnblogs.com/hiber/archive/2007/04/29/725617.html" target="_new">http://www.cnblogs.com/hiber/archive/2007/04/29/725617.html
  回复  引用  查看    

#19楼[楼主] 2007-07-18 12:14 Artech      

@宋国安
我想他是没有仔细看Extension Method生成的IL,它和一般的Static Method是有细微的差别的。我将在Part II中介绍这点差异。
  回复  引用  查看    

#20楼[楼主] 2007-07-18 12:33 Artech      

@Nineteen@newsmth
谁能保证我的设计完全没有问题,我真的很羡慕你没有出现过对“现有Interface和Type进行扩展的需求”。我想出现这样的需求,一方面是设计的问题,但是更大的则来源于Client的需求的变化。

说到LINQ,我们先不忙说他的技术实现,单单说他的思想,我觉得也是一种很值得大力推广的。技术的发展有一个主线:统一。就像原来我们以开发分布式系统,我们要使用Remoting,Web Service, MSMQ,Enterprise Service... ...,现在我们有把他们统一起来的WCF。原来我们只能在Windows Form Application中使用事件驱动的编程方式,现在我们通过ASP.NET把这种快捷的方式迁移到Web Application。

LINQ就是为了实现对不同数据进行处理而产生。对于编程模式,OO已经深入人心。但是很多类型的处理却不能使用OO的方式来处理,比如relational data, XML。

我想Extension Method的初衷,也许正如同你所说,它是为“成就”LINQ而设计,但是现在来看,我们不应把他局限在LINQ。

至于TransactionScope,由于他是为了Distributed Transaction而设计,在一个Distributed Application,和SOA领域的运用将会使很多的。用起来很傻,其实它并不傻:)

以上仅代表个人观点。
  回复  引用  查看    

#21楼 2007-07-18 13:22 Nineteen@newsmth[未注册用户]

@Artech
LINQ没有深入研究过,但是相信,即使是简单的select,它也不能根据集合的特点选择算法.所以在性能critical一些的项目上,它的应用前景不见得那么广泛.

TransactionScope其实挺傻的,等你用的时候就知道了.
如:
1)即使你只操作相同数据库,哪怕只是调用同一个存储过程,只要操作次数超过1就会把事务从本地事务提高到分布式事务.
2)Scope里面,一条纯粹的select也会被作为事务的一部分

就这俩东西就傻到极点了,还不说SqlClient的俩相关bug.

反正俺用的时候宁愿通过CommittableTransaction去手工Enlist.

btw:留个联系方式可以吗?呵呵
  回复  引用    

#22楼[楼主] 2007-07-18 13:27 Artech      

@Nineteen@newsmth
Email:jiangjinnan@gmail.com
  回复  引用  查看    

#23楼 2007-07-19 07:15 microhf[未注册用户]

nono!
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!");
}
}
这是真正意义上的类方法,C#的Extension Method根本没法和其比

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 };
}
}
其实就是个XXXHelper或者XXXManager
对于Javascript我可以在想要的时候
Vector.add = function(v)
{
...
}
这使javascript有非常的魅力。
  回复  引用    

#24楼[楼主] 2007-07-19 11:25 Artech      

@microhf
我知道JS和C#他们毕竟是完全不同的语言,在实现方面肯定是存在差异。我把它和Extension Method作一个类比时候i为了说明Extension Method到底需要完成怎样的功能:在不对现有Class的定义作改动的情况下,对Class进行所需的扩展。
  回复  引用  查看    

#25楼 2007-07-21 13:05 Jeffrey Zhao      

Extension Method还是有局限性的,例如只能访问public members,呵呵。   回复  引用  查看    

#26楼 2007-07-26 09:16 镜涛      

是通过参数类型来决定扩展对象的么 ?   回复  引用  查看    

#27楼[楼主] 2007-07-26 16:07 Artech      

@镜涛
通过第一个用this标识的参数类型.
  回复  引用  查看    

#28楼 2007-07-29 11:32 随风流月      

@Nineteen@newsmth
LINQ 的 Join 的确性能不良。
但是它更大的用处在 DLinq 方面,将后期(!)生成 SQL 语句。
  回复  引用  查看    

#29楼 2007-07-29 11:32 随风流月      

@Jeffrey Zhao
它实际上就是一个 Shared Function,当然访问不到 Private 了 :-)
  回复  引用  查看    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 821881




相关文章:

相关链接: