由扩展方法引申出的编程思维

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怎么调用。

image

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

image

这就是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,毕竟这是我的工作,希望各位补充给出解决方法。

谢谢。

posted @ 2011-03-17 06:45 飞林沙 阅读(4777) 评论(57) 编辑 收藏

 回复 引用 查看   
#1楼 2011-03-17 08:04 海洋之 心      
沙发
 回复 引用 查看   
#2楼 2011-03-17 08:28 axgle      
class 中国人{
static count{ return 中国人有13亿;}

}
axgle =new 中国人();
axgle.say(中国人.count());
"axgle is 中国人,中国人有13亿,所以axgle有13亿."
类本身是一个对象,这个对象也是有属性和方法的.这是类对象不同于类的instance对象.

 回复 引用 查看   
#3楼 2011-03-17 08:28 鹤冲天      
哈哈,有时间看看我的 c#扩展方法奇思妙用 系列文章
 回复 引用 查看   
#4楼 2011-03-17 08:39 什么你说什么      

 回复 引用 查看   
#5楼 2011-03-17 08:47 初始小花      
静态方法和扩展方法各有自己的用途,扩展方法只是实例方法,是无法完全取代静态方法的。
 回复 引用 查看   
#6楼 2011-03-17 09:29 Ivony...      
Base64本身不是压缩。。。

其次也不建议对弱类型(string)之类来做过多的扩展。譬如说url应该针对Uri进行扩展。。。。


我一年多前就说了扩展方法就是实现了函数的中缀表达式,结果一群人觉得放在中间没啥稀奇,一天到晚说扩展方法如何在LINQ中大放光芒之类。

 回复 引用 查看   
#7楼 2011-03-17 09:45 Jeffrey Zhao      
引用但是如果说到语言的修改,应该是仅仅当MSIL发生变动的时候,我们才可以说语言发生了修改。

为什么?按照这个说法,C#只有1.0到2.0才是改变,往后就没有变化咯?

 回复 引用 查看   
#8楼[楼主] 2011-03-17 09:46 飞林沙      
@初始小花

要说完全替代确实有难度,但是我还是认为应该尽力避免静态方法的使用。其实想想静态方法在项目中的应用场景,大部分都这样XXHelper,XXTools这样的类中的。
否则就是类的设计不合理了

 回复 引用 查看   
#9楼[楼主] 2011-03-17 10:03 飞林沙      
@Jeffrey Zhao
引用Jeffrey Zhao:
引用但是如果说到语言的修改,应该是仅仅当MSIL发生变动的时候,我们才可以说语言发生了修改。

为什么?按照这个说法,C#只有1.0到2.0才是改变,往后就没有变化咯?


因为我是觉得对于这种基于VM的语言来说,无论是C#还是Java,他们的顺序从低到高都是字节码(IL)-->C#(Java)(这里我的意思是语言实现)-->基于C#(Java)实现的类库。
如果IL不发生变化,就拿LINQ来说吧,他做的不过是在编译时改了一个规则。
不过我仔细想了下,其实这么说好像也没什么道理,因为如果按这么说的话,那一样可以说,IL的改变不过是再解释成机器码的时候改了一下规则....

我的这句话确实有问题....

 回复 引用 查看   
#10楼 2011-03-17 10:09 Jeffrey Zhao      
@飞林沙
呵呵。肯定有问题啊。说的夸张点,难道C#未来某个版本进化成VB样子了,Java某个版本进化成Scala样子了,难道他们算没有变化吗?
而且,谁说C#只能编译成MSIL啊,它也可以编译成Java Bytecode和Native Code或LLVM哪,C#的改变和它编译后的目标代码应该没什么关系的。
所谓语言的改变,其实就是表面的那些变化,而不是内在变化。就好比Python语言,无论是PyPy还是IronPython和Jython实现,它“内在”变了,但语言反而应该说没有变。

 回复 引用 查看   
#11楼 2011-03-17 10:12 横刀天笑      
@飞林沙
不是有问题,是有太大问题了。
不管语言从低到高,还是开发方法从面向过程到面向对象等,都是因为问题规模的不断增长,我们需要提高抽象的层次,在更高层次上来解决问题而来的,而这些对于更广大的应用程序员来说是无比重要的,甚至比你所说的低层修改来得更重要。

 回复 引用 查看   
#12楼 2011-03-17 10:15 Jeffrey Zhao      
引用Python也是一样,增加了方法充其量是类库的修改,而仅仅是语言的解释过程都发生了修改才可以算得上是语言层面的修改,例如从Python 2.x到Python3.x的大版本变动。

这句话和你刚才说的又有矛盾了,它们的内在完全没有变化嘛,只是如C# 2.0到3.0那样语法上的改变,也就是所谓LINQ“不过是在编译时改了一个规则”。

 回复 引用 查看   
#13楼 2011-03-17 10:28 刀 刀      
引用Ivony...:
Base64本身不是压缩。。。
其次也不建议对弱类型(string)之类来做过多的扩展。譬如说url应该针对Uri进行扩展。。。。
我一年多前就说了扩展方法就是实现了函数的中缀表达式,结果一群人觉得放在中间没啥稀奇,一天到晚说扩展方法如何在LINQ中大放光芒之类。

“弱类型(string)“是啥意思?你是想说“不建议对顺从类型(compliant type)做过多扩展“? 那完全同意。

 回复 引用 查看   
#14楼 2011-03-17 10:29 Jeffrey Zhao      
@刀 刀
Ivony的意思应该说是“十分通用”的类型吧……

 回复 引用 查看   
#15楼 2011-03-17 10:32 刀 刀      
@Jeffrey Zhao
大概是说CLS compliant type?

 回复 引用 查看   
#16楼 2011-03-17 10:34 Jeffrey Zhao      
@刀 刀
Uri不也是CLS-compliant的嘛。

 回复 引用 查看   
#17楼 2011-03-17 10:37 刀 刀      
@Jeffrey Zhao
那还是按你说的,叫“十分通用的类型”吧……

 回复 引用 查看   
#18楼 2011-03-17 10:54 横刀天笑      
@刀 刀
就是非常通用,使用非常频繁的常见类型,没有自己特定的含义。这样“污染面”非常大。

 回复 引用 查看   
#19楼 2011-03-17 10:59 刀 刀      
@横刀天笑
是的,VB.NET干脆不支持对Object类型扩展,个人认为就是为了防止大面积“污染”。

 回复 引用 查看   
#20楼 2011-03-17 11:41 Ivony...      
引用刀 刀:
@横刀天笑
是的,VB.NET干脆不支持对Object类型扩展,个人认为就是为了防止大面积“污染”。



我还真就对object进行了扩展,为的是改善C#糟糕的强制类型转换语法:

((XXX) obj).XXX
=>
obj.CastTo<XXX>().XXX

 回复 引用 查看   
#21楼 2011-03-17 12:15 天行健 自强不息      
长见识了,顶
 回复 引用 查看   
#22楼 2011-03-17 12:16 Virus-BeautyCode      
研究的还真是广泛啊,支持
 回复 引用 查看   
#23楼 2011-03-17 12:54 咸蛋哥哥      
学习~~~
 回复 引用 查看   
#24楼 2011-03-17 18:00 gws      
感觉容易误导别人,不知道的还以为那个方法是.net中string的。
 回复 引用 查看   
#25楼[楼主] 2011-03-17 18:02 飞林沙      
@gws

您指的是哪个方法容易误导?

 回复 引用 查看   
#26楼 2011-03-17 18:08 嘿逗      
我最喜欢大面积污染啦,哈哈!
        public static DataSet Select(this string selectStr, string connectionStr)
        {
            DataSet ds = new DataSet("data");
            SqlDataAdapter da = new SqlDataAdapter(selectStr, connectionStr);
            da.Fill(ds, "info");
            return ds;
        }

        public static DataSet Select(this string selectStr)
        {
            return selectStr.Select(@"Data Source = WHH ...");
        }

        public static void Main()
        {
            var userList = "select * from [User_Data] where UserType = 1".Select(@"Data Source = WHH ...");
            var newsList = "select * from [News_Data] where NewsType = 1".Select();
        }

 回复 引用 查看   
#27楼[楼主] 2011-03-17 18:45 飞林沙      
@嘿逗

这个就不太好了,我觉得如果这样更好应该是封装成一个QueryObject

class QueryObject
{
private string sql;
public QueryObject(string sql){this.sql = sql}
public DataSet Execute(){...}
}
这样是不是更合适些呢

其实我的例子也不太好,就像Ivony说的,我的那个也更适合放到url的扩展方法里。

 回复 引用 查看   
#28楼 2011-03-17 19:57 Jeff Wong      
顶,学习了。
我曾经天真地以为扩展方法和弱类型(var)是微软为了让.net平台的静态强类型语言”动态化“而做的妥协。比如当我看到javascript里的prototype方法扩展时,就会条件反射地想到c#的扩展方法,其实它们毫无关系。

虽然vb不允许对object 扩展 可是对<T>扩展不还是一样
发一个我家的大面积污染

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.Reflection;

namespace Spits.Common.Extensions
{




	public class TryExecuteResult<TContext,TResult>
	{

		public TryExecuteResult(TContext context)
			: this(context, default(TResult), null)
		{

		}

		public TryExecuteResult(TContext context, Exception exception)
			: this(context, default(TResult), exception)
		{

		}

		public TryExecuteResult(TContext context, TResult result)
			: this(context, result, null)
		{


		}

		public TryExecuteResult(TContext context, TResult result, Exception exception)
		{
			TryContext = context;
			TryResult = result;
			Exception = exception;
			HasException = (exception == null);

		}


		public bool HasException { get; private set; }

		public Exception Exception { get; private set; }

		public TContext TryContext { get; private set; }
		public TResult TryResult { get; private set; }
	}
	
   
	public class TryDoResult<TContext>
	{

		public TryDoResult(TContext context)
			: this(context, null)
		{

		}



		public TryDoResult(TContext context, Exception exception)
		{
			TryContext = context;
			
			Exception = exception;
			HasException = (exception == null);

		}


		public bool HasException { get; private set; }

		public Exception Exception { get; private set; }

		public TContext TryContext { get; private set; }

	}
	static public class ObjectExtensions
	{
       


		public static T Do<T>(this T source, Action<T> action)
		{
			Contract.Requires(action != null);


			action(source);
			return source;



		}

		public static TTarget CastAs< TTarget>(this object source)
		{

			return (TTarget)source;            

		}

		public static TryExecuteResult <Object ,TTarget > TryCastAs<TTarget>(this object source)
		{

			return source.Try(s => s.CastAs<TTarget>());
		}




		public static IEnumerable<T> LazyForEach<T>(this IEnumerable<T> sources, Action<T> action)
		{
			Contract.Requires(sources != null);
			Contract.Requires(action != null);

			return sources.Select(itm => itm.Do(action));
		}
        public static List<T> ForEach<T>(this IEnumerable<T> sources, Action<T> action)
        {
            Contract.Requires(sources != null);
            Contract.Requires(action != null);
            return  sources.LazyForEach(action).ToList();
       
        }
		public static IEnumerable<TryDoResult<T>> LazyTryEach<T>(this IEnumerable<T> sources, Action<T> action)
		{
			Contract.Requires(sources != null);
			Contract.Requires(action != null);

			return sources.Select(itm => itm.Try(action));
		}
        public static List<TryDoResult<T>> TryEach<T>(this IEnumerable<T> sources, Action<T> action)
        {
            Contract.Requires(sources != null);
            Contract.Requires(action != null);
            return sources.Select(itm => itm.Try(action)).ToList (); 
        }




		public static TryDoResult<T> Try<T>(this T source, Action<T> action)
		{
			Contract.Requires(source != null);
			Contract.Requires(action != null);
  
			try
			{
				action(source);

				return new TryDoResult<T>(source);
			}
			catch (Exception ex)
			{
				return new TryDoResult<T>(source, ex);
			}





		}


		public static TryExecuteResult<TContext,TResult> Try<TContext, TResult>(this TContext source, Func<TContext,TResult> func)
		{
			Contract.Requires(source != null);
			Contract.Requires(func != null);

			try
			{
				var rval = func (source);

				return new TryExecuteResult<TContext,TResult>(source ,rval);
			}
			catch (Exception ex)
			{
				return new TryExecuteResult<TContext,TResult>(source, ex);
			}





		}
	}
}


 回复 引用 查看   
#31楼 2011-03-18 08:52 嘿逗      
引用飞林沙:
@嘿逗

这个就不太好了,我觉得如果这样更好应该是封装成一个QueryObject

class QueryObject
{
private string sql;
public QueryObject(string sql){this.sql = sql}
public DataSet Execute(){...}
}
这样是不是更合适些呢

其实我的例子也不太好,就像Ivony说的,我的那个也更适合放到url的扩展方法里。

我那个是看你博文之后临时写的,不过感觉起来还满酷的,可以专门为数据库创建个项目的时候用到。

引用嘿逗:
引用飞林沙:
@嘿逗

这个就不太好了,我觉得如果这样更好应该是封装成一个QueryObject

class QueryObject
{
private string sql;
public QueryObject(string sql){this.sql = sql}
public DataSet Execute(){...}
}
这样是不是更合适些呢

其实我的例子也不太好,就像Ivony说的,我的那个也更适合放到url的扩展方法里。

我那个是看你博文之后临时写的,不过感觉起来还满酷的,可以专门为数据库创建个项目的时候...

我不觉得大面积污染有什么错,linq污染了所有迭代器已经麻木了
只要谓词清晰,xml注释写得到位,大面积污染没啥大不了的

引用Jeff Wong:
顶,学习了。
我曾经天真地以为扩展方法和弱类型(var)是微软为了让.net平台的静态强类型语言”动态化“而做的妥协。比如当我看到javascript里的prototype方法扩展时,就会条件反射地想到c#的扩展方法,其实它们毫无关系。

var 不是弱类型,应该更偏向于被动类型。
原来的英文也只是变量的意思。


 回复 引用 查看   
#34楼 2011-03-18 09:25 嘿逗      
引用韦恩卑鄙 v-zhewg @waynebaby:
我不觉得大面积污染有什么错,linq污染了所有迭代器已经麻木了
只要谓词清晰,xml注释写得到位,大面积污染没啥大不了的

对,像
object.ToJSONString()
IEnumerable<T>.ToDataSet()
这样经常用到的扩展一下也没啥,不过一般我都封装在某个专门用来扩展的类库里,或者把访问限制在程序集内。

@嘿逗
我个人的经验看
对于扩展方法管理有专门的命名空间是很必要的。
单独的类库看需要吧

 回复 引用 查看   
#36楼 2011-03-18 09:33 嘿逗      
引用韦恩卑鄙 v-zhewg @waynebaby:
@嘿逗
我个人的经验看
对于扩展方法管理有专门的命名空间是很必要的。
单独的类库看需要吧

如果放到单独的命名空间里,如果不改代码模板的话,开发人员使用的时候还需要引入命名空间,对他们来说太“麻烦”了。
我最喜欢懒程序员了,一般都把扩展方法和被扩展的接口放在同一个命名空间下。
不过像一些太大规模的污染,例如object,我有时考虑在某个[接口]上扩展,就像linq一样,其实也是扩展到接口。

@嘿逗
懒了自然会乱嘻嘻

 回复 引用 查看   
#38楼 2011-03-18 10:13 徐少侠      
嗯,加个引用都懒就不好了哦。
支持扩展到接口

 回复 引用 查看   
#39楼 2011-03-18 10:26 Ivony...      
我想,这样描述可能更为准确:

不建议对语焉不明的类型进行特定领域的的扩展。

什么叫做语焉不明?就是从类型我们无法得知其用途、特性,或者在多个不相干的场景呈现截然不同的语义的类型。

譬如说string,我们都知道string是一个字符串,但字符串可以是url、ip地址、名字、单位、货币代码、提示信息。

什么叫做特定领域的扩展,就是针对语焉不明类型的某个特有的领域和场景作出的扩展。

譬如说我们对string进行这样的扩展是合理的:
IsWhitespace( this string )

但是这样的就显然不合理:
GetDomain( this string )

 回复 引用 查看   
#40楼 2011-03-18 10:31 Ivony...      
那个Select的扩展,不仅容易与LINQ的Select产生误解,而且也没省多少事儿。

用我几年前的DbUtility:

new SqlDbUtility( connectionString ).ExecuteData( "SELECT XXX" );

不见得复杂多少。。。。

而且数据库连接字符串一般是共用配置的,所以事实上实际项目中,还不如:

var db = new SqlDbUtility( connectionString )

db.ExecuteData( "XXX" );
db.ExecuteData( "XXX" );
db.ExecuteNonQuery( "XXX" );

 回复 引用 查看   
#41楼[楼主] 2011-03-18 10:36 飞林沙      
@韦恩卑鄙 v-zhewg @waynebaby
引用韦恩卑鄙 v-zhewg @waynebaby:
@嘿逗
我个人的经验看
对于扩展方法管理有专门的命名空间是很必要的。
单独的类库看需要吧


我一般喜欢针对某个具体的类库加上Extension,比如Cnblogs.Extension.StringExtension,需要的时候就引入
using Cnblogs.Extension.StringExtension

 回复 引用 查看   
#42楼 2011-03-18 11:25 gws      
呵呵,我说的是GetHashCode(string)这个方法。如果是多年.net经验的有可能可以看出来,要是来个初学者或者以后的维护的人,对于你这个代码,不知道的还以为就是object的GetHashCode,也就是大家提到的"污染"吧。
 回复 引用 查看   
#43楼[楼主] 2011-03-18 12:44 飞林沙      
@gws

恩,我举得这个例子确实有问题。
如Ivony所说,这个扩展方法放到UrlExtension里更合适

 回复 引用 查看   
#44楼 2011-03-18 13:53 Icebird      
Base64属于字符编码转换,与压缩无关
 回复 引用 查看   
#45楼[楼主] 2011-03-18 14:25 飞林沙      
@Ivony...
@Icebird

引用Icebird:Base64属于字符编码转换,与压缩无关


恩,修改过了,谢谢指正

引用Ivony...:
我想,这样描述可能更为准确:

不建议对语焉不明的类型进行特定领域的的扩展。

什么叫做语焉不明?就是从类型我们无法得知其用途、特性,或者在多个不相干的场景呈现截然不同的语义的类型。

譬如说string,我们都知道string是一个字符串,但字符串可以是url、ip地址、名字、单位、货币代码、提示信息。

什么叫做特定领域的扩展,就是针对语焉不明类型的某个特有的领域和场景作出的扩展。

譬如说我们对string进行这样的扩展是合理的:
IsWhitespace( this string )

但是这样的就显然不合理:
GetDomain( th...

我觉得只需要对命名空间进行管理就好
比如 MyLib.Extensions.Urls空间里面的扩展方法 扩展到string也无所谓
因为你不是工作在密集的url 与字符串之间的上下文中 你压根不会using这个命名空间。

前面的兄弟说这样是个治不了懒的办法。

我觉得只需要我们有一些约束就好了

比如我要扩展的string, 无论在哪个命名空间 我们都把这个静态累起名叫做 public static class StringExtenstions
这样我们要扩展String的时候 只需要打
StringEntensions 然后按 alt +shift +f10
vs就会弹出所有可能的命名空间 让你选择。
不过这样要先防止两个命名空间有完全一样签名的函数。


@Ivony...
>不建议对语焉不明的类型进行特定领域的的扩展。

这是一个很严厉的规定哦,
相当于"语焉不明类型不可以做静态函数的第一个参数"一样严厉。

既然静态函数一般是可以根据其所在静态类来进行管理,"语焉不明类型不可以做静态函数的第一个参数"这个规则可以不必执行,
“不建议对语焉不明的类型进行特定领域的的扩展。”这个严厉规定应该也可以有办法处理吧。

 回复 引用 查看   
#48楼[楼主] 2011-03-18 16:59 飞林沙      
@韦恩卑鄙 v-zhewg @waynebaby
引用韦恩卑鄙 v-zhewg @waynebaby:
@Ivony...
>不建议对语焉不明的类型进行特定领域的的扩展。

这是一个很严厉的规定哦,
相当于"语焉不明类型不可以做静态函数的第一个参数"一样严厉。

既然静态函数一般是可以根据其所在静态类来进行管理,"语焉不明类型不可以做静态函数的第一个参数"这个规则可以不必执行,
“不建议对语焉不明的类型进行特定领域的的扩展。”这个严厉规定应该也可以有办法处理吧。


Ivony的目的应该还是要尽可能地避免不必要的污染,也就是说这个我写了一个扩展方法的库,就算有一天,我从做互联网的A公司离职了,到了一个做ERP的B公司,我的这些扩展类库一样可以用。
我觉得这个有两个解决办法:
第一,我可以针对基本对象做一层简单的封装,假设说.NET Framework没有Url这个类,那我就写一个
class Url{private string url}
然后这样一样可以避免Helper的泛滥,然后也一样可以实现中缀表达式的目的。

第二,就是说用命名空间来就觉问题,就算我对String做扩展方法,我一样可以把他放到UrlExtension里,这样,只需要引入合适的命名空间,来达到目的。

 回复 引用 查看   
#49楼 2011-03-18 17:20 横刀天笑      
@Ivony...
我在想是不是可以这么来看这个问题
我们不应该对一个非常通用的类加一个与某个具体领域相关的扩展方法。
比如,我们不应该给string扩展GetDomain方法。如果你真的要这么做,那就表明我们的系统中缺少一个描述这个领域的对象:Url(假设框架中没有Url类),那我们应该创建这么一个类来描述出这个领域对象,然后在这个类里添加这个方法,而不是给string添加一个GetDomain方法来草草了事。

 回复 引用 查看   
#50楼[楼主] 2011-03-18 17:24 飞林沙      
@横刀天笑
引用横刀天笑:
@Ivony...
我在想是不是可以这么来看这个问题
我们不应该对一个非常通用的类加一个与某个具体领域相关的扩展方法。
比如,我们不应该给string扩展GetDomain方法。如果你真的要这么做,那就表明我们的系统中缺少一个描述这个领域的对象:Url(假设框架中没有Url类),那我们应该创建这么一个类来描述出这个领域对象,然后在这个类里添加这个方法,而不是给string添加一个GetDomain方法来草草了事。


我觉得这是一种方法,当然我觉得用我之前说的第二种方法也未尝不可。
反正都能达到避免污染的目的。

引用第二,就是说用命名空间来就觉问题,就算我对String做扩展方法,我一样可以把他放到UrlExtension里,这样,只需要引入合适的命名空间,来达到目的。

 回复 引用 查看   
#51楼 2011-03-19 07:27 我想我是风      
引用刀 刀:
@横刀天笑
是的,VB.NET干脆不支持对Object类型扩展,个人认为就是为了防止大面积“污染”。


哪儿有介绍vb.net不支持object的扩展?我专门去试了,完全没有问题呀

 回复 引用 查看   
#52楼 2011-03-19 09:56 刀 刀      
引用我想我是风:
引用刀 刀:
@横刀天笑
是的,VB.NET干脆不支持对Object类型扩展,个人认为就是为了防止大面积“污染”。

哪儿有介绍vb.net不支持object的扩展?我专门去试了,完全没有问题呀

我不是做VB.NET的,但是我相信我没记错http://blogs.msdn.com/b/vbteam/archive/2007/01/24/extension-methods-and-late-binding-extension-methods-part-4.aspx

 回复 引用 查看   
#53楼 2011-03-19 19:18 我想我是风      
看的不是很明白,不过这个主要还是讲后绑定的问题,扩展方法是可以用object做第一个参数的,不过引用它的实例变量也是一个object,提示不能后绑定,这个vb.net本身也有Option Strict选项可以用呀
 回复 引用 查看   
#54楼 2011-03-19 23:10 Ivony...      
引用横刀天笑:
@Ivony...
我在想是不是可以这么来看这个问题
我们不应该对一个非常通用的类加一个与某个具体领域相关的扩展方法。
比如,我们不应该给string扩展GetDomain方法。如果你真的要这么做,那就表明我们的系统中缺少一个描述这个领域的对象:Url(假设框架中没有Url类),那我们应该创建这么一个类来描述出这个领域对象,然后在这个类里添加这个方法,而不是给string添加一个GetDomain方法来草草了事。



是这样,总归来说,明确的类型不仅仅是语义的要求,也是避免出错的好办法。

命名空间并不总是能有效的隔离不同的扩展方法,当同名时,将会造成灾难,你会希望从来没有出现过这个语言特性才好,C#的“更佳转换”重载规则虽然非常明确和符合人们的预期,但是如果多了一个using就会改变执行结果一定会造成很多困扰。

当然,有些人会觉得new Uri( url ).Host仍然比较麻烦,但事实上扩展方法也可以改善这一点,譬如说:

url.AsUri().Host

如果有合适的命名空间,我们还是可以将这些AsXXX控制在一个不至于让人眼花的范围。

再拿上面的例子来说,如果我们希望通过一个数据库链接字符串来执行一段SQL语句,也许这是一个不错的选择:

"data source=.\SQLEXPRESS;".AsConnectionString().ExecuteData( "SELECT * FROM tablename" );

又比如:

"Ivony".AsStream( Encoding.UTF8 ).Zip();

 回复 引用 查看   
#55楼 2011-03-19 23:21 横刀天笑      
@Ivony...
明白你的想法了
你的这个AsXXX的扩展方法的使用挺好的~~
As了之后有了目标,然后再在目标上进行扩展,突出了领域的概念,也解决了“随便”扩展造成混乱的问题。

 回复 引用 查看   
#56楼 2011-04-27 14:35 take it and go      
扩展这个注意不错
 回复 引用 查看   
#57楼 2011-05-05 19:32 banana.totolv      
good
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

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

0 1986643 hdQXNsBQpXE=