.NET,你忘记了么?(八)—— 从dynamic到特性误用 [转]

时间:2022-01-16 15:47:56

1. 摘要

每个程序员都想写出漂亮的代码,但是什么是漂亮,这个我想每个人都有着自己的看法。那么我就说几种典型的想法:

A. 写出别人看不懂的代码,让别人觉得很高深。

B. 写出简短的代码

C. 用最新的语言特性写出代码

这个我不发表评论,毕竟每个人有着自己的观点,我也不能证明自己的就是对的。但是在这里,我想说一些典型的误用。

2. 从dynamic谈起

作为C#4.0的更新之一,dynamic已经越来越被推到了很多技术论坛的第一线。我看了很多关于dynamic的讲解,但是我还是我一贯的观点。既然我们用的微软的东西,那么我们在使用一个语言特性的同时,我们首先要弄清微软为什么要推出这门语言,不要盲目去使用。这样往往会适得其反。

那下面我就看大多数教程中的一个传统代码:

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
dynamic p = new People("Test");
Console.WriteLine(p.GetName());
}
}
class People
{
private string name;
public string GetName()
{
return name;
}
public People(string name)
{
this.name = name;
}
}
}

但是正如很多评论中的质疑的一样,这样的操作究竟有什么作用?(我并不是怀疑很多作者不懂这个,而是说这个会误导很多人)于是很多人就会不明不白地去跟风,去乱用dynamic。

那么我们就在这里说清,究竟为何dynamic。

3. dynamic和var

在说这两个关键字之前,我们要先搞清楚两个概念。什么叫强类型语言,什么叫弱类型语言。

一句经典的话我想最能解释他们的区别了:static typing when possible,dynamic typing when needed.

其实也就是说:静态语言是在编译时确定类型,而弱类型是在运行时确定类型。

一个简单的例子就能看出他们的区别:

首先是使用var的情况:

.NET,你忘记了么?(八)—— 从dynamic到特性误用 [转]

接下来是dynamic:

.NET,你忘记了么?(八)—— 从dynamic到特性误用 [转]

4. 究竟为何dynamic

在国外某博客中,我记得有这样一个说法,是说dynamic会颠覆传统的C#编程方式,从前说世间万物皆为对象,那么现在世间对象皆为dynamic。

class People
{
private dynamic name;
public People(dynamic name)
{
this.name = name;
}
public dynamic Introduce()
{
dynamic s = "Hello,I am" + name;
return s;
}
public delegate dynamic Notify(dynamic argument);
}

不过,就我个人而言,并不认同这种说法,已经有很多《“深入剖析”dynamic》之类的文章了,我就不在多写了。总之dynamic会对效率产生很大的影响。如果这样滥用dynamic:

A. 对程序的效率有很大影响

B. Visual Studio 强大的智能感知功能被完全废弃了。

既然这样,那么我们为什么要使用dynamic,就我的理解而言:

A. 语言的互操作,比如去调用C++的一个Com组件,我们完全可以用dynamic取代反射略显复杂的语法。

B. 我们都知道var只能用于变量,而无法用于属性,而我们使用var的情况往往是因为我们不大容易确定某一个变量(或者属性)的类型,同样,很可能出现一个类的属性或者方法返回类型不易确定返回类型的情况,这个时候,我们就可以用dynamic了。比如:

public dynamic GetAllGrilFriendsDetails()
{
var gfDetails = from o in db.People
where o.name = this.name
select new
{
Name = o.firstName + o.lastName,
Age = o.age,
Address = o.address
};
return gfDetails;
}

为什么我在方法内部去用 dynamic gfDetails,如果你讨厌去看IL代码细节,那么我们只看由于dynamic产生的反编译C#代码数量也许就能吓到你了:

.NET,你忘记了么?(八)—— 从dynamic到特性误用 [转]

5. 从误用继续说开去

任何一种事物永远都是双面性的,同样,任何一种新鲜事物的产生总是会有着他的利和他的弊。究竟是利还是弊,其本质原因不在于他本身,而在于他周围的环境对他的使用是利大于弊,还是利小于弊。

任何一个C#新语言特性也是亦然。而他周围的环境就是我们程序员。

我看到过太多太多的误用,比如对泛型的误用,对委托的误用,对扩展方法的误用。

在这里就再谈谈扩展方法的误用。

6. 何时扩展方法

我在<Javascript玩转Prototype(二) >中提过Prototype的缺点,在这里我只说一点:能够动态地添加属性和方法固然是增加了灵活性。可是我们讨论一种情况,100个人同时来开发一个Javascript的项目,很多没经验的人爱上了玩转prototype,一个人往这个类里加一个方法,还面向对象么?

扩展方法也是一样,100个开发者同时去开发一个项目,每个人都写一个扩展方法,那么这个项目会乱成什么样大家可想而知。

那么什么时候该用扩展方法,我个人认为只有三种情况:

A. 你独立负责一个组件的编写,而这个组件需要调用其他组件中的类,而你常常需要用到某个类中的某个他并为提供的“方法”。那么这个时候,你可以在你的组件内特殊放置一个类,用来容纳你所需的扩展方法。

B. 一个团队面对的一个已经封装好的组件,但是某个方法是这个组件没有提供的,重写组件实在麻烦,那好吧。扩展方法。

C. 其实这个与第二点有些相似,当你面对的是.NET Framework中提供的类库,那么没办法,只能扩展方法。

posted @ 2009-05-29 14:29 飞林沙 阅读(2450) 评论(20) 编辑 收藏

评论列表

#1楼 2009-05-29 14:42 徐少侠

国内的培训学校的教师能力的确值得怀疑

不过,远离不是解决问题的办法

我就在培训机构工作

我希望让学生学习最基础的,了解最流行的,理解变迁的本质

#2楼 2009-05-29 14:46 Gray Zhang

请问泛型有效率影响的证据?从我的理解上,泛型是生成新的类型,拿来的效率影响之说呢

#3楼 2009-05-29 15:14 winter-cn未登陆[未注册用户]

我们都知道var只能用于变量,而无法用于属性,而我们使用var的情况往往是因为我们不大容易确定某一个变量(或者属性)的类型,同样,很可能出现一个类的属性或者方法返回类型不易确定返回类型的情况,这个时候,我们就可以用dynamic了。
----------------------------------------------------------
你的这个理解也是有些问题的 var其实本质是懒得写类型 让编译系统自动帮你决定 所以问题不是出在属性或者局部变量上
dynamic把对类型的检测延迟到运行时 dynamic最主要的意义还是改进封装性 使得你能够编写更加灵活通用代码 同时运行时检测类型并捕获异常 可以真正做到duck type

其实可以想到 dynamic有很多有趣的用法 比如跟匿名委托配合

#4楼 2009-05-29 15:16 winter-cn未登陆[未注册用户]

--引用--------------------------------------------------
Gray Zhang: 请问泛型有效率影响的证据?从我的理解上,泛型是生成新的类型,拿来的效率影响之说呢
--------------------------------------------------------
.net的泛型会略微影响效率 跟C++略有些不同

#5楼 2009-05-29 15:26 Jeffrey Zhao

我也用List<Object>,因为有用的扩展方法都是加载IEnumerable<T>上的,ArrayList不行。
所以我现在也一直推荐说,情愿用List<Object>也不要用ArrayList。
至于性能,真有影响吗?我去试试看,我目前认为是一样的,因为其实是一样的代码阿。

http://pic.cnitblog.com/face/u9419.png

#6楼 2009-05-29 15:41 Gray Zhang

期待老赵的结论,数组其实也是特殊的泛型吧…

#7楼 2009-05-29 15:41 Gray Zhang

期待老赵的结论,数组其实也是特殊的泛型吧…

#8楼 2009-05-29 16:05 Jeffrey Zhao

@Gray Zhang
呵呵,得出结果了,正在写文章呢。

http://pic.cnitblog.com/face/u9419.png

#9楼[楼主] 2009-05-29 16:10 飞林沙

@winter-cn未登陆

嗯,呵呵,是的,我语言表达能力有些问题,当然,每个方法都有着他自己的返回类型,我们可以确定。但是有时写起来比较麻烦

http://pic.cnitblog.com/face/u35999.jpg

#10楼 2009-05-29 16:51 Jeffrey Zhao

性能比较结果如下:http://www.cnblogs.com/JeffreyZhao/archive/2009/05/29/generic-performance-test.html

结论:范型不会影响性能。

http://pic.cnitblog.com/face/u9419.png

#11楼 2009-05-29 17:32 xiaotie

不明白泛型的嵌套为什么会引起代码的膨胀

http://pic.cnitblog.com/face/u6006.jpg

#12楼[楼主] 2009-05-29 17:42 飞林沙

@Jeffrey Zhao

老赵,给我些时间,我找一下之前做过的一个笔记,确实有这样的印象。

http://pic.cnitblog.com/face/u35999.jpg

#13楼[楼主] 2009-05-29 17:43 飞林沙

@xiaotie

每个类型都生成不同的方法表,这是其一。
我之前做过一个完整的泛型方面的笔记,我回去找下,然后再补充

http://pic.cnitblog.com/face/u35999.jpg

#14楼 2009-05-29 17:47 Jeffrey Zhao

@飞林沙
List<T>,如果T是值类型,的确是每个值类型是不同的代码。但是如果T是引用类型,那么就是共享同一份代码的。
还有其实就算是引用类型,范型也可以减少cast啊,而且编程也爽多了。

http://pic.cnitblog.com/face/u9419.png

#15楼 2009-05-29 19:02 xiaotie

@飞林沙

泛型应该是减少代码的。

要实现同等功能,ClassA<Type> where Type : IInterfaceA 大致等同于

ClassA
{
public IInterfaceA A {get;set;}
}

用泛型写要少写很多代码。

如果是 Class<TType1<TType2<TType3<TType4>>>> 这样的泛型,节省的代码就更多了。

编译后的代码大小,老赵上面也说了。

C#下泛型烦在写的时候太麻烦。

像写个Class<TType1<TType2<TType3<TType4<TType5<TType6<TType7<TType8<TType9>>>>>>>>> 这样的泛型类,光写 where 能把人折腾死。

http://pic.cnitblog.com/face/u6006.jpg

#16楼[楼主] 2009-05-29 19:19 飞林沙

@Jeffrey Zhao
@xiaotie

我在这里也没什么根据了,我先把我这一节跟注释掉,等明天找到了依据再和你们吵,哈!

http://pic.cnitblog.com/face/u35999.jpg

#17楼 2009-05-29 21:50 JimLiu

其实还是会很有用的,比如有时候直接var ...一个linq出来的匿名类型,这时候就能以dynamic的形式传给View了。
当对性能不是如此敏感的时候,显然比定义一个ViewModel类型敏捷了不少——当然,用强类型还是有其好处的。
再到做Silverlight这类的时候,有了dynamic也许也会方便很多。上次那个session(忘了是什么活动的)里的demo就有这么点体会。

http://pic.cnitblog.com/face/u35553.jpg

#18楼 2009-05-30 01:44 clayman

泛型实际上有时候性能更好http://blogs.msdn.com/ricom/archive/2005/08/26/performance-quiz-7-generics-improvements-and-costs-solution.aspx
泛型确实会导致一定的代码膨胀,编译器有时候需要为不同的特定类型,生成不同的代码

#19楼 2009-05-30 09:26 Muse

我用var就是为了偷懒,比如foreach Dictionary的时候.
我觉得dynamic还有一个用法,比如我需要一个方法返回不同类型的对象,而这些对象来源于同一个地方,我只是要根据不同的环境返回不同的属性(比如给一个ID,根据需要返回一个对象的不同属性),这时用dynamic的话,只需要一个方法,然后加一个参数,而不需要些若干相似的方法了。

#20楼15419402009/5/30 18:52:54 2009-05-30 18:52 装配脑袋

@xiaotie
where过多,过长,都是错误使用泛型的例子。例如
void T Create<T>() where T: ISomething, new
八成都是错的用法。更别提约束一大堆接口了。约束的目的是减少可用的类型,而不是提供接口和基类的成员,如果不是本着这个原则使用约束,就必定是将本应该用OO解决的问题让泛型来解决了。