技术开发 频道

拯救C#2.0,但是我们真做的到吗?

 【IT168技术文档】

 所转载自Jeff Zhao的博客

    似乎还有不少项目在用C#2.0(本文最后我们来做一个调查),但是C#2.0的生产力实在不如C#3.0——如果您不信,那么一会儿就会意识到这一点。有朋友认为语言能力不重要,有了好用的框架/类库也可以有很高的生产力。所以这篇文章,我们就设法使用“类库”来弥补C#2.0的缺陷。

 但是,我们真做的到吗?

 C#2.0之殇

 C#2.0较C#1.0来说是一个突破,其中引入了泛型,以及匿名方法等新特性。如果前者还可以说是平台的增强,而语言只是个“辅助”的话,而后者则百分之一百是编译器的魔法了。别小看这个特性,它为C#3.0的高生产力踏出了坚实的一步——不过还是差了很多。例如,我们有一个要求:“把一个字符串数组中的元素转化为整数,再将其中的偶数放入一个List<int>容器中”。如果是C#3.0,这是再简单不过的功能:

string[]strArray={"1","2","3","4"};

 vareven=strArray.Select(s=>Int32.Parse(s)).Where(i=>i%2==0).ToList();

 那么对于C#2.0(当然对于C#1.0也一样),代码又该怎么写呢?

 List<int>even=newList<int>();

 foreach(stringsinstrArray)

 {

 inti=Int32.Parse(s);

 if(i%2==0)

 {

 even.Add(i);

 }

 }

 有人说函数式编程有什么用,C#3.0就是个很好的证明。C#3.0中引入了Lambda表达式,增强了在语言中构造匿名方法的能力——这是一个语言中函数式编程特性的必备条件。C#3.0的实现与C#2.0相比,可读性高,可以直接看出转化、过滤,以及构造容器的过程和标准。由于语言能力的增强,程序的表现能力得到了很大的提高,在很多时候,我们可以省去将一些代码提取为独立方法的必要。当然,即使您将其提取为额外的方法,C#3.0也可以让您写出更少的代码。

 如果您觉得以上代码的差距还不是过于明显的话——那么以下功能呢?

int[]intArray={1,2,3,4,5,6,7,8,9,10};

 //所有偶数的平均数

 varevenAverage=intArray.Where(i=>i%2==0).Average();

 //都是偶数?

 varallEven=intArray.All(i=>i%2==0);

 //包含偶数?

 varcontainsEven=intArray.Any(i=>i%2==0);

 //第4到第8个数

 varfourthToEighth=intArray.Skip(3).Take(5);

 如果您使用C#2.0来写,您会怎么做?

 拯救C#2.0

 C#3.0通过引入了函数式编程特性大幅增强了语言的生产力。如果说C#2.0和Java还没有太大差距的话,那么C#3.0已经将Java甩开地太远太远。不过真要说起来,在Java中并非不可以加入函数式编程的理念。只不过,如果没有足够的语言特性进行支持(如快速构造匿名函数、闭包、一定程度的类型推演等等),函数式编程对于某些语言来说几乎只能成为“理念”。不过现在,我们暂且先放下对“函数式编程”相关内容的探索,设法拯救C#2.0所缺失的生产力吧。

 C#3.0中可以使用Lambda表达式构造一个匿名函数,这个能力其实在C#2.0中也有。我们姑且认为这点不是造成差距的主要原因,那么有一点是C#2.0绝对无法实现的,那就是“扩展方法”。C#3.0中的扩展方法,可以“零耦合”地为一个,甚至一系列类型添加“实例方法”。当然,这也是编译器的功能,实际上我们只是定义了一些静态方法而已。这一点在C#2.0中还是可以做到的:

publicclassEnumerable

 {

 publicstaticIEnumerable<T>Where<T>(Func<T,bool>predicate,IEnumerable<T>source)

 {

 foreach(Titeminsource)

 {

 if(predicate(item))

 {

 yieldreturnitem;

 }

 }

 }

 publicstaticIEnumerable<TResult>Select<T,TResult>(Func<T,TResult>selector,IEnumerable<T>source)

 {

 foreach(Titeminsource)

 {

 yieldreturnselector(item);

 }

 }

 publicstaticList<T>ToList<T>(IEnumerable<T>source)

 {

 List<T>list=newList<T>();

 foreach(Titeminsource)

 {

 list.Add(item);

 }

 returnlist;

 }

 }

 于是现在,我们便可以换种写法来实现相同的功能了:

 string[]strArray={"1","2","3","4"};

 List<int>even=

 Enumerable.ToList(

 Enumerable.Where(

 delegate(inti){returni%2==0;},

 Enumerable.Select(

 delegate(strings){returnInt32.Parse(s);},

 strArray)));

 即使您可以接受delegate关键字构造匿名函数的能力,但是上面的做法还是有个天生的缺陷:逻辑与表现的次序想反。我们想表现的逻辑顺序为:转化(Select)、过滤(Where)、及容器构造(ToList),C#3.0所表现出的顺序和它相同,而C#2.0的顺序则相反。由于语言能力的缺失,这个差距无法弥补。很多时候,语言的一些“小功能”并不能说是可有可无的特性,它很可能直接决定了是否可以用某种语言来构造InternalDSL或进行BDD。例如,由于F#的灵活语法,FsTest使得开发人员可以写出"foobar"|>shouldcontains"foo"这样的语句来避免机械的Assert语法。同样,老赵也曾经使用actor<=msg这样的逻辑来替代actor.Post(msg)的显式调用方式。

0
相关文章