一起谈.NET技术,由扩展方法引申出的编程思维

  1. Helper大爆炸

  .NET Framework为我们提供了丰富的类库,但是这并不是万能地,在大部分的时间,我们都需要为我们的项目特殊定制我们的通用类库。

  常常,我们都可以构造一个类,类里封装一些方法。但是对于很多时候,我们并没有办法提取出这样一个类,举一个小例子,我们在很多时候,需要把url给保存到数据库里,作为一个唯一标识,但是我们知道url所占空间很大,如果用url来建立索引的话是非常耗费空间,而且影响效率的,那么我们最常用的办法就是把url做一个Hash来作为索引的替代品。

  这个时候,我们根本就没有办法说我们来怎么样提取一个类,然后在类里写这样一个方法,这个时候,我们通常就只能这样:


public static class HashHelper
{
public static string GetHashCode(string s)
{
//GetHashCode........
return String.Empty
}
}

  然后我们会这样使用:


public static void Main(string[] args)
{
string url = "www.fandongxi.com"
string sql = "insert into Test values('"+HashHelper.GetHashCode(url)+"')"
//执行SQL
}

  这里,只是一个例子,并不是说我们要这个样子拼接字符串。

  很快,肯定又会出现一个情况,说,我们要保存网页的内容,但是网页的内容直接存储到数据库里太大了,那么我们就需要对网页文本做一个Base64的压缩。

  那么,我们就又得继续写:


public static class Base64Helper
{
public static string GetBase64Text(string text)
{
//Base64........
return String.Empty
}
}

  接下来我们在使用的地方就又多出来一个Base64Helper。那么过几天,还会出现SHA1Helper , MD5Helper等等各种各样的Helper。

  渐渐地,我们会不会发现,Helper的数量已经让我们难以忍受了呢?

  2. 扩展方法的提出

  接下来的事情,我们都知道了,在.NET Framework 3.5中,也就是在C#3.0中,引入了扩展方法这个概念。

  那就让我们扩展方法来解决上面的难题。

  各位现在一定知道,无论是做UrlHashCode,还是Base64压缩,还是SHA1加密,还是MD5加密,这些都是针对字符串,或者说是一段文本的处理,那么很自然地,我们就需要把这些全部写入String类的扩展方法中。


public static class ExtensionClass
{
public static string GetHashCode(this string s)
{
//........
}
public static string GetBase64Text(this string text)
{
//.......
}
}

public static void Main(string[] args)
{
string url = "www.fandongxi.com"
string sql = "insert into Test values('"+url.GetHashCode()+"')"
//执行SQL
}

  在这里,我不想剖析去读扩展方法的实现本质,这里我们只谈编程思维和扩展方法所带来的意义。

  3. 扩展方法让C#更加面向对象

  从面向对象的角度来看,世间万物皆为对象,所有属性,所有方法都是属于某一个对象的,那么再从这个角度看开去,本就不应该存在静态类,也不应该存在静态方法,所谓的静态,不过是面向对象语言对并不成熟的语法实现的一种屈从罢了。

  我们要求Base64加密后的文本,其实是文本调用自身的一个方法,之所以我们在之前的方法中需要一个Base64Helper,而不能这样子"http://www.fandongxi.com%22.replace(%22com%22,%22cn/")直接调用,只是因为.NET Framework无法预计到我们所有的业务场景,所以把只能把最通用的方法封装到已有的类库中。

  4. 从扩展方法向外谈一些

  让我们从扩展方法逐渐地向外围来探讨一些关于编码规范,以及一些代码优雅的问题。我们先不妨假设我们并不存在“+”运算符,或者说,我们禁止在程序中使用+运算符,那么也就是说,我们需要对“+”这个操作来做一个简单的封装,那么我们常规意义上会怎么做?


public int Add(int a,int b)
{
return a+b;
}

public static void Main(string[] args)
{
int result = Add(3,4)
Console.WriteLine(result)
}

  让我们来看这个函数,我们顺着代码的意思向下读,加,3,4。这明显是不符合我们常规的数学思维的,如果用了扩展方法之后,我们一定是应该这样来写。


public static class Extension
{
public static int Add(this int a,int b)
{
return a+b;
}
}

public static void Main(string[] args)
{
int a = 3;
a.Add(b)
}

  可是这个"."运算符看上去还是那么有点别扭…..没办法,至少这样读上去让我们的代码顺畅了很多不是么?像写文章,说话一样写代码一直是我们程序员追求的最高境界,就像这样的代码总是好的。

  Good:people.eat(food)

  而不是Bad:Eat(people,food)

  对把!

  5. 前缀,中缀和后缀表达式

  说到这,就不得不谈谈前缀,中缀和后缀表达式了。

  学过数据结构的朋友们,一定都记得在数据结构中,有一道经典的习题,就是利用“栈”来实现前缀,中缀和后缀表达式的转换。在考试题中也经常会出现这样的习题。那现在让我们来复习一下,什么是前缀,中缀和后缀表达式。

前缀表达式就是不含括号的算术表达式,而且它是将运算符写在前面,操作数写在后面的表达式,也称为“波兰式”。

  大名鼎鼎的Lisp就是前缀表达式的典型,让我们看一个最简单的小例子,还是那个经典的斐波那契数列:


(define (fib n)
(fib-iter 1 0 n))

(defile (fib-iter a b count)
(if (= count 0)
b
(fib-iter (+ a b) a (- count 1))))

  每次写Lisp的时候,都会被密密麻麻的括号所吓到,可是真的没什么太好的解决方案呢!

  中缀表达式就很简单了,和我们常规所涉及到的代码是一样的,后缀也是一个道理,在此就不再一一赘述。鉴于后缀的应用不是很大,在此我们也只谈谈前缀和中缀的意义。

  那么我们想想,为什么Lisp要采用这么蹩脚的前缀表达式语法呢?

  记得在大二第一次学习C语言的时候,老师让我们写一个简单的计算器,当时每个同学都写出了+,-,*,/的操作,但是在当时大多数的我们都没有办法写出更为常用的混合运算,以及()的操作,当时只有班上某鹤立鸡群的哥们写出了让我们当时完全无法看懂的代码。再直到大三学习数据结构,再反过来想他当时的代码,才恍然大悟。

  废话说了一堆,那么其实前缀表达式最大的意义就是他更贴近计算机的思维,他只需要两种操作就能完成运算,就是入栈和出栈。让我们来看一个简单的小例子

  3+(1-4),首先这是一个中缀表达式,把他转换为前缀表达式就是+3 – 1 4,计算机会从右向左来扫描这个表达式,4入栈,1入栈,然后遇到 - ,1和4出栈,并且完成运算,(-3)入栈,3进栈,+入栈,(-3)和3出栈,完成运算。

  也就是说,其实在计算机完成我们所编写的数学操作时,其实往往都是把我们的中缀表达式首先转换为前缀表达式,然后完成计算,而Lisp采用前缀表达式,则是省去了这一个步骤,从而提高解释器的效率。

  那我们就来总结下前缀和中缀表达式的意义。

  前缀表达式更加贴近计算机思维,方便计算。而中缀表达式更加贴近数学思维,容易被我们所理解。

  那回顾下,我们之前写Add的代码,如果说我们去掉.运算符,而且方法不加括号,是否采用扩展方法,把C#的语法和Lisp的语法相结合,其实就成了这样的形式。


public int Add(int a,int b)
{
return a+b;
}
public static void Main(string[] args)
{
(set! result (Add a b))
}
public static class Extension
{
public static int Add(this int a,int b)
{
return a+b;
}
}
public static void Main(string[] args)
{
(set! result (a Add b))
}

  还是后者更贴近我们的自然思维一些。

  .NET Framework很强大,给我们提供了扩展方法这个概念,那么如果没有了扩展方法,其他语言给出了怎么样的解决方案呢?

  那让我们来看看Haskell给出的方案。

  5. 看看Haskell的方法

  Haskell是一门函数式的语言,在FP大行其道的今天,Haskell这门久居深宫的语言也渐渐地浮出了水面。

  废话不多说,我们只来看看Haskell是如何在没有扩展方法的情况下来解决语法和自然思维不相协调的问题的。

  让我们先来编写一个简单的Haskell函数。

  add x y = x + y

  代码很简单,没什么值得多说,让我们来看看Haskell怎么调用。

  这是我们传统的调用方式,可是Haskell为了更贴近我们的自然思维,为参数个数数量为2的方法提供了这样一个便捷的调用:

  这就是Haskell为我们提供的“中缀表达式”的解决方案。

  扩展方法很好,但是当我们的语言中没有扩展方法的时候,Haskell给我们提供了一个优秀的典范。

  6. 语言和类库

  说到这,我就想顺便谈谈关于语言扩展和类库扩展的问题。

  在《Masterminds of Programmming》一书中,Python语言之父Guido在接受采访时,谈到PEP(Python增强处理)时,顺便说到了关于在编写编程语言时,如何来根据用户的意见来处理语言实现的问题。

  他谈到:

如果某个用户提出一个新特性,它几乎不会成功。因为用户对实现没有全面的理解,他几乎不可能提出一个合理的新特性。

  那么在我看来什么是用户?用户就是使用这门语言来完成工作任务的人,他们往往需要的都是增加一个新功能,换句话说,他们需要的仅仅是一个方法而已。

  那么什么是增加语言特性,什么是增加类库方法,Guido也给出了比较合理的解释。

如果某个特性对于Web来说确实很棒,那么,对于加到语言中来说,就未必是优秀的特性了。如果它确实利于编写更短的函数,或者是有利于编写可维护更强的类,把它添加到语言中可能就是一件好事。

  其实Guido的意思很简单,是否增加到语言中,关于在于这个特性是否是领域相关的,如果是领域相关的,也许它需要做的仅仅是扩展类库,无论是增加Python的类库,还是用C去扩展Python API,总之无需对语言做出改变。

  那么对于C#来说,什么是类库的修改,什么是语言的修改,在我看来,每一个版本的修改都一定有着类库的修改,但是如果说到语言的修改,应该是仅仅当MSIL发生变动的时候,我们才可以说语言发生了修改。//仔细想了一下,这个观点有问题....但是我没找到更合适的语言来做比喻。也许应该说,只有当语法的编译规则发生改变的时候,我们才可以说语言发生了修改。

  Python也是一样,增加了方法充其量是类库的修改,而仅仅是语言的解释过程都发生了修改才可以算得上是语言层面的修改,例如从Python 2.x到Python3.x的大版本变动。

  7. 总结

  在本文中,主要是从扩展方法说起,谈到我们该怎么样更好的编写更贴近自然语言的程序。

  然后再到一些没有扩展方法语言给出的折衷实现。而对于Python,C等其他语言,我尚且没有找到合适的方法来解决问题。

  如果各位有好的办法,尤其是对于Python,毕竟这是我的工作,希望各位补充给出解决方法。

  谢谢。

时间: 2024-07-30 09:59:34

一起谈.NET技术,由扩展方法引申出的编程思维的相关文章

一起谈.NET技术,数组排序方法的性能比较(3):LINQ排序实现分析

上次我们分析了Array.Sort方法的实现方式,并了解到类库会为一些特例而使用高性能的排序方式--int数组便是这样一例,因此从测试结果上来看其性能特别高.不过从数据上看,即便是在普通的情况下,Array.Sort的性能也比LINQ排序要高.不过也有朋友从测试中得出的结论正好相反,这又是为什么呢?那么现在,我们再来分析一下LINQ排序的实现方式吧,希望这样可以了解到两者性能差别的秘密. 只可惜,LINQ排序的代码在System.Core.dll程序集中,微软没有发布这部分源代码,我们只得使用.

由扩展方法引“.NET研究”申出的编程思维

1. Helper大爆炸上海闵行企业网站设计与制作g> .NET Framework为我们提供了丰富的类库,但是这并不是万能地,在大部分的时间,我们都需要为我们的项目特殊定制我们的通用类库. 常常,我们都可以构造一个类,类里封装一些方法.但是对于很多时候,我们并没有办法提取出这样一个类,举一个小例子,我们在很多时候,需要把url给保存到数据库里,作为一个唯一标识,但是我们知道url所占空间很大,如果用url来建立索引的话是非常耗费空间,而且影响效率的,那么我们最常用的办法就是把url做一个Has

一起谈.NET技术,数组排序方法的性能比较(中):Array.Sort<T> 实现分析

昨天我们比较了Array.Sort方法与LINQ排序的性能,知道了LINQ排序的性能以较大幅度落后于Array.Sort方法.而对于Array.Sort来说,性能最高的是其中使用Comparer.Default作为比较器的重载方法.在前文的末尾我们做出了推测:由于排序算法已经近乎一个标准了(快速排序),因此从算法角度来说,Array.Sort方法和LINQ排序上不应该有那么大的差距,因此造成两者性能差异的原因,应该是具体实现方式上的问题. 下载.NET框架的代码 既然是比较实现的区别,那么阅读代

《圣殿祭司的ASP.NET4.0专家技术手册》----2-12 扩展方法

2-12 扩展方法 圣殿祭司的ASP.NET4.0专家技术手册 扩展方法(Extension Methods)允许针对现有类型加入自定义方法,而不必用传统方式,先继承然后再实现方法,最后还要再进行编译,完全省略了这些不必要的步骤. 然而,什么时候需使用到扩展方法?通常有两个时机: (1)需扩展类型额外的方法,就可通过扩展方法加入额外的方法: (2)希望直接使用系统已建立好的扩展方法,这种情况在LINQ中尤其明显. 范例2-12 使用扩展方法扩展string类型方法 假设要将阿拉伯数字"2266&

一起谈.NET技术,从扩展方法到流畅的程序体验(一)

今天让公司的程序员试用了一下还在开发中的代号为"Jumony"的HTML数据绑定引擎,开发人员的一句评价被我视为最高的褒奖. "感觉这个框架就是,你想到什么就写什么." 想到什么就写什么,在这个越来越强调快速开发的时代,这一点变得越来越重要.我最近经常戏言:"natural code才是王道",当然,不是说我们要用中文去编程,而是程序应该成为越来越自然的表达. 让程序员获得流畅的编程体验,是将来每一个框架都必须去考虑和实现的事情.随着.NET F

再谈反病毒领域的工程化技术与科学方法的结合

问题描述 再谈反病毒领域的工程化技术与科学方法的结合--一次谈话记录的整理seak(在某高校与信安专业教师交流某次大学生信安大赛作品情况.和反病毒领域的人才培养等,当时讲的可能是语无伦次,感谢同事Emma进行录音整理.因部分内容东拉西扯跑题了有删节今天还是想谈谈工程经验和学术方法的结合问题,既然大家谈到了内容挖掘,我们就从内容挖掘开始.基于内容挖掘的思想,我印象中较早是从上世纪哥伦比亚大学的相关研究开始的,从其样本集来看基本是DOS下的COM文件,其中很大比例的样本都是用汇编语言编写的.COM文

一起谈.NET技术,从.NET中委托写法的演变谈开去(上):委托与匿名方法

在<关于最近面试的一点感想>一文中,Michael同学谈到他在面试时询问对方"delegate在.net framework1.1,2.0,3.5各可以怎么写"这个问题.于是乎,有朋友回复道"请问楼主,茴香豆的茴有几种写法","当代孔乙己",独乐,众乐.看了所有的评论,除了某些朋友认为"的确不该不知道这个问题"之外,似乎没有什么人在明确支持楼主. 不过我支持,为什么?因为我也提过出这样的问题. 这样,我们暂且不提应

一起谈.NET技术,谈谈我处理异常的一般方法

我们在编写程序的时候会遇到各种各样的意外情况,如除数为0,数组越界,非法转型,栈溢出等等.因而我们需要有一种机制来处理这些情况,异常处理就是其中的一种机制.当然,还有其他的机制,在MFC中,由于标准的不统一,就存在着各种错误报告方法,如有通过函数返回特殊值的方式,有通过执行某一语句后查询特殊语句获取错误的码的方式,等等. 在C#中,只有一种报告方式,即异常.这样可以让开发人员从大量的文档中解脱出来,不必为一些非逻辑的问题而花费大量时间. C#中的异常不同于C++,所有的异常类型都是继承自Syst

Linux后门技术及实践方法谈完整版

以下的文章主要描述的是Linux后门技术及实践方法,假如你在实际操作中遇到Linux后门技术, 但是你却不知道对Linux后门技术如何正确的解决, 那么以下的文章对你而言一定是良师益友.以下就是文章的主要内容描述.后门简介入侵者完全控制系统后,为方便下次进入而采用的一种技术.一般通过修改 系统配置文件和安装第三方后门工具来实现. 具有隐蔽性,能绕开系统日志,不易被系统管理员发现等特点.Linux后门技术增加超级用户账号破解/嗅探用户密码放置SUID Shellrhosts + +利用系统服务程序