Log4X

链路纵横

公告

统计

2008年10月7日

JAVA程序员看C#的精华与糟粕

C#和java是号称90%的相同加上10%的不同。因此当时我学习C#,阅读两种代码完全没有什么阻碍。

对C#了解得深入以后,来发表下对这两种语言各自特性的一些看法

比较起java和C#大相径庭的那10%,会发现C#五花八门的特性要多很多。比较知名的有:委托,属性,真正的泛型,索引器,类初始化器,分部类,操作符重载,struct,unsafe代码,IDisposable等,另外.net framework 3.5还加了一大串纯粹由编译器提供的特性。

委托,这个可以算是C#之于java的最大优势。虽然java可以依靠接口,匿名内部类这些特性实现委托一样的功能,但却要麻烦许多,如果涉及到N个委托实例相加的情况,那么一个C#里面简单的"+"号,在java里就只能用FilterChain,InterceptorStack这种概念了。

属性据说属性信息是带到运行时的,后查看反射的确有PropertyInfo类,之前说它是编译器语法糖是受了某篇分析IL代码的文章误导,不过这个是不是语法糖都没什么关系,它和java普遍做法的get,set各有优劣。属性写起来简捷,但却比较难看出哪些属性是只读,哪些是只写不读。

真正的泛型,这个又是一大C#的优势,同时伪泛型又是java的一大败笔。但是java年代比较长,为兼容性考虑不得不使用假的泛型实现。如果泛型不是在jdk1.5时推出,而是在jdk1.4,和collection framework一起推出,我认为它也会采用真正的泛型实现。

索引器,有了这个很多容器类都可以直接用[]取元素,感觉还不错,比没有好,编译器的小把戏。

类初始化器,典型的懒汉特性。每次用顶多能省下一两行代码,又是编译器的小把戏。

分部类(partial class),纯粹为了vs.net的那一大堆图形化设计器老和人的代码冲突而搞出来得玩意,又是编译器的小把戏。

操作符重载,属于用到得不多,要用时却显得特别有用的东西,很奇怪java为什么不提供这个功能。

struct,在堆栈上的东西,释放内存那是超级的快,只不过需要用到这个的场合,大概都在使用C++编程。

unsafe代码,纯粹增加语言复杂性的东西。就好像一个人搬家,看这个不舍得扔,那个也不舍得扔,搞到最后把瓶瓶罐罐都搬走了。

IDisposable,实现这个接口,配合using块,非常的强大,终于可以像C++那样掌握对象的销毁了。

接着说说java比C#多的特性:

匿名内部类:真是极端方便的一个东西,还和JAVA的好多设计模式有关系,不过C#把java匿名内部类的写法拿去用作类初始化器了,糟蹋了...

动态代理: java里面要实现AOP,易如反掌;C#要实现AOP,难如登天,不得不借助Assamble命名空间下的那些动态IL生成工具。这就是动态代理的作用。

希望有一天C#可以拥有匿名内部类和动态代理,变得更完善。也希望java也可以有委托等。

posted @ 2008-10-07 07:48 YYX 阅读(7054) 评论(80) 编辑
一个细小,难以察觉的错误

 

这是一个控制Java动态代理执行的类(附C#实现),target域是他的代理目标,动态代理会生成一个可以替代target的类,在调用target类的某方法时,实际上是调用被改造过的方法(会多执行代码块中标记custom code的那些代码)。

不过这些不是重点。

重点是,ignorePrefixList中包含了一些字符串,而要求是以这些字符串开头的方法,不执行//custom code中省略的逻辑,而是绕到else块直接执行原方法。

下面这段代码,看似在对方法名和ignorePrefixList中的字符串进行逐个比较的时候,没什么问题,但还是希望各位别看后面,推测下isMethodIgnored方法会发生什么问题。

代码:

 1 public class MgtInvocation implements InvocationHandler{
 2 
 3     private static List ignorePrefixList;
 4     
 5     static {
 6         ignorePrefixList = new ArrayList();
 7         ignorePrefixList.add("get");
 8         ignorePrefixList.add("list");
 9     }
10     
11     private Object target;
12     
13     public Object invoke(Object proxy, Method method, Object[] args)
14             throws Throwable {
15         if(!isMethodIgnore(method)){
16             //custom code 
17             //
18             return method.invoke(target, args);
19             //custom code
20             //
21         }else {
22             return method.invoke(target, args);
23         }
24     }
25     
26     private boolean isMethodIgnore(Method mtd){
27         boolean res = false;
28         String mtdName = mtd.getName();
29         for(Iterator it = ignorePrefixList.iterator();it.hasNext();){
30             res = res||mtdName.startsWith((String)it.next());
31         }
32         return res;
33     }
34 }

 

翻译成C#大致是这样的(C#没有动态代理,所以这个类是做做样子的,又因为Iterator和IEnumrator稍有不同,此处使用并不会导致错误,所以isMethodIgnore方法内部也稍有不同):

 1 public class FakeInvocation 
 2     {
 3         private static IList ignorePrefixList;
 4         private object target;
 5 
 6         static FakeInvocation(){
 7             ignorePrefixList = new ArrayList();
 8             ignorePrefixList.Add("get");
 9             ignorePrefixList.Add("list");
10         }
11 
12         public object invoke(object proxy, MethodInfo method, object[] args)
13         {
14             if (!isMethodIgnore(method))
15             {
16                 //custom code 
17                 //
18                 return method.Invoke(target, args);
19                 //custom code
20                 //
21             }
22             else
23             {
24                 return method.Invoke(target, args);
25             }
26         }
27         public bool isMethodIgnore(MethodInfo mtd) {
28             bool res = false;
29             String mtdName = mtd.Name;
30             for (int i = 0; i < ignorePrefixList.Count; )
31             {
32                 res = res || mtdName.StartsWith((String)ignorePrefixList[i++]);
33             }
34             return res;
35         }
36     
37     }
38 

 

上面代码的问题是,isMethodIgnored方法极易陷入死循环。

当ignorePrefixList中,除去最后一个字符串元素外,任何一个若为mtdName的起始字符串,整个方法就会陷入死循环。

印象当中,死循环是菜鸟中的菜鸟才犯,因此当时根本想不到自己的代码会死循环,还以为是线程间死锁了。看来看去,也没发现哪里同步块不对,最后靠单步debug才找出错误。

原因很简单,就是||运算符,在发现左边为true时,就直接返回true而不执行右边了。

 

所以当这个代理去代理以get开头的目标方法时,就出现了这种景象:第一轮比较了"get"后,res被设成了true,迭代子看到后面还有个"list"的字符串元素,于是执行

1 res = res||mtdName.startsWith((String)it.next());

 

而||发现res本来就为true,结果直接返回,没有执行关键的it.next();接着就去看循环条件了。但因为next()方法没执行,因此迭代子位置都没动,下一元素还是"list"字符串。结果又回到开头一幕。于是就死掉了...

修正得方法很多,交换||两端位置可以:

 

1 private boolean isMethodIgnore(Method mtd){
2         boolean res = false;
3         String mtdName = mtd.getName();
4         for(Iterator it = ignorePrefixList.iterator();it.hasNext();){
5             res = mtdName.startsWith((String)it.next())||res;
6         }
7         return res;
8     }

 

把||改成|也可以:

 

1 private boolean isMethodIgnore(Method mtd){
2         boolean res = false;
3         String mtdName = mtd.getName();
4         for(Iterator it = ignorePrefixList.iterator();it.hasNext();){
5             res = res|mtdName.startsWith((String)it.next());
6         }
7         return res;
8     }

 

 遇到res为true直接跳出方法也可以:

 

1     private boolean isMethodIgnore(Method mtd){
2         String mtdName = mtd.getName();
3         for(Iterator it = ignorePrefixList.iterator();it.hasNext();){
4             if(mtdName.startsWith((String)it.next()))return true;
5         }
6         return false;
7     }

 

另外,我在把java换成C#过程中,发现要在C#中重现这错误还不容易。

因为如果'直译'isMethodIgnore方法,就是

 

1         public bool isMethodIgnore(MethodInfo mtd) {
2             bool res = false;
3             String mtdName = mtd.Name;
4             for (IEnumerator eu = ignorePrefixList.GetEnumerator(); eu.MoveNext(); )
5             {
6                 res = res || mtdName.StartsWith((String)eu.Current);
7             }
8             return res;
9         }

 

C#中的迭代子是不管后面有没有元素,先移动并返回移动成功与否(MoveNext),移动完了取当前元素(Current)。而java的迭代子是先向后瞟一眼(hasNext),发现有元素,取出下一个,并移动到那里(next)。

这样的话,移动操作并不是在 循环块内,因此可以保证每次都执行。

另外其实在C#和1.5以后的jdk里面,以上的for循环写法其实就是foreach的'手工版'。而如果用foreach的话,就不会有这个问题。

从上面的小错误,可以总结出两个经验:

 - 用&&和||的时候得想一想,右边如果不执行会不会对整体造成影响。

 - 尽量不把结束条件,前进操作之类写到for循环内部。

posted @ 2008-10-07 00:03 YYX 阅读(2005) 评论(5) 编辑