Hello World
Spiga

Why Java Sucks and C# Rocks(补1):Reddit,兼谈C#属性

2010-06-28 14:18 by 老赵, 6712 visits

最近博客冷清了不少,主要是事情较多,一是.NET交流会,二是工作,三是几篇暂时无法发在博客上的文章。周末在家,发现邮箱里经常收到SlideShare的邮件,说是我的Why Java Suck and C# Rocks幻灯片在推特上很火热。而今天早上我忽然发现,它被人发到Reddit的编程版块了,讨论地颇为热烈。关于讨论内容,您可以亲自阅读一下。最近的讨论也让我想要补充一些关于C#属性的问题。

关于这个话题,在我不断强调讨论的目的之后,在我的博客上还是有许多朋友没有明白我在说什么,热血上涌当即给予反击,这些从后面的评价中便可以看出。不过在Reddit上,当围观人群看到相同的内容,甚至只有幻灯片,没有更多解释说明,但是大家都能理解我的意思,都能意识到我是希望“用Scala代替Java语言”。而即便是没有看到幻灯片的最后,或是对此有不同意见,也都是在用正常的方式提出质疑。这样的差别让我感到很讽刺,有些话我已经说过很多次了,现在只是引一个例证,也就不再重复了。

在Reddit上的讨论中,有人提出C#里的属性和事件其实都只是方法的语法糖,意义不大。最近在一些人的文章里也出现了类似的说法。我在这个系列的中原本并不打算着重分析C#里的“属性”概念,因为它对于“编程思维”的影响可能并不如其他方面来的明显。不过如果真要深究的话,我还是非常看重这个语言特性的(不过其实属性和事件都是.NET特性)。那么现在我也来简单谈一下“属性”,而“事件”则在后面的文章里讨论吧——那篇文章的草稿已经躺了大半个月了。

属性,在C#里以get/set方法对的形式出现,例如:

// C#
public class MyClass
{
    private int m_count;
    public int Count
    {
        get { return this.m_count; }
        set { this.m_count = value; }
    }
}

而在Java中的等价代码则是:

// Java
public class MyClass
{
    private int m_count;
    public int getCount() { return this.m_count; }
    public void setCount(int value) { this.m_count = value; }
}

在Java中并非没有“属性”的概念,而是将它直接拆成了getXxx和setXxx两个方法而已,而对于一些工具(如IDE)、框架(如序列化框架)来说,它会忽略方法之前的get/set,直接将后面的字符识别为一种“成员字段”,而在.NET对象中,“属性”天生就是一种表示成员字段的标准方案。

在我看来,从使用上来说,属性比分离的方法要方便一些。例如我们要将MyClass对象的Count属性加1,则可以这么写:

// C#
c.Count++;

而在Java里则必须这样:

// Java
c.setCount(c.getCount() + 1);

当然您可能会觉得这方面的差距并不大。那么我们换一个场景,现在有一个User数组,我们要根据它的年龄进行排序:

// C#
users.Sort(u => u.Age);

// Java
users.sort(#(User u)(u.getAge()));

这里我们暂且忽略Java中古怪的Lambda表达式写法,只关注“属性”和“get方法”之间的区别。在我看来,C#的代码体现了声明式编程的理念,从语义上讲可以清晰地视为“将u按u.Age字段”排序;与之想法,一旦我们只能利用getAge方法时,便又重新回到了“命令式”思维方式上。为什么这么说?因为调用getAge方法,意味着“获取”这个“Age”字段,它代表的是一个“动作”,表明排序时去“获取”这样一个数值,也就是体现了“怎么做(how to do)”,而不像C#代码中体现的是“做什么(what to do)”。

下面一个例子可能更为典型。例如在使用ORM框架,如(N)Hibernate时,我们时常会在复杂的XML配置中迷失方向。后来有了Fluent NHibernate,让我们可以使用“代码”来进行配置,这样无论是从可读性还是可维护性来说都有了质的飞跃。目前在项目中,由于没有使用关系型数据库,于是我便遵循Fluent Interface的理念,设计了这样的API:

Property(c => c.ArticleID).Identity();
Property(c => c.Content).Name("Html");
Property(c => c.Tags);
Property(c => c.Keywords).ChangeWith(c => c.Content).ChangeWith(c => c.Tags);

以上代码是Article对象的映射规则,四行代码表明:

  • 将ArticleID映射为标识符
  • 将Content映射为存储中的Html字段。
  • 将Tags映射至存储,名称不变。
  • 将Keywords字段映射至存储中,并随Content及Tags字段改变。

我写的映射框架支持传统ORM框架的状态跟踪功能,不过还提供了只读字段的“按需改变”。如Keywords,它是只读属性,框架会读取它的值,并将其放入存储内(以便进行查询),但是我们不需要将其写回对象中去。同时,在Contents和Tags改变的时候,框架也会重新获取Keywords的值,并跟新至存储内。利用C#的表达式树特性,我们可以十分轻松愉快地“声明”出这样的规则。在这个规则中,我们利用“属性”来标明字段之间的关系,这可以说是一种DSL。但如果没有属性,一切都是get方法,那么这段代码又会是什么样子呢?

所以,相对C#来说Java语言中很难写出优雅的声明式代码及Fluent NHibernate这样的框架。原因有多种,如Lambda的古怪语法形式是为一、缺少表达式树功能是为二,而不直接支持“属性”这样的概念也是另一个原因吧。

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

29 条回复

  1. lee51076008
    124.117.249.*
    链接

    lee51076008 2010-06-28 15:23:47

    沙发,终于抢上了,

  2. chensz
    124.133.255.*
    链接

    chensz 2010-06-28 15:34:55

    Fluent NHibernate是什么意思?

  3. zhangronghua
    219.146.59.*
    链接

    zhangronghua 2010-06-28 16:47:22

    仅仅从美感上说C#的属性比Java的get/set更胜一筹。

  4. 链接

    Jim Liu 2010-06-28 17:28:06

    Property对C#还是很重要的吧,这玩意是作为一种独立的概念而存在于Meta Data里的,而Java的Meta Data里却是Field和Method,而且有好多C#的编程技巧都围绕Property展开的。更何况少了get/set的确代码的视觉效果好了不少。还有就是一个纯粹的概念模型的问题了:属性应该是对象本身具有的,访问一个对象的属性应该是访问者的动作而非这个对象的动作,而用get/set方式这个动作的主体权就完全转变了。

  5. yyliuliang
    124.205.158.*
    链接

    yyliuliang 2010-06-28 17:42:59

    http://blog.zhaojie.me/2010/06/more-why-java-sucks-and-csharp-rocks-1-reddit-and-property.html.html 有两个.html ...

  6. 老赵
    admin
    链接

    老赵 2010-06-28 17:50:55

    @yyliuliang

    谢谢,改好了。希望不会对SEO有太大影响,呵呵。

  7. yemg
    122.242.119.*
    链接

    yemg 2010-06-28 19:00:05

    有人针对你写了一篇:http://blog.csdn.net/firelong2010/archive/2010/06/23/5688783.aspx。

  8. 老赵
    admin
    链接

    老赵 2010-06-28 19:46:51

    @yemg

    不算新了,我上一篇“吵你妹”就是嫌他烦的(当然也包括其他一些人或情况)。不过我觉得我没啥好评论的,该说的东西在博客园上大家都已经说过了。话说,建议您在关注CSDN的同时,也可以关注一下博客园,其实他是先在博客园上发,然后才去CSDN的。

  9. 链接

    yyliuliang 2010-06-29 00:11:37

    hi 老赵您好 有个问题想请教下 fsharp里递归函数必须要带 rec 是不是本身lambda演算要需要通过组合子来递归函数 , rec关键字就是编译器帮助自动生成组合子的作用吗?

  10. 老赵
    admin
    链接

    老赵 2010-06-29 01:04:37

    @yyliuliang

    为函数取名了就不是lambda了啊,其实没有rec,编译器也可以做递归的不是吗?其实编译器做此类递归是不会想着组合子什么的吧。

  11. 链接

    陈梓瀚(vczh) 2010-06-29 16:15:06

    我觉得rec纯粹是受了远古时期语言的影响。总之不会有任何真的Y Combinator去做在生成的代码里面的。而且F#这种倾向于立即计算而不是惰性计算的语言,更不可能将Y Combinator作为原生的东西实现进去。至于惰性语言,可以看《The Implementation of Functional Programming Language》,Y Combinator最终也不是写在语言里面的,而是虚拟机直接创造出来的这种模拟Y Combinator的行为来实现递归的。

  12. 链接

    mfjt55 2010-06-29 16:49:33

    上次看组合子的时候,也在想F#声明函数时用rec是不是用了组合子,不然为什么F#里lambda里没有rec就不能自己用自己,希望老赵有时间能帮忙分析下F#里的rec做了什么东东。

  13. 老赵
    admin
    链接

    老赵 2010-06-29 17:02:15

    @mfjt55

    那明显只是语法规则怎么定了而已,我们在编译器里改吧改吧就可以实现不用rec也能递归了……

  14. yyliuliang
    124.205.158.*
    链接

    yyliuliang 2010-06-29 17:09:15

    @vczh 而是虚拟机直接创造出来的这种模拟Y Combinator的行为来实现递归的。

    这段不太明白 既然c#,f#都是编译成IL 在clr这一层次是肯定统一的, 但是从f#编译阶段到IL 这一段过程,rec起了什么作用 比较疑惑

  15. imcoddy
    124.205.186.*
    链接

    imcoddy 2010-06-29 17:11:09

    Java的getter和setter已经算是惯例,凑合着用感觉还行吧。不过相比之下,C#这样的属性定义确实显得优雅不少。

    看了老赵先生第二段内容,感叹一下,国内技术的氛围的确还是有些浮躁吧。

  16. 链接

    Ivony 2010-06-29 18:50:34

    Y Combinator更多的时候存在的意义只是为了证明lambda演算的能力而已。

    从语法上来说,rec关键字指令编译器更改名称解析的范围。将其推送到=右边的表达式去。

  17. 链接

    Ivony 2010-06-29 18:53:34

    rec的作用就是使得这样的不符合“标准”语法的语句合法:

    int i = i + 1;//C# Syntax Error
    let i = i + 1;//F# Syntax Error

    同理:
    let f i = (f i) + 1;//理应Syntax Error。

    只是我们写多了C#不觉得而已。

  18. qualle2008
    77.6.4.*
    链接

    qualle2008 2010-06-30 00:03:41

    下次是不是该写一个Why OCaml Sucks and F# Rocks系列了,素材更多。就是靶子不够分量。

  19. 老赵
    admin
    链接

    老赵 2010-06-30 09:52:27

    @qualle2008

    我支持高手来写,无所谓谁Sucks谁Rocks,言之有物言之有理即可,我自觉还没法写这样的文章。

  20. Tacker
    119.39.101.*
    链接

    Tacker 2010-06-30 17:27:43

    老赵 你好,看了你的mvc1的相关视频感觉做的很好,不知道有没有打算做MVC2的视频呢,最近卡在mvc2的自定义视图引擎这块。

  21. Nick
    59.108.16.*
    链接

    Nick 2010-07-12 13:19:29

    rec似乎是为了类型推演而存在

  22. netwjx
    58.213.239.*
    链接

    netwjx 2010-07-15 15:38:12

    有些特殊情况下,java的设计会有些优势,如:我拥有了一个对象a,借助IDE可以知道有哪些属性,但是现在问题是,我知道我需要设置一个对象属性,我明确的知道它一定不是对象的方法,java下输入set, 然后就可以看到哪些属性可以赋值, 借助IDE可以阅读文档, 然后就可以找到自己需要的属性。

    VS中, 在所有属性和方法里面翻看太慢了,还很容易翻完了也不知道到底在哪, 再加上VS的文档提示信息格式不好用,不能换行,不能链接(经常是需要看到关联的文档才能确定是自己需要的), 我的做法是在FF中搜我需要的特性+inurl:msdn.

    理论上IDE可以提供一个方法过滤出允许set的属性,目前不知道,最少也需要一个特殊的快捷键吧, 这不就是语言的设计影响到开发者学习和使用么?

    对VS的文档提示信息严重的BS, 以及MSDN文档的组织太松散, 总要点啊点啊点啊, 我哪能在一个列表页就确定我需要的那个属性,点进一个页面再退出来是很容易进入链接迷魂阵的。在VS中浏览 搜索本地的MSDN感觉很悲剧, 还不如用google。

  23. 老赵
    admin
    链接

    老赵 2010-07-15 17:06:54

    @netwjx

    你的意思主要是说,不能直接看出属性能不能读写是吧?可能是一个问题,需要编辑器和文档用更直观的表现清楚了,呵呵。

  24. wdk23411
    60.169.23.*
    链接

    wdk23411 2010-07-23 13:37:16

    我说两句吧,我一直是用java的(在2005年用过一段时间的C#),也经常和我周边的朋友讨论(甚至是争论)java和C#,争论到最后用java的还是用java,用c#仍然整C#

    没有什么其他原因,熟练了,C#因为语言本身(也许还有微软)的原因做快速开发相当的合适,因为项目小,周期短,开发效率是首位的。如果真的拿大型项目来说,C#当然是可以做的,只不过大型项目的复杂度会大大拖累C#本身“语言级的快速”,基本上不会比用java开发快,这个时候java诸如框架成熟等优势也就体现出来了。

    说实话,作为java程序员,我很羡慕C#的那些“语法糖”,非常的羡慕,但也仅仅是羡慕而已,并不觉得这些会给自己的程序以多大的改善。

  25. 链接

    hellolaojiang 2010-08-23 18:43:14

    C# 里的属性确实很方便,恰巧我也做过一些JAVA项目,感觉GET/SET在某些地方确实很不LOOK AND FEEL。 但C#里的属性,如果不真正理解它,非常容易引起程序员的迷惑,特别是带参数的属性(在C#里为索引器)。为了说明这点,我取个例子,下面这段代码,没有语法错误,但却无法编译通过:

    public class User
    {
        private string[] cols = new string[] {"jason","cookie"};
    
        public string this[int pos]
        {
            get
            {
                return (pos >= 0 && pos < 2) ? cols[pos] : string.Empty;
            }
            set 
            {
                if (pos >= 0 && pos < 2)
                    cols[pos] = value;
            }
        }
    
    
        public string get_Item(int pos)
        {
            return (pos >= 0 && pos < 2) ? cols[pos] : string.Empty;
        }
    }
    

    其实在CLR级别,所有的属性都是一些GET/SET方法,并且对于带参数的属性还做了约定,会自动生成:getItem,setItem的方法(这是由于C#不允许指定索引器名称造成的);因此编译器为了不和这些自动生成的方法产生冲突,就会终止编译。并报一个很奇怪的异常:

    Error Type 'testProperty.User' already reserves a member called 'get_Item' with the same parameter types.

    如果你不理解属性的内部特性,是不是就很迷惑,很难理解,为什么会有如此的异常。修复很简单,直接将你的方法改个其他名字就OK了。但如果你又不想修改名字呢?哪估计就没得办法了,因为这个时候,即使是用IndexerName特性也无法解决问题。

    (另外这个编辑器的代码插入,很不友好。看不见标识符。)

  26. 老赵
    admin
    链接

    老赵 2010-08-24 01:17:58

    @hellolaojiang: 另外这个编辑器的代码插入,很不友好。看不见标识符。

    看看帮助就知道该怎么做了,那么明显友好的实时预览放在这里,为什么还是无法发表友好的评论呢?

  27. halftone
    184.82.227.*
    链接

    halftone 2013-04-28 13:41:41

    看你这一系列文章,很受益,感谢!

  28. 链接

    isee 2013-10-07 17:21:27

    java里面的属性如果设置为public 也是可以 c.count++;的

    我想c#的方式也没什么特别之处,而且 属性私有,但是操作属性却不通过set方法,set方法是干嘛的呢?

    // java:
    Point {
        public int x;
        public int y;
    
        get set ...
    }
    
  29. 老赵
    admin
    链接

    老赵 2013-10-08 10:11:43

    @isee

    所以你连属性和Field的区别都分不清,至于为什么用属性为什么不建议用field就更不清楚了吧,建议多去了解一些东西再来发表看法,越说越显得你被Java毒害很大…

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我