Hello World
Spiga

为什么JVM上没有C#语言?浅谈Type Erasure特性

2010-02-22 23:50 by 老赵, 10279 visits

每次提到语言的时候我总是忍不住骂Java是一门生产力低下,固步自封的语言——这估计要一直等到Java语言被JVM上的其他语言取代之后吧。JVM上目前已经有许多语言了:JRuby,Jython;还有一些特定于JVM平台的语言,如Scala和Groovy等等。但是,为什么JVM上没有C#语言呢?按理说,这门和Java十分相似,却又强大许多的语言更容易被Java程序员接受才对。您可能会说,Sun和微软是对头,怎么可能将C#移植到JVM平台上呢?嗯,有道理,但是为什么社区里也没有人这么做呢(要知道JVM上其他语言都是由社区发起的)?其实在我看来,这还是受到了技术方面的限制。

泛型是Java和C#语言的重要特性,它使得程序员可以方便地进行类型安全的编程,而不需要像以前那样不断进行类型转换。例如,我们要在Java中写一个泛型字典的封装便可以这么做:

public class DictWrapper {

    private HashMap<K, V> m_container = new HashMap<K, V>();

    public V get(K key) {
        return this.m_container.get(key);
    }

    public void put(K key, V value) {
        this.m_container.put(key, value);
    }
}

看上去和C#并没有什么区别,不是吗?不过,如果我们观察编译后生成的bytecode(类似于.NET平台上的IL),便会发现一丝奇妙之处。使用javap -c DictWrapper得到的结果是:

Compiled from "DictWrapper.java"
public class jeffz.practices.DictWrapper extends java.lang.Object{
public jeffz.practices.DictWrapper();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."<init>":()V
   4:	aload_0
   5:	new	#2; //class java/util/HashMap
   8:	dup
   9:	invokespecial	#3; //Method java/util/HashMap."<init>":()V
   12:	putfield	#4; //Field m_container:Ljava/util/HashMap;
   15:	return

public java.lang.Object get(java.lang.Object);
  Code:
   0:	aload_0
   1:	getfield	#4; //Field m_container:Ljava/util/HashMap;
   4:	aload_1
   5:	invokevirtual	#5; //Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
   8:	areturn

public void put(java.lang.Object, java.lang.Object);
  Code:
   0:	aload_0
   1:	getfield	#4; //Field m_container:Ljava/util/HashMap;
   4:	aload_1
   5:	aload_2
   6:	invokevirtual	#6; //Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
   9:	pop
   10:	return
}

从bytecode中可以看出,其中并没有包含任何与K,V有关的信息。get/put方法的参数和返回值都是Object类型,甚至内置的HashMap也是如此。那么调用DictWrapper的代码是如何做到“强类型”的呢?例如:

public static void main(String[] args) {
    DictWrapper<String, String> dict = new DictWrapper<String, String>();
    dict.put("Hello", "World");
    String world = dict.get("Hello");
}

它的bytecode便是:

public static void main(java.lang.String[]);
  Code:
   0:	new	#2; //class jeffz/practices/DictWrapper
   3:	dup
   4:	invokespecial	#3; //Method jeffz/practices/DictWrapper."<init>":()V
   7:	astore_1
   8:	aload_1
   9:	ldc	#4; //String Hello
   11:	ldc	#5; //String World
   13:	invokevirtual	#6; //Method jeffz/practices/DictWrapper.put:(Ljava/lang/Object;Ljava/lang/Object;)V
   16:	aload_1
   17:	ldc	#4; //String Hello
   19:	invokevirtual	#7; //Method jeffz/practices/DictWrapper.get:(Ljava/lang/Object;)Ljava/lang/Object;
   22:	checkcast	#8; //class java/lang/String
   25:	astore_2
   26:	return
}

看到标号为22的那行代码没有?这条checkcast指令便是将上一句invokevirtual的结果转化为String类型——DictWrapper.get所返回的是个最普通不过的Object。

这便是Java语言的泛型实现——请注意我这里说的是Java语言,而不是JVM。因为JVM本身并没有“泛型”的概念,Java语言的泛型则完全是编译器的魔法。我们写出的泛型代码,事实上都是和Object对象在打交道,是编译器在帮我们省去了冗余的类型转换代码,以此保证了代码层面的类型安全。由于在运行时去除所有泛型的类型信息,因此这种泛型实现方式叫做Type Erasure(类型擦除)

在.NET中则完全不同,“泛型”是真真切切落实在CLR层面上的功能。例如DictWrapper.Get方法在.NET上的IL代码便是:

.method public hidebysig instance !TValue Get(!TKey key) cil managed
{
    .maxstack 2
    .locals init (
        [0] !TValue CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: ldfld class [mscorlib]...Dictionary`2 ...DictWrapper`2::m_container
    L_0007: ldarg.1 
    L_0008: callvirt instance !1 [mscorlib]...Dictionary`2::get_Item(!0)
    L_000d: stloc.0 
    L_000e: br.s L_0010
    L_0010: ldloc.0 
    L_0011: ret 
}

您可以发现,.NET的IL便确切包含了TKey和TValue的类型信息。而在运行的时候,CLR会为不同的泛型类型生成不同的具体类型代码,这在我之前的文章中也有所提及。

那么,Java和C#两种泛型实现方式分别有什么优势和劣势呢?Java这种Type Erasure做法,最大的优势便在于其兼容性:即便使用了泛型,但最后生成的二进制文件也可以运行在泛型出现之前的JVM上(甚至JDK中不需要添加额外的类库)——因为这里的泛型根本不涉及JVM的变化。而.NET中的泛型需要CLR方面的“新能力”,因此.NET 2.0的程序集是无法运行在CLR 1.0上的——当然.NET 1.0的程序集可以直接在CLR 2.0上执行。而CLR实现方式的优势,便在于可以在运行期间体现出“模板化”的优势。.NET程序员都知道,泛型可以节省值类型的装箱和拆箱的开销,即便是引用类型也可以避免额外的类型转化,这些都能带来性能上的提高。

因此,在.NET社区经常会把Java的这种实现方式称之为“假泛型”,而同时也会有人反驳到:泛型本来就是语言上的概念,实现不同又有什么关系,凭什么说是“假”的呢?其实,由于失去了JVM的支持,一些.NET平台上常用的,非常有效的开发方式都难以运用在Java上。例如所谓的泛型字典

public class Cache<TKey, TValue>
{
    public static TValue Instance;
}

public class Factory
{
    public static string Create<TKey>()
    {
        if (Cache<TKey, string>.Instance == null)
        {
            Cache<TKey, string>.Instance = // some expensive computation
        }

        return Cache<TKey, string>.Instance;
    }
}

由于Cache<TKey>在运行时是个具体独立的类型,因此泛型字典是性能最高的存储方式,比O(1)时间复杂度的哈希表还要高出许多。如果说这也只是运行方面的优势,那么这段代码中的“泛型工厂”代码(即Factory.Create<SomeType>(),包括类似的Factory<T>.Create()这种)则是Java语言中无法实现的。这是因为Type Erasure的作用,在运行时JVM已经丧失了TKey这样的类型信息,而在.NET平台上,TKey则是Create<TKey>签名的组成部分。

Type Erasure造成的限制还有不少,如果您是一个C#程序员,可能难以相信以下的Java代码都是不合法的:

public class MyClass<E> {
    public static void myMethod(Object item) {
        if (item instanceof E) { // Compiler error
            ...
        }
        E item2 = new E(); // Compiler error
        E[] iArray = new E[10]; // Compiler error
    }
}

由于JVM不提供对泛型的支持,因此对于JVM上支持泛型的语言,如Scala,这方面的压力就完全落在编译器身上了。而且,由于这些语言以JVM为底,Type Erasure会影响JVM平台上几乎所有语言。以Scala为例,它的模式匹配语法可以用来判断一个变量的类型:

value match {
    case x:String => println("Value is a String")
    case x:HashMap[String, Int] => println("Value is HashMap[String, Int]")
    case _ => println("Value is not a String or HashMap[String, Int]")
}

猜猜看,如果value变量是个HashMap[Int, Object]类型的对象,上面的代码会输出什么结果呢?如果是C#或是F#这样运行在.NET平台上的语言,最终输出的一定是“Value is not ...”。只可惜,由于JVM的Type Erasure特性,以上代码输出的却是“Value is HashMap[String, Int]”。这是因为在运行期间JVM并不包含泛型的类型信息,HashMap[K, V]即是HashMap,无论HashMap[String, Int]还是HashMap[Int, Object]都是HashMap,JVM无法判断不同泛型类型的集合之间有什么区别。不过还好,Scala编译器遇到这种情况会发出警告,程序员可以了解这些代码可能会出现的“误会”。

因此,为什么有IKVM.NET这样的项目可以将Java语言编译成.NET程序集(也可以将Java的jar包转化成.NET程序集),却没有项目将C#编译到JVM上(或是将C#程序集转化为jar包)。这是因为,JVM不足以支撑C#语言所需要的所有特性。而从运行时的中间代码角度来说,JVM Bytecode的能力也是.NET IL的子集——又有什么办法可以将超集塞入它的子集呢?

此外,如CLR的值类型可能也很难直接落实在JVM上,这也是JVM上运行C#的又一阻碍。由于这些因素存在,我想如F#这样的.NET语言也几乎不可能出现在JVM上了。

当然,如果真要在JVM上实现完整的C#也并非不可以。只要在JVM上进行一层封装(例如还是就叫做CLR,CLR Language Runtime),一定可以满足C#的全部要求(例如为每个泛型方法传入具体的类型对象)。但是这个做法的代价较高,且难以优雅地与Java程序进行互操作,因此投入产出比让人堪忧。此外,已经有人在JVM上实现了一个x86模拟器,那么又有什么是做不了的呢?实在不行,我们就在模拟器上装一个Windows操作系统,然后装一个Microsoft .NET,再……

Creative Commons License

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

Add your comment

109 条回复

  1. xuefly
    *.*.*.*
    链接

    xuefly 2010-02-22 23:52:00

    不敢抢老赵的沙发
    还是忍不住抢了

  2. yayv[未注册用户]
    *.*.*.*
    链接

    yayv[未注册用户] 2010-02-22 23:59:00

    恩。最开始java 5要增加泛型支持时,这种做法就遭到了广大java程序员的反对,但还是这么做了,然后就成为了不可扭转的历史。

  3. 87Super
    *.*.*.*
    链接

    87Super 2010-02-23 00:03:00

    我才登录一下 沙发就被你们抢了。。

  4. 老赵
    admin
    链接

    老赵 2010-02-23 00:06:00

    @yayv
    只可惜我最后一次用Java做项目是没有泛型的Java 4(应该叫1.4才对)……搞不懂为什么要反对啊。

  5. Milo Yip
    *.*.*.*
    链接

    Milo Yip 2010-02-23 00:12:00

    很久沒有留意 Java,學習了。
    不過我覺得還是有可能在編譯過程中加入代碼去實現 C# 的 Generics,並且去模擬 value type的語意等。而不需要去到 emulator 的程度。
    理論上可行麼? (雖然因為 JVM 少了相關資訊,而導致 JIT 不能容易優化)

  6. winter-cn未登录[未注册用户]
    *.*.*.*
    链接

    winter-cn未登录[未注册用户] 2010-02-23 00:33:00

    只是实现起来不太方便
    只能像C++那样 为每个泛型的模板生成一段独立的代码
    或者用隐藏字段存储一下泛型类反射的信息

  7. 老赵
    admin
    链接

    老赵 2010-02-23 00:33:00

    @Milo Yip
    我最后那句其实只是玩笑话么,呵呵。
    其实,可能只是需要一套类库,比如就叫做CLR──CLR Language Runtime,然后把C#需要的功能跑在CLR上就行了。
    当然,性能这边自然就尴尬了,因此没人这么做,也没哪个语言去解决Type Erasure的问题……

  8. 老赵
    admin
    链接

    老赵 2010-02-23 00:41:00

    @winter-cn未登录
    不光是不太方便,基本上还是需要JVM的支持。
    编译时创建不同的代码是“静态展开”,似乎可行。
    但是,泛型有动态部分啊,例如反射,例如Factory.Create<T>(),只有运行时才能知道具体的T类型。
    而这个T类型是编译期无法静态得知的。

  9. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-23 00:45:00

    我好奇的是为什么不改JVM,让它原生支持泛型。向下兼容就是了,向上兼容是不是一个有点变态的需求呢?

  10. 老赵
    admin
    链接

    老赵 2010-02-23 00:47:00

    @zzfff
    我也是这么想的,向下兼容本就是个包袱,向上兼容更是包袱中的包袱……

  11. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 01:33:00

    其实这个问题我是有很多话要说啊,写在这里吧。

    首先呢,其实泛型的大多数重要的特性是编译器而非运行时的,例如泛型类型自动推断,所以即使运行时不支持,泛型类型推断也可以做到,这就很好了。

    当然JVM的致命伤还是出在运行时根本没有泛型,这就让“类型字典”这样的特性是不可能复现的。

    像C++一样编译期展开不是不可行,但这必须在同一个程序集内,如果这个程序集(包)被编译了,里面的类型应该是不可能增加的(不了解Java不确定)。

    CLR最大的优势其实在于它可以将List<string>和List<int>当作完全不同的两个类型来看待,这是JVM无法做到的。而其他的,如泛型类型信息的保存,则完全可以用变通的方式来解决。

    讨论泛型方法,老赵说的那个其实可以实现的。

    对于任何实例而言,其泛型类型参数一定是明确的,对于任何执行(Executing)的语句,泛型类型参数在调用堆栈中也一定是明确的。

    简单的说:
    不存在List<T>的实例,只有List<int>或者List<object>或……

    也不可能执行Factory.Create<T>这样的语句,在调用堆栈(上下文)中,T一定是明确的。

    假定有这样一个方法:
    void GenericMethod<T>( T args )

    如果我们这样调用:GenericMethod( 1 );显然T明确的是int。
    不明确的情况只能让它放到另一个泛型方法/类型中,如这样:
    void GenericMethod2<T>( T args )
    {
    //...
    GenericMethod( args );
    }

    但对于GenericMethod2的调用必然是明确的。换言之只要沿着调用堆栈上溯,T必然明确。

    无论我们做什么变换,这总是成立的,例如:
    void GenericMethod<T>( List<T> arg )
    由于List<T>实例不存在,只存在具体的List<int>或其他。所以T也必然明确。

    既然在调用堆栈中类型信息一定明确,事实上我们就可以把类型信息给传过来,来模拟一下。

    例如,假设我们有一个这样的泛型方法:
    void GenericMethod<T>( T arg )
    JVM中无法模拟,但可以变通成这样的签名:
    void GenericMethod( object arg, Class type )

    当然,调用的时候,譬如说我们这样调用:
    GenericMethod( 1 );
    生成这样的伪代码:
    GenericMethod( 1, int.class );
    泛型方法其实是可以变通实现的。

    无法逾越的障碍还是在泛型类型上。

  12. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 02:10:00

    泛型的实现跟整个生态系统都有关系。在CLRv2/C# 2.0出来的时候,.NET的历史包袱还不是很重,但几乎同一时期的Java 5背负的历史包袱就重多了。如果像.NET一样实现reified generics,那么为了兼容老程序势必得像.NET一样为原本不支持泛型的API平行新添一套泛型API,如System.Collections与System.Collections.Generic的关系一样。Java选择了兼容性——老的Class文件可以在新的JVM上运行,老的代码也可以通过新的编译器来编译。
    Java在添加泛型时留了一手:Java 5对JVM规范的修改包括对Class文件格式的更新,其中4.4.4小节:

    Signatures are used to encode Java programming language type information that is
    not part of the Java virtual machine type system, such as generic type and method
    declarations and parameterized types. See The Java Language Specification, Third
    Edition, for details about such types.

    This kind of type information is needed to support reflection and
    debugging, and by the Java compiler.

    ...

    The Java virtual machine does not check the well formedness of the signatures
    described in this subsection during loading or linking. Instead, these checks are
    deferred until the signatures are used by reflective methods, as specified in the
    API of Class and members of java.lang.reflect. Future versions of the Java
    virtual machine may be required to performs some or all of these checks during
    loading or linking.


    换句话说,规范里确实出现了“泛型”的概念,所有符合Java 5更新后的规范的JVM实现都必须能正确读取和校验泛型信息,用于支持泛型相关的反射API;但同时,泛型又不是JVM类型系统的一部分,而是Java 5及更高版本的Java语言的类型系统的概念。所以简单的说“JVM不知道泛型”是错误的,同时JVM也确实没有在类型系统这一根本层面上支持泛型。
    “留了一手”是说,Java留下了在日后添加reified generics的可能性,不然JVM规范里也不会专门留下上面引用的最后一句。像在JVM Language Summit这种活动上,大家最希望JVM能支持的就是reified generics,特别是像Scala之类对泛型有很高需求的语言。

    如果不考虑与Java API的互操作,只是说在JVM上实现“真泛型”的语言的话,编译时膨胀是可以做到的。像老赵说的Factory.Create<T>(),可以通过增加间接层的方式来实现。
    假想有在JVM上支持真泛型的“Nava”语言。其中一种思路是:首先,带有泛型类型、方法等的名字使用特别的编码,例如说将Factory<T>类生成为一个名为“Factory@@T”的类,就像C++的name mangling一样将一些信息“藏”在名字里。(要带有语言中不允许在标识符中出现的字符,移民与用户创建的标识符冲突,但不能用左方括号“[”、前斜杠“/”、左右尖括号“<”“>”等在JVM规范的method descriptor/signature中有特殊含义的字符)。这种特别的编码后的名字将被Nava编译器识别,作为判断是否为泛型的依据。方法中使用占位符T的地方可以临时生成为Object,并带上特别的annotation让Nava编译器能得知这个是占位符。
    然后,如果编译时发现有对Factory<String>的使用,则将“Factory@@T”的所有逻辑复制一份,新建“Factory@String@”类,将原本的占位符T替换为String。然后在编译new Factory<String>()时生成new Factory@String@()即可。这是典型的编译时膨胀法的泛型的思路。
    考虑得不是很详细,上面的描述可能在细节上需要调整才能真的实现出来,但思路的方向我相信是行得通的。
    不过这样实现出来的语言与Java API的互操作性会非常差;它甚至不能把Java的反射API暴露出来,而得自己实现一套反射API才行。

    Scala在一些涉及JVM类型系统没有提供支持的地方也是自己生成了些反射代码来实现的。例如说refinement type,{ def foo() : Unit } 这样的类型Scala编译器会生成反射代码在运行时查找实际传进来的对象有没有符合要求的foo()方法。
    想想C# 4.0中dynamic call site是如何实现的,编译器也是把源码里看起来像是一个方法调用点的地方变成了一串代码,创建CallSite对象,创建payload,调用CallSite的Invoke委托等等。这种编译技巧在JVM上的语言自然也是可以用的。

    提到

    Jeffrey Zhao:
    而从运行时的中间代码角度来说,JVM Bytecode的能力也是.NET IL的子集——又有什么办法可以将超集塞入它的子集呢?


    Milo说得没错,Turing complete的话总是可以模拟的。连C++程序都可以跑在FlashPlayer里了,很多时候问题还是“值不值得做”而不是“能不能” =_=|||

    另外挑点骨头,

    Jeffrey Zhao:
    Java这种Type Erasure做法,最大的优势便在于其兼容性:即便使用了泛型,但最后生成的二进制文件也可以运行在泛型出现之前的JVM上(甚至JDK中不需要添加额外的类库)——因为这里的泛型根本不涉及JVM的变化。


    这个描述是不正确的。只要使用新编译器编译Java程序,在使用默认目标版本的时候,生成出来的Class文件是无法在老JVM上跑的。JVM在加载Class文件时会检查开头的“Class File Version”,新编译器编译出来的会比老JVM所支持的Class文件版本高,所以老JVM会拒绝加载新Class文件。可以参考这帖看Java版本与Class文件版本间的关系。

    最后,我觉得没人在JVM上移植C#的原因之一是没足够强的需求。Java平台有十几年的历史,有丰富成熟的类库,而.NET上很少有功能强到会引诱Java程序员不惜移植C#去使用的功能。Java语言跟C#语言所覆盖的功能范围大同小异,即便Java语法冗长些,实际使用时通过各种技巧也能达到接近C#的使用的效果;泛型是个例外……
    看使用了Project Lombok后写的一个Java类:
    public class Foo {
      private @Getter String name;
    }

    与一个C# 3.0的类:
    public class Foo {
      public string Name { get; private set; }
    }

    也差不了那么多……

  13. virus
    *.*.*.*
    链接

    virus 2010-02-23 08:48:00

    老赵,又来经典的了,鼎鼎

  14. killkill
    *.*.*.*
    链接

    killkill 2010-02-23 08:51:00

    经典,经典!

  15. Gnie
    *.*.*.*
    链接

    Gnie 2010-02-23 08:58:00

    老赵牵头搞个JVM上的C# :)

  16. 顾客
    *.*.*.*
    链接

    顾客 2010-02-23 09:08:00

    坚定的北大青鸟反对者?

  17. 1-2-3
    *.*.*.*
    链接

    1-2-3 2010-02-23 09:08:00

    学习了。
    可能就是这样吧,一个语言也会老去。时间越长,遗留代码就越多,兼容的压力就越大,越来越难以改变。不但难以加入新特性,就连缺点也不能去掉,越来越臃肿怪异。

  18. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-23 09:10:00

    @RednaxelaFX
    少了互操作,在jvm实现c#的意义就大打折扣了。
    不过按你文中的方式实现c#的话,使用"Nava"访问java平台原有类库应该没什么问题,用java访问"Nava"写的类库的话也只需要重写一套反射的api似乎就可以了?

  19. 111112[未注册用户]
    *.*.*.*
    链接

    111112[未注册用户] 2010-02-23 09:33:00

    总结起来,就是设计时泛型和运行时泛型的区别。

  20. 老赵
    admin
    链接

    老赵 2010-02-23 09:34:00

    顾客:坚定的北大青鸟反对者?


    是啊是啊

  21. 老赵
    admin
    链接

    老赵 2010-02-23 09:34:00

    @RednaxelaFX
    哈哈,还是R大厉害,我也再补充几句话。
    其实我这个说法也不完整,的确大部分语言特性,只是C#的编译器更强大,不过这里的泛型其实也解释了我最后提到的“为什么没有.net程序集到jar”的转换。
    至于JVM上能否实现,这自然是可以的啦,程序是人写出来的。所以我这里谈“为什么”其实也是在解释成本因素,还有效果,例如引入间接层后的性能和复杂度会怎么样……
    .NET现在很多类库已经充分利用C#的语言特性在办事了,现在其实我相信它们对于Java界也有吸引力,只是Java和.NET两条路已经越走越远了。
    我还是很高兴.NET能力覆盖JVM能力,C#能力覆盖Java能力的,因为这样.NET可以用N多Java类库,hoho。

  22. 老赵
    admin
    链接

    老赵 2010-02-23 09:35:00

    Gnie:老赵牵头搞个JVM上的C# :)


    没必要啊,一是我说的JVM上搞的话能力不够,复杂度太高等等,二是都有Scala了为什么还要C#。

  23. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 09:38:00

    @幸存者
    我的猜想是Java原有的类库中的泛型API很难兼容的暴露给Nava用,只能暴露raw-type。于是Nava去用Java类库的感觉就像1.4或更早之前一样。
    关键是反过来要让Java用Nava写的库,特别是泛型库,会非常非常的麻烦。因为mangling时用了源码中不能出现的字符,普通Java程序不可能以正常的Java语法调用Nava的泛型API,只能用反射——那么麻烦谁还愿意用呢?
    就像用F#写库的话一般会考虑到互操作的需求而做一层薄薄的包装,Nava要跟Java良好互操作也需要包装,不过有没有人会愿意用一种80%样子和功能跟Java一样、但速度慢10x的JVM语言就不好说了orz

  24. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 09:45:00

    Jeffrey Zhao:
    @RednaxelaFX

    其实我这个说法也不完整,的确大部分语言特性,只是C#的编译器更强大,不过这里的泛型其实也解释了我最后提到的“为什么没有.net程序集到jar”的转换。
    至于JVM上能否实现,这自然是可以的啦,程序是人写出来的。所以我这里谈“为什么”其实也是在解释成本因素,还有效果,例如引入间接层后的性能和复杂度会怎么样……


    其实有Grasshopper咯……老赵可能没关注过,不过不是只有IKVM.NET、Ja.NET这个方向的兼容层,反过来的也有……

  25. 老赵
    admin
    链接

    老赵 2010-02-23 09:53:00

    @RednaxelaFX
    不错不错,R大出马总是等引出一些新东西来,它解决了这个泛型的问题吗?效果怎么样呢?

  26. 老赵
    admin
    链接

    老赵 2010-02-23 09:54:00

    RednaxelaFX:
    不过有没有人会愿意用一种80%样子和功能跟Java一样、但速度慢10x的JVM语言就不好说了orz


    是的,其实就是这个样子……

  27. duguguiyu
    *.*.*.*
    链接

    duguguiyu 2010-02-23 09:56:00

    哈。亮了。Java的包袱太沉重了,伪泛型真的很发指。。。

  28. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 10:20:00

    关于泛型我再插两句嘴。

    C++的编译时展开绝对能解决大部分泛型类型的问题,但是编译后怎么展开或者说类型膨胀就比较麻烦,这需要动态产生出类型。

    简单的说我们现在有一个包A,已经编译好了,里面有一个泛型类型Factory<T>。

    那么在包(程序集)A之外,例如包B,来使用这个泛型类型,例如这样实例化:
    new Factory<MyClass>(),其中MyClass根本就是在包B中定义的类型,包A在编译的时候是不可能知道和猜到的,所以Factory@@MyClass类型就不可能存在。而且更要命的是,这个类型还不可能定义在包A中,因为这个类型内部有定义在包B中的类型MyClass的引用。

    这个时候就只能在运行时动态的产生一个包Factory@@MyClass,里面动态产生一个类型,引用自A和B两个包。

    当然我们可以发现这样导致互通性完全的被破坏。除了我们自己发明的Nava语言,任何其他的语言基本上不可能访问这个Factory<MyClass>的对象。

  29. 吴峰
    *.*.*.*
    链接

    吴峰 2010-02-23 10:20:00

    java一直在包袱中往前爬行,实在有太多的系统用低版本的jvm了,不向上兼容的话,就像.net的程序运行在xp上,要安装framework...还得升级到最新。

  30. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2010-02-23 10:21:00

    推倒

  31. Arthraim
    *.*.*.*
    链接

    Arthraim 2010-02-23 10:48:00

    看上去JVM上有个CLR就一劳永逸了……

  32. 冰の酷龙
    *.*.*.*
    链接

    冰の酷龙 2010-02-23 10:51:00

    @RednaxelaFX
    其实有Grasshopper咯……老赵可能没关注过,不过不是只有IKVM.NET、Ja.NET这个方向的兼容层,反过来的也有……


    看了下描述。。简直是变态啊。java和.net,其实都有接触,但是从没想过一起来用。。。

  33. Arthas-Cui
    *.*.*.*
    链接

    Arthas-Cui 2010-02-23 11:39:00

    我记得当时要在.net上实现python的时候, 作者认为扯淡。
    最后又是他自己给实现了。

    所以我觉得吧, C#不能在jvm上跑, 完全不是什么理论问题。
    就是个阵营问题。

  34. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 11:40:00

    Ivony...:
    简单的说我们现在有一个包A,已经编译好了,里面有一个泛型类型Factory<T>。

    那么在包(程序集)A之外,例如包B,来使用这个泛型类型,例如这样实例化:
    new Factory<MyClass>(),其中MyClass根本就是在包B中定义的类型,包A在编译的时候是不可能知道和猜到的,所以Factory@@MyClass类型就不可能存在。而且更要命的是,这个类型还不可能定义在包A中,因为这个类型内部有定义在包B中的类型MyClass的引用。

    这个时候就只能在运行时动态的产生一个包Factory@@MyClass,里面动态产生一个类型,引用自A和B两个包。


    Ivony对Java的包有个误解,这里贴没有默认支持的语法高亮的代码块有点麻烦,回复发在这里了,参考下?

    用前面提出的Nava思路来做编译时膨胀的话,遇到Factory<MyClass>时就在自己的JAR包里生成一个Factory@MyClass@就好了,没任何问题。

  35. 恒星的恒心
    *.*.*.*
    链接

    恒星的恒心 2010-02-23 12:18:00

    真正的高手

  36. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 12:32:00

    RednaxelaFX:

    Ivony...:
    这个时候就只能在运行时动态的产生一个包Factory@@MyClass,里面动态产生一个...



    Ivony对Java的包有个误解,这里贴没有默认支持的语法高亮的代码块有点麻烦,回复发在这里了,参考下?

    用前面提出的Nava思路来做编译时膨胀的话,遇到Factory<MyClass>时就在自己的JAR包里生成一个Factory@MyClass@就好了,没任何问题。



    我说的包就是package啊,不是JAR啊,看了那篇文章,貌似就是说JAR就是.NET里面的DLL,即多文件程序集吧?

    Factory@MyClass@生成在哪个package里面呢?MyClass所在的package么?

  37. 诺贝尔
    *.*.*.*
    链接

    诺贝尔 2010-02-23 12:49:00


    泛型就是编译器的多态。
    c++这种语言,编译是在运行之前。
    c#这种语言,可以边运行边编译。

    java,可以完全实现c++的策略,可惜它没有这样做,而是用了忽悠群众的类型搽除技术,只是一个语法糖,没有一点意义。更不用说达到c#的崇高境界啦。

    在java中用泛型,几乎都是自己给自己添堵的行为。

    不过,现在的动态语言概念,也能实现“编译”这个概念吧。所以java应该也有可能在将来实现真正的泛型。

  38. 陛下
    *.*.*.*
    链接

    陛下 2010-02-23 12:59:00

    开发人员总有自己的开发习惯,尤其熟悉并偏向某一类语言之后。确实,与 c# 比较起来,java 的生产效率是要低一些。看架构吧,大概关键的还是做出了什么,而不是用了什么来做;即便开发效率上有所损失,但与良好的设计相比,这些损失“也许”是可以忽略的。

  39. 老赵
    admin
    链接

    老赵 2010-02-23 12:59:00

    Arthas-Cui:
    我觉得吧, C#不能在jvm上跑, 完全不是什么理论问题。
    就是个阵营问题。


    我这篇文章说得就不仅仅是阵营问题而是技术问题,hoho。

  40. 老赵
    admin
    链接

    老赵 2010-02-23 13:01:00

    @陛下
    有什么设计是Java能实现的但C#不行吗?反过来我可以举出很多例子哦。

  41. 老赵
    admin
    链接

    老赵 2010-02-23 13:01:00

    Arthas-Cui:
    我记得当时要在.net上实现python的时候, 作者认为扯淡。
    最后又是他自己给实现了。


    不是,是有公司说clr上对python语言支持不好,业界也这么认为。
    但是ironpython的作者觉得奇怪,因为他实现的jython在jvm上跑的很好。
    于是他想亲自验证一下“为什么”,结果验证下来跑的好好的。

  42. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 13:48:00

    Ivony...:

    RednaxelaFX:

    Ivony...:
    这个时候就只能在运行时动态的产生一个包Factory@@MyClass,里面动态产生一个...



    Ivony对Java的包有个误解,这里贴没有默认支持的语法高亮的代码块有点麻烦,回复发在这里了,参考下?

    用前面提出的Nava思路来做编译时膨胀的话,遇到Factory<MyClass>时就在自己的JAR包里生成一个Factory@MyClass@就好了,没任何问题。



    我说的包就是package啊,不是JAR啊,看了那篇文章,貌似就是说JAR就是.NET里面的DLL,即多文件程序集吧?

    Factory@MyClass@生成在哪个package里面呢?MyClass所在的package么?


    假如Factory<T>的全限定名是ab.cd.ef.Factory<T>,而MyClass的全限定名是st.uv.wx.MyClass,那么生成Factory@MyClass@也是生成为ab.cd.ef.Factory@st.uv.wx.MyClass@即可,只要类加载器正确Java/JVM会认为生成出来的类与原Factory<T>在同一个package里。

    Java的package、JAR和可访问性规则在.NET/C#中没有完全对应的东西,JAR与程序集/模块都不一样,package与namespace也不一样。不应该混为一谈。Java没有强名称,所以无论生成的类型在什么地方(散在外面、在某个JAR包里还是运行时直接在内存中生成)都不影响package的判定和package的可访问性,不像.NET的程序集会有internal可访问性的关系受影响。

  43. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 13:54:00

    @RednaxelaFX


    我被C#毒害了。。。。。

  44. Milo Yip
    *.*.*.*
    链接

    Milo Yip 2010-02-23 13:55:00

    RednaxelaFX:
    其实有Grasshopper咯……老赵可能没关注过,不过不是只有IKVM.NET、Ja.NET这个方向的兼容层,反过来的也有……



    也看了一下 grasshopper,是支持 C# Generics 的,立論倒了…… 繼續努力。

  45. 老赵
    admin
    链接

    老赵 2010-02-23 14:23:00

    @Milo Yip
    要支持肯定是能够支持的(文章最后我也提到了),我感兴趣的是不是完美支持,还有是如何多加一层抽象的……不知道R大研究过没有?

  46. 顾客
    *.*.*.*
    链接

    顾客 2010-02-23 14:45:00

    @Jeffrey Zhao
    为什么要反对北大青鸟呢?赞同谁?

  47. 老赵
    admin
    链接

    老赵 2010-02-23 14:46:00

    @顾客
    因为它骗钱,收钱却教不好学生。
    不支持谁,支持自己好好学。

  48. 顾客
    *.*.*.*
    链接

    顾客 2010-02-23 14:57:00

    Jeffrey Zhao:
    @顾客
    因为它骗钱,收钱却教不好学生。
    不支持谁,支持自己好好学。


    这个也对,收的钱太多了,教的东西比较肤浅。再加上多数人不用心学,通常一个班只有几个人接近或达到职业化需求!

  49. 陛下
    *.*.*.*
    链接

    陛下 2010-02-23 15:08:00

    Jeffrey Zhao:
    @陛下
    有什么设计是Java能实现的但C#不行吗?反过来我可以举出很多例子哦。


    老赵,我没为 java 叫屈,其实我也喜欢 c#,只不过随便说几句,感觉关注了那么新的语法、看上去绚的技术,还不如用某个搓点的语言好好摆弄几个好的成熟的框架。当然,关键还是个人不努力;未来,至少我个人会淡化语言上的偏见,多着眼于实际应用。
    跟你其实不在一个层次上,很多话说出来反而显得我太业余,好在也能和你一样,偶尔在工作中自娱自乐,足矣,呵呵。

  50. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 15:18:00

    @Jeffrey Zhao
    Grasshopper采用的方法是“增加参数”法。跟Ivony在11楼的回复类似,如果是泛型类型则添加成员变量来记录各个实际泛型参数类型,如果是泛型方法则在方法列表里添加参数传入实际泛型类型。这些也是compiler trick,跟我在12楼假想的编译膨胀法不同,不过从“增加间接层”的大方向上看是一致的。
    Mainsoft把.NET Framework的很大部分都移植了;当然不是自己从头写,而是基于Mono的源码。所以通过Grasshopper用.NET类库感觉不到问题,用Java类库时就跟我前面说的一样只能用raw-type;但如果用Java去调用Grasshopper生成的JVM字节码的程序,感觉就会很怪。这就是单向整合,而不是真正双向的互操作。

    可以参考官网上的一段视频:Grasshopper TV Episode 2 - Dive into Grasshopper,大致从7:30开始看就OK了,具体内容在9:00之后

    记得我是以前关注Mono的时候翻Mono的新闻看到Grasshopper的,IKVM.NET也是在Mono官网和feed上发现的。就这点来说Mono真的给了我很多启发……

  51. lifengfeng[未注册用户]
    *.*.*.*
    链接

    lifengfeng[未注册用户] 2010-02-23 15:21:00

    我搞java的时候最郁闷的还不是这些 主要的是没有一个类型可以统一
    基本数据类型和类类型 造成我实现基类方法的时候要为 Object byte int long float double 各写一个重载方法。

  52. 疯流成性
    *.*.*.*
    链接

    疯流成性 2010-02-23 15:22:00

    有一篇看不懂的文章飘过

  53. langage only is chuanshuo[未注册用户…
    *.*.*.*
    链接

    langage only is chuanshuo[未注册用户] 2010-02-23 16:20:00

    @Jeffrey Zhao
    我来说一个,C# interfaces cannot declare fields. :)

  54. 比较意义不是很大[未注册用户]
    *.*.*.*
    链接

    比较意义不是很大[未注册用户] 2010-02-23 16:25:00

    @langage only is chuanshuo
    我也说一个,JDK提供了动态代理类proxy,非常方便. C#要想实现比较麻烦。

  55. 子路[未注册用户]
    *.*.*.*
    链接

    子路[未注册用户] 2010-02-23 16:37:00

    Java Has two types of inner classes, static inner classes and non-static inner classes. C# has a similar feature as static-inner classes but does not have any counterpart for non-static inner classes. Furthermore in java you could declare anonymous inner classes inside methods, but you can not do that in C#.

    不可否认,java语言的演化和c#不可同日而语。一个closure几经波折,现在才勉强要加入到java7中。但愿oracle以后能加快java语言的更新。JAVA目前的优势还是在于多年的积累吧,尤其是许多开源的项目。还有一个是在Linux下的应用(mono还未成气候)

  56. boool
    *.*.*.*
    链接

    boool 2010-02-23 16:44:00

    看得出老赵对C#的喜爱,确实java现在的版本升级并没有加入太多让人惊喜的东西,不过,因为它出现更早,提出许多新特性,新思想,才让.Net现在发展得更好!

  57. 老赵
    admin
    链接

    老赵 2010-02-23 16:51:00

    @boool
    难道你认为C#的特性是Java提出来的吗?

  58. 老赵
    admin
    链接

    老赵 2010-02-23 16:51:00

    @子路
    你又把平台和语言混淆了……我说的是语言,不是平台。

  59. 老赵
    admin
    链接

    老赵 2010-02-23 16:53:00

    @langage only is chuanshuo
    我们说的是软件设计方式吧,不是语言特性了。
    语言特性可能,也可能不会影响软件设计方式。
    C#有很多影响设计方式的语言特性,呵呵。

  60. 老赵
    admin
    链接

    老赵 2010-02-23 16:54:00

    @RednaxelaFX
    没错,其实Java领域解决Type Erasure问题也是通过传递String.class这种进行的,具体的我有机会也去研究一下……

  61. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 16:57:00

    比较意义不是很大:
    @langage only is chuanshuo
    我也说一个,JDK提供了动态代理类proxy,非常方便. C#要想实现比较麻烦。




    这个是类库的功能而不是语言的特性吧。


    C#要实现这个也很简单么,C#中不仅仅可以对接口这么玩,对类型也可以(透明代理)。



    另外打个小广告,很早以前就有写一个鸭子类型帮助器的想法,一直懒的去做,主要是觉得好像用处不是很大的说。

    鸭子接口帮助器可以帮助你用一个没有实现某接口类型的实例创建一个实现了你指定接口的实例出来,可以由你自己决定接口绑定规则或是自行匹配。

    简单的说假设你有一个类型是这样的:

    public class Account
    {
    public string Username { get; }
    }

    你后来才发现这个类型应该实现IIdentity接口才会更好,但修改这个类型重新编译的代价有点大,鸭子接口帮助器可以帮助你利用一个Account的实例创建一个IIdentity的实例出来,后者只是对前者做了一个包装而已。

  62. 老赵
    admin
    链接

    老赵 2010-02-23 16:58:00

    @Ivony...
    发布一下吧

  63. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 17:06:00

    Jeffrey Zhao:
    @Ivony...
    发布一下吧



    其实我当时只是看RoR的鸭子类型吹的乌拉乌拉的,后来一想,C#也可以玩么,就去鼓捣了,结果学会了一大堆东西,但发现鼓捣出来也没啥用,就懒得弄了。

  64. 老赵
    admin
    链接

    老赵 2010-02-23 17:15:00

    @Ivony...
    拖着拖着C#在语言级别上都已经支持了……还是简单介绍一下吧。

  65. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 17:17:00

    Jeffrey Zhao:
    @Ivony...
    拖着拖着C#在语言级别上都已经支持了……还是简单介绍一下吧。



    没啥技术含量啊,就是Emit一个类型出来就完了。

    我去把代码翻出来整理一下整个Release版,你们确定有什么地方能用么。

  66. 老赵
    admin
    链接

    老赵 2010-02-23 17:24:00

    @Ivony...
    在.NET 3.5上实现duck typing咯,不过关键还是要看使用起来是否方便优雅。

  67. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-23 17:33:00

    @Ivony
    是不是涉及RealProxy,IRemotingTypeInfo什么的?我也觉得fun的意义大于现实意义。
    @Jeffrey Zhao
    说下关于语言的反面意见:)要是放开设计的话,我相信Redmond的guys完全可以把C#打造成宇宙大杀器30000,关键是现实价值

  68. 老赵
    admin
    链接

    老赵 2010-02-23 19:26:00

    @zzfff
    嗯?语言自然不是特性越多越好,为什么说是反面意见?

  69. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-23 19:59:00

    interface就是C++,Java,C#这类静态强类型、OO思想的语言的鸭子类型吧?
    @Jeffrey Zhao
    没什么。我只是觉得在C#语言上实现真鸭子意义不大。(其实我也不怎么了解鸭子)

  70. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-23 20:10:00

    鸭子类型和C# 4.0 dynamic的比较?哪位来给我科普一下:)

  71. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-23 20:21:00

    @zzfff
    或许可以先了解一下structural typing,再去思考duck typing与static/dynamic typing有没有关系。
    C++、Java、C#都是nominal typing的。

  72. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-23 20:29:00

    发现IDynamicObject这个东东,可惜我是个无趣的功利主义者:(

  73. Ivony...
    *.*.*.*
    链接

    Ivony... 2010-02-23 20:51:00

    其实也是遇到的难度挺大,泛型就不说了,还有ref和out等参数修饰符,研究这些东西的过程中学会了很多,要做出来好多是体力活,所以有点懒。。。。

    原理说穿了再简单不过,就是做一个包装类实现某接口,再把方法的调用转发给源实例就OK了。

    C#在直接实现鸭子类型应该是不会,毕竟C#的接口不仅仅是功能约定,所以这种显示的语法鸭子类型实现,也有意义。可是我在用C#的时候没遇到这种需求。

  74. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-24 00:11:00

    @Ivony...
    为Account模拟出IIdentity,我用RealProxy的思路:(最新修改)

    using System;
    using System.Reflection;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Proxies;
    using System.Runtime.Remoting.Messaging;
    using System.Security.Principal;
    
    public interface IMyInterface
    {
        int SayHello(string s, ref int i, out string s2);
    }
    public class Account
    {
        //IIdentity members
        string AuthenticationType { get { return "NTLM"; } }
        bool IsAuthenticated { get { return true; } }
        string Name { get { return @"cnblogs\zzfff"; } }
        //IMyInterface members
        int SayHello(string s, ref int i, out string s2)
        {
            Console.WriteLine(s);
            i++;
            s2 = i.ToString("X");
            return 10 + i;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                IIdentity id = StronglyTypedDuckTyping.Instance<IIdentity>(new Account());
                Console.WriteLine(id.Name);
                IMyInterface my = id as IMyInterface;
                int i = 10; string s;
                int ret = my.SayHello("hello world", ref i, out s);
                Console.WriteLine("ret:{0},i:{1},s:{2}", ret, i, s);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
    public sealed class StronglyTypedDuckTyping : RealProxy, IRemotingTypeInfo
    {
        public static TInterface Instance<TInterface>(object target) where TInterface : class
        {
            return (TInterface)new StronglyTypedDuckTyping(target, typeof(TInterface)).GetTransparentProxy();
        }
        //
        private object _target;
        private StronglyTypedDuckTyping(object target, Type interfaceType)
            : base(interfaceType)
        {
            _target = target;
        }
        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage mcm = (IMethodCallMessage)msg;
            try
            {
                //这里需要完善!
                MethodInfo mi = _target.GetType().GetMethod(mcm.MethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                object[] args = (object[])mcm.Args.Clone();
                object ret = mi.Invoke(_target, args);
                return new ReturnMessage(ret, args, args.Length, null, mcm);
            }
            catch (Exception ex)
            {
                return new ReturnMessage(ex, mcm);
            }
        }
        //IRemotingTypeInfo members
        public bool CanCastTo(Type fromType, object o) { return true; }
        public string TypeName
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }
    }
    
    

  75. boool
    *.*.*.*
    链接

    boool 2010-02-24 09:38:00

    老赵,我确实认为.net1.0时代的C#许多东西是和java的概念相似的,不过现在C#发展的速度远远超过了java,提供了许多更灵活的新特性,那些 java 确实没有

  76. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-24 12:07:00

    虽然我很久以前就知道RealProxy什么的,但昨晚为fun鼓捣了一下,发现还有点用处,quick-and-dirty的丑小鸭.....

  77. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-24 18:50:00

    Jeffrey Zhao:
    @Ivony...
    拖着拖着C#在语言级别上都已经支持了……还是简单介绍一下吧。


    我“被”这句话蒙了一整天。C# 4.0中的dynamic就是duck typing啊:http://en.wikipedia.org/wiki/Duck_typing
    关于dynamic的实现,
    http://en.wikipedia.org/wiki/C_Sharp_4.0#Dynamic_member_lookup 里说有三中机制:IDynamicObject,COM IDispatch,reflection,还没研究,猜测它们需要CLR的支持吧?或者说,有CLR的支持,C#实现起来更轻松。
    平台与语言相互辉映...

  78. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-24 19:08:00

    敬佩下RednaxelaFX,你是真正的高手,以后一定多向你请教、切磋。我是半罐水,蜻蜓点水

  79. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-24 21:28:00

    @zzfff
    wiki上的描述经常跟不上时代的脚步……当然DLR的细节变化也是太多太快。
    IDynamicObject已经不叫这个名字,现在是叫IDynamicMetaObjectProvider。
    使用DLR的语言在bind的时候可以先试图用语言自身的语义来bind,失败的话退到用IDynamicMetaObjectProvider来bind,再失败的话尝试COM interop的binder,还是失败的话使用fallback到DLR默认的反射方式来bind;如果所有这些尝试都失败了,binding过程还可以退到语言定义的方式来处理,以此支持method-missing之类的机制。

    在DLR 1.0的时间范围内,DLR的实现都没有用到CLR的新改变,基本上就是在用CLRv2已有的功能而已。

  80. zzfff
    *.*.*.*
    链接

    zzfff 2010-02-24 21:43:00

    @RednaxelaFX
    C# 4.0中的dynamic特性是不是属于DLR的范畴?

  81. 老赵
    admin
    链接

    老赵 2010-02-24 22:58:00

    @zzfff
    不需要CLR支持,是类库+编译器配合的结果。

  82. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-24 23:20:00

    @zzfff
    C# 4.0的dynamic类型是由C#编译器与DLR配合完成的。DLR在.NET Framework 4.0中也是标准库的一部分了。

    在System.Core.dll中,
    System.Linq.Expressions命名空间在.NET Framework 4.0中也由LINQ的核心进化为DLR的Expression Tree核心部分,然后System.Dynamic是DLR的动态类型系统的核心部分。

    在System.Dynamic.dll中:
    这里也有一部分System.Dynamic命名空间的实现,是与COM interop相关的,但这些类型都不是public的。

    与C#直接相关的在Microsoft.CSharp.dll中,实现了支持dynamic关键字用的binders。
    dynamic关键字本身只是代表带有[System.Runtime.CompilerServices.DynamicAttribute]的System.Object,C#编译器会为设计dynamic类型的表达式生成使用了DLR的动态调用点。这个可以自己编译些小例子看看 ^_^
    以后C# 5的compiler-as-a-service的实现估计也是在Microsoft.CSharp.dll里了吧。

  83. 老赵
    admin
    链接

    老赵 2010-02-25 00:07:00

    @RednaxelaFX
    我比较关心System.Dynamic.dll和独立下载的DLR的区别是什么……

  84. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-25 00:22:00

    @Jeffrey Zhao
    System.Dynamic.dll只是COM interop的部分,在Codeplex版的DLR里对应的应该是Microsoft.Dynamic.dll吧;DLR核心还是在System.Core.dll里,对应Codeplex版是Microsoft.Scripting.Core.dll;Codeplex版还有Microsoft.Scripting.dll,这部分我不知道.NET Framework 4.0 RC与对应的IronPython是怎么安排的,还没装来看。

    整合到.NET Framework 4.0的标准库里的DLR会使用到一些新增的集合啊委托类型之类的,而Codeplex版/CLRv2版则在这些地方使用了DLR自己实现的对应物。最简单的例子,CLRv2版的DLR里Expression Tree v2是在Microsoft.Scripting.Ast命名空间里,而同一份代码在.NET 4里则是在System.Linq.Expressions命名空间里。代码是同一份,只是用了#if CLR2、#if SILVERLIGHT之类的条件编译去解决些类型冲突/类型缺失问题而已。
    Codeplex版在.NET Framework 4正式发布后仍然会持续开发和发布,所以将会有更多时间实现更多功能,例如更丰富的Expression Tree类型;而整合到标准库里的DLR代码得冻结在较早的版本上经过QA啊QC之类的过程。

  85. 老赵
    admin
    链接

    老赵 2010-02-25 00:56:00

    @RednaxelaFX
    看来要在CLR 2上使用dynamic的功能还是很不容易的,真要做起来还不如借助F#了……

  86. tomdeng[未注册用户]
    *.*.*.*
    链接

    tomdeng[未注册用户] 2010-02-25 09:24:00

    java 的这种泛型实现方式,也可以避免类爆炸啊

  87. 老赵
    admin
    链接

    老赵 2010-02-25 09:53:00

    @tomdeng
    什么叫类爆炸,会产生什么问题亚?

  88. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-25 10:51:00

    @Jeffrey Zhao
    CLR实现泛型的方式也是“编译时膨胀”,只不过CLR的“编译时”对用户来说是“运行时”而已。
    例如说List<T>是一个类型,会有对应的MethodTable和各个MethodDesc、EEClass等来支持它。而运行时不可能实例化List<T>,T必然要被替换为某个具体类型,例如说List<String>;这个List<String>也会有对应的MethodTable、MethodDesc、EEClass等数据结构来支持。虽说泛型参数为引用类型的泛型方法可以共用代码,但元数据是共享不了的,每实例化一个泛型类型都需要一整套元数据。

    C++的模板实例化很有可能被实现为每个具体类型(的组合)生成一套代码,比.NET的方式更吃空间。Java的擦除式泛型则在运行时全部使用raw-type,就没有类膨胀过多而引发的类爆炸问题。
    比较糟糕的是HotSpot因为一些历史原因把类型元数据放在GC堆的一块特殊区域里,称为PermGen,这个区域默认比较小,数据多了就会爆掉……其它JVM,例如JRockit,则会把类型元数据也放在普通GC堆上,回避PermGen爆掉的问题。

  89. tom deng
    *.*.*.*
    链接

    tom deng 2010-02-25 16:11:00

    @RednaxelaFX
    让我又学习了一把:)

  90. 老胡[未注册用户]
    *.*.*.*
    链接

    老胡[未注册用户] 2010-02-26 12:54:00

    初看一遍,楼主满有道理,但是回过头来想想,事情没有楼主说得那么夸张。
    RednaxelaFX的说法才是正解。java上实现标准泛型,难度有一点,当时绝对没有楼主说的那么夸张,JVM支持固然好,不支持直接通过javac做也是可以的。
    另外,关于膨胀问题,我不同意RednaxelaFX的看法。不觉得C++就比C#多,量级上我认为C++该比C#少,只不过一个你看得见,一个藏在底下,你看不见。

  91. 老胡[未注册用户]
    *.*.*.*
    链接

    老胡[未注册用户] 2010-02-26 17:55:00

    JAVA到现在还没有完全支持模板,只是在容器类上支持还真是奇怪。
    刚开始看楼主说法,觉得很有道理,但是回过头来仔细想想,又觉得不对。
    无论是类似C++的编译膨胀,还是目前采用的javac翻译消除方案,应该都能实现全面模板支持啊。
    以这个例子:
    public class Cache<TKey, TValue>
    {
    public static TValue Instance;
    }

    public class Factory
    {
    public static string Create<TKey>()
    {
    if (Cache<TKey, string>.Instance == null)
    {
    Cache<TKey, string>.Instance = // some expensive computation
    }

    return Cache<TKey, string>.Instance;
    }
    }
    我来尝试翻译看看
    ===============================
    public class Cache_TKey_TValue;
    {
    public static HashMap<Class,HashMap<Class,Object>> ClassStaticDataMap1;
    }

    public class Factory
    {
    public static string Create_TKey()
    {
    //由javac引入,运行时根据TKEY类型赋值的变量。
    Class Tkey;
    if(Cache_TKey_TValue.ClassStaticDataMap1[TKey][string.class] == null)
    {
    Cache_TKey_TValue.ClassStaticDataMap1[TKey][string.class] = //相关运算

    }
    Cache_TKey_TValue.ClassStaticDataMap1[TKey][string.class];
    }
    }
    ============================
    核心思想就是模板类实例化的时候建立对应的变量,用object实例代替class实例。java水平不行,上面代码可能有语法错误,意思就是这个意思了。
    从运行效率上肯定要慢,但是逻辑上是实现了的。
    请楼主和RednaxelaFX指正。
    据说java现在新版在引入什么闭包,壬答演算之类技术;为啥不顺手把模板支持再强化一下,奇怪?

  92. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-26 19:11:00

    @老胡
    C#泛型在经过C#编译器编译到MSIL后,泛型尚未被膨胀,声明泛型类型/方法的地方(declaration site)仍然只记录着占位符,而使用泛型类型的地方(use site,包括非泛型类型继承泛型类型的情况)也只是记录着泛型类型实例化的参数信息。实际的膨胀是由CLI实现在运行时进行的。这里就只用CLR作为CLI实现的代表来讨论。

    CLR在膨胀代码时,每遇到一个泛型类型实例化(不是对象实例化,是类型实例化)时就会需要生成对应的元数据对象去描述该实际类型,其中的方法如果尚未被编译,则在方法初次被调用(或其它强制触发JIT编译的方式)时会被JIT编译出代码和异常处理表、GC表等。其中,如果泛型参数类型全部都是引用类型,由于CLR使用指针来实现引用,而所有引用类型的指针都是等宽的,生成的代码就可以共享;如果泛型参数类型里有原始类型则会被单独实例化。举例来说,List<string>与List<Exception>中的方法会共享代码,而List<int>与List<char>之间则不会共享代码。

    C++的泛型在许多编译器的实现中不会共享任何代码,会为每种源码中遇到的每种参数组合实例化出一份代码;这些代码如果被内联的话问题还不大,没被内联的话就会显得非常冗余。
    观察这样的一份代码:

    template<typename T>
    class Foo {
    public:
      void __attribute ((__noinline__)) swap(T& t1, T& t2) {
        T temp = t1;
        t1 = t2;
        t2 = temp;
      }
    };
    
    class A { };
    class B { };
    
    int main() {
      A *a1 = new A, *a2 = new A;
      B *b1 = new B, *b2 = new B;
      
      Foo<A*> fooA;
      fooA.swap(a1, a2);
      
      Foo<B*> fooB;
      fooB.swap(b1, b2);
      
      int retCode = a1 - (a2 - (b1 - b2));
      
      delete a1;
      delete a2;
      delete b1;
      delete b2;
      
      return retCode;
    }

    (注意__noinline__阻止了swap的内联,否则得到的结果会不同*)

    用MinGW版GCC 4.3.0以g++ test.cpp -S -O2编译,得到的代码部分是:
    	.file	"test.cpp"
    	.section	.text$_ZN3FooIP1AE4swapERS1_S3_,"x"
    	.linkonce discard
    	.align 2
    	.p2align 2,,3
    .globl __ZN3FooIP1AE4swapERS1_S3_
    	.def	__ZN3FooIP1AE4swapERS1_S3_;	.scl	2;	.type	32;	.endef
    __ZN3FooIP1AE4swapERS1_S3_:
    LFB4:
    	pushl	%ebp
    LCFI0:
    	movl	%esp, %ebp
    LCFI1:
    	pushl	%ebx
    LCFI2:
    	movl	12(%ebp), %edx
    	movl	16(%ebp), %ecx
    	movl	(%edx), %ebx
    	movl	(%ecx), %eax
    	movl	%eax, (%edx)
    	movl	%ebx, (%ecx)
    	popl	%ebx
    	leave
    	ret
    LFE4:
    	.section	.text$_ZN3FooIP1BE4swapERS1_S3_,"x"
    	.linkonce discard
    	.align 2
    	.p2align 2,,3
    .globl __ZN3FooIP1BE4swapERS1_S3_
    	.def	__ZN3FooIP1BE4swapERS1_S3_;	.scl	2;	.type	32;	.endef
    __ZN3FooIP1BE4swapERS1_S3_:
    LFB5:
    	pushl	%ebp
    LCFI3:
    	movl	%esp, %ebp
    LCFI4:
    	pushl	%ebx
    LCFI5:
    	movl	12(%ebp), %edx
    	movl	16(%ebp), %ecx
    	movl	(%edx), %ebx
    	movl	(%ecx), %eax
    	movl	%eax, (%edx)
    	movl	%ebx, (%ecx)
    	popl	%ebx
    	leave
    	ret
    LFE5:
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.text
    	.p2align 2,,3
    .globl _main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    LFB3:
    	leal	4(%esp), %ecx
    LCFI6:
    	andl	$-16, %esp
    	pushl	-4(%ecx)
    LCFI7:
    	pushl	%ebp
    LCFI8:
    	movl	%esp, %ebp
    LCFI9:
    	pushl	%ebx
    LCFI10:
    	pushl	%ecx
    LCFI11:
    	subl	$32, %esp
    LCFI12:
    	call	___main
    	subl	$12, %esp
    	pushl	$1
    LCFI13:
    	call	__Znwj
    	movl	%eax, -16(%ebp)
    	movl	$1, (%esp)
    	call	__Znwj
    	movl	%eax, -20(%ebp)
    	movl	$1, (%esp)
    	call	__Znwj
    	movl	%eax, -24(%ebp)
    	movl	$1, (%esp)
    	call	__Znwj
    	movl	%eax, -28(%ebp)
    	leal	-20(%ebp), %eax
    	pushl	%eax
    	leal	-16(%ebp), %eax
    	pushl	%eax
    	leal	-9(%ebp), %eax
    	pushl	%eax
    LCFI14:
    	call	__ZN3FooIP1AE4swapERS1_S3_
    	leal	-28(%ebp), %eax
    	pushl	%eax
    	leal	-24(%ebp), %eax
    	pushl	%eax
    	leal	-10(%ebp), %eax
    	pushl	%eax
    	call	__ZN3FooIP1BE4swapERS1_S3_
    	movl	-16(%ebp), %edx
    	movl	-28(%ebp), %eax
    	subl	-24(%ebp), %eax
    	addl	-20(%ebp), %eax
    	movl	%edx, %ebx
    	subl	%eax, %ebx
    	addl	$28, %esp
    	pushl	%edx
    LCFI15:
    	call	__ZdlPv
    	popl	%ecx
    	pushl	-20(%ebp)
    	call	__ZdlPv
    	popl	%edx
    	pushl	-24(%ebp)
    	call	__ZdlPv
    	popl	%eax
    	pushl	-28(%ebp)
    	call	__ZdlPv
    	movl	%ebx, %eax
    	leal	-8(%ebp), %esp
    	popl	%ecx
    	popl	%ebx
    	leave
    	leal	-4(%ecx), %esp
    	ret

    其中__ZN3FooIP1AE4swapERS1_S3_与__ZN3FooIP1BE4swapERS1_S3_就是Foo<T>::swap(T&,T&)分别以A*与B*为模板参数类型实例化出来的代码。可以看到它们的内容完全一样,没有得到共享。

  93. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-26 19:12:00

    同样的情况在CLR里就会有多份不同的元数据但只有一份代码。

    using System;
    using System.Runtime.CompilerServices;
    
    class Foo<T> {
        [MethodImpl(MethodImplOptions.NoInlining)]
        public void Swap(ref T t1, ref T t2) {
            T temp = t1;
            t1 = t2;
            t2 = temp;
        }
    }
    
    class A { }
    class B { }
    
    sealed class Program {
        static void Main(string[] args) {
            var a1 = new A();
            var a2 = new A();
            var b1 = new B();
            var b2 = new B();
            
            var fooA = new Foo<A>();
            fooA.Swap(ref a1, ref a2);
            var fooB = new Foo<B>();
            fooB.Swap(ref b1, ref b2);
            
            Console.WriteLine("{0}{1}{2}{3}", a1, a2, b1, b2);
        }
    }

    关于CLR实现的代码共享,可以阅读这篇论文:Design and Implementation of Generics for the .NET Common Language Runtime,从第7页开始有代码的设计描述。

    关于C++模板、Java与C#泛型的比较,在《Shared Source CLI 2.0 Internals》的第6章,“Design Approach”里也有简单讨论,可以参考。要留意其中对“组件”、“元数据”、“运行时”等的描述。

    *:上面的C++代码例子用了__noinline__来阻止GCC在O2优化级别上将swap内联;如果不加这个声明,这个例子中就看不到Foo模板类对A与B分别的实例化,因为调用点本身被折叠掉了。考虑到实际C++项目中有许多使用了模板的地方都可以完全得到内联并优化掉冗余代码,实际的代码膨胀不会像不优化时那么严重(不优化时所有模板都会为所有在源码中出现过的模板参数组合的实例化)。但是CLR的方式则可以共享大量代码而只需要为不同泛型参数类型的组合创建不同的元数据,相对来说应该比C++方式带来的代码膨胀要小。

    @老胡

    老胡:
    JAVA到现在还没有完全支持模板,只是在容器类上支持还真是奇怪。


    这是个误解。Java是在语言级支持泛型,而不是只在库中某些特定类型上支持泛型的;Java自身所支持的泛型在JVM里不需要“模拟”。在JVM层面上,我前面的回复已经提到了,JVM需要支持读取泛型元数据,但JVM所支持的类型系统中没有泛型。
    这与ActionScript 3的Vector.<T>不同。ActionScript 3现在的实现就只支持一种泛型容器,Vector.<T>。原本在ECMAScript 4的草案中包含有Vector.<T>、Map.<K, V>、IteratorType.<K>、ControlInspector.<T>等标准库支持的泛型类型,并且用户可以自定义新的泛型类型;但ActionScript 3的实现却不允许用户自定义新的泛型类型,泛型容器也只有Vector.<T>一个。
    您的代码或许在Java 7能跑,在Java 7之前Java语法中“[]”只能用于数组的下标访问,不能用于别的类型……但是是的,您的思路行得通。“//由javac引入,运行时根据TKEY类型赋值的变量。”这个要是由编译器引入的话,就会出现在参数位置上了。Grasshopper在JVM上模拟C#泛型的基本思路就是类似这样的,由编译器添加隐含参数传入方法/构造器中。

    老胡:
    据说java现在新版在引入什么闭包,壬答演算之类技术;为啥不顺手把模板支持再强化一下,奇怪?


    Java的几种闭包方案都不涉及JVM的改动,跟Java泛型一样是语言层面的。泛型支持如果不在VM层面做就总是有点遗憾,要“强化”Java泛型就需要JVM的改进和支持,以及标准库的同步更新。至于为什么就不让JVM直接支持泛型,这个恐怕去问问Mark Reinhold比较合适 ^^

  94. 老胡
    *.*.*.*
    链接

    老胡 2010-02-26 22:19:00

    @RednaxelaFX:
    多谢指正
    编译膨胀我本来认为道理都是一样,但是C++是编译时进行操作,更可以优化,而.net是JIT时候进行操作,从JIT角度来看,首要目的是快,我以为他是用空间换时间了。没想到其又快空间又省。
    我想了一会,能够理解了,C#所有类都是从某个根继承,肯定有类似C++虚函数表的关系,List<string>与List<Exception>中如果有调用string或者exception某个方法,应该也是用类似虚函数表之类延后绑定技术实现的。这样同样实现的代码就可以调用不同的函数了。

    RednaxelaFX:
    这些代码如果被内联的话问题还不大


    这个我不理解,内联就是扩展代码,不是标准的空间换时间做法么?节省了一次函数调用开销,但是空间应该多了啊?

    RednaxelaFX:
    这是个误解。Java是在语言级支持泛型,而不是只在库中某些特定类型上支持泛型的


    JAVA的这个语言级别支持泛型我感觉还是很不够啊,只能支持到类似容器这种级别,我觉得老赵的两个例子应该都是标准的模板应用,可是java还不能支持。目前这种模板实现里面只能调用类型T中原来属于Object类中的方法。

    RednaxelaFX:
    。“//由javac引入,运行时根据TKEY类型赋值的变量。”这个要是由编译器引入的话,就会出现在参数位置上了


    这个是最近翻了翻ejb3.0的书,里面有谈到一些ejb注入的标签,差不多就是你调用某个方法时候有一个类变量就已经被系统自动赋值了,我就顺手这样写了。不过我感觉这种注入技术我感觉似乎有点违反了java的访问规则,连类中的private变量都可以这样注入。

  95. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-26 22:29:00

    c++中内联本身就是代码冗余,模板函数内联与否对代码膨胀影响不大。

  96. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-26 22:38:00

    老胡:
    目前这种模板实现里面只能调用类型T中原来属于Object类中的方法。


    java的泛型可以用通配符来指定类型参数T是某个类的子类或父类,类似于c#的泛型约束。当T是某个类的子类是,可以使用其父类的方法。

  97. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-26 22:48:00

    老胡:

    RednaxelaFX:
    这些代码如果被内联的话问题还不大


    这个我不理解,内联就是扩展代码,不是标准的空间换时间做法么?节省了一次函数调用开销,但是空间应该多了啊?


    单纯的内联只是代码膨胀,但是内联最大的好处并不是节省函数/方法调用的开销,而是把不太方便进行的过程间分析问题(inter-procedural analysis)变成了可以方便分析的过程内分析问题(intra-procedural analysis)。这样对其它优化非常有利,可以更有效的发现和删除冗余代码,所以内联后的代码未必总是会膨胀,有两种情况反而会变小:1、被内联的代码比调用序列还短;2、被内联的代码被发现是冗余代码而消除。
    前面我举的C++例子,如果不加__noinline__,那么GCC 4.3.0就能够完全不生成Foo<A>::swap与Foo<B>::swap实例化后的代码,这就是内联在节省代码上的一种表现。

    老胡:

    RednaxelaFX:
    。“//由javac引入,运行时根据TKEY类型赋值的变量。”这个要是由编译器引入的话,就会出现在参数位置上了


    这个是最近翻了翻ejb3.0的书,里面有谈到一些ejb注入的标签,差不多就是你调用某个方法时候有一个类变量就已经被系统自动赋值了,我就顺手这样写了。不过我感觉这种注入技术我感觉似乎有点违反了java的访问规则,连类中的private变量都可以这样注入。


    成员是可以注,但局部变量注不了。您的代码中TKey是个局部变量,这个除了以参数的形式传进来之外没什么别的好办法“注入”。

  98. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-26 23:10:00

    @RednaxelaFX
    不过内联也并非没有缺点啊。
    模板不过是类级别上的代码膨胀,一个模板函数只有在使用不同的类型参数的时候才会生成不同的代码。但是一个内联函数如果调用了成百上千次,其代码则会被复制成百上次,膨胀还是很惊人的。

  99. 老胡
    *.*.*.*
    链接

    老胡 2010-02-26 23:21:00

    幸存者:

    老胡:
    目前这种模板实现里面只能调用类型T中原来属于Object类中的方法。


    java的泛型可以用通配符来指定类型参数T是某个类的子类或父类,类似于c#的泛型约束。当T是某个类的子类是,可以使用其父类的方法。


    以下代码
    class A{
    public void func(void){
    //some code here
    }
    }
    class B{
    public void func(void){
    //some code here
    }
    }
    class Foo<T>{
    public void set(T x){x.func();}
    }
    类Foo在java1.5无法编译,报错
    method func() not found in class java.lang.Object
    以目前的javac翻译方法,T都被转型Object了,所以func方法根本找不到了,如果完整支持,要用反射机制把func查找出来。

  100. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-26 23:35:00

    @老胡
    你这段代码拿到c#中也编译不了。
    在java中可以这样

    class Foo<T extends A>{
        public void set(T x) { x.func(); }
    }
    

    在c#中可以这样
    class Foo<T> where T : A {
        public void Set(T x) { x.Func(); }
    }
    

  101. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-26 23:53:00

    这样的代码在c++中能编译通过是因为c++的模板是在编译期进行代码替换的,就像一个“高级宏”,因此只要类型参数T的实参有func函数就能编译通过。
    java和c#在编译期只有一份泛型类型的代码(c#在jit时对值类型泛型生成单独的代码,也就是膨胀法,引用类型则只为每一个泛型类型生成一个新的方法表,但是方法表中的方法入口地址都是一样的),当类型参数T没有任何约束时,编译器并不知道func()是否是类型实参的方法,因此不能编译通过。

  102. 老胡
    *.*.*.*
    链接

    老胡 2010-02-27 00:12:00

    @幸存者
    我习惯了C++写法,对java和c#具体语法是真不大熟。
    <T extends SomeClass>这种模板方式也就是一种多态方案了,主要也就是省了点转型代码。缺省<T>么就成<T extends Object>。不过C#既然有底层虚拟机对模板技术的支持,是不是应该具备了跳出这个约束的能力?让我回头再思考一下。

  103. 老胡
    *.*.*.*
    链接

    老胡 2010-02-27 12:21:00

    RednaxelaFX:
    成员是可以注,但局部变量注不了。您的代码中TKey是个局部变量,这个除了以参数的形式传进来之外没什么别的好办法“注入”。


    多谢指教,顺便请教一个问题。
    我最近在做一个并发程序,为减少持锁,研究了一下double check模式,从C++看到JAVA,看到说java上该模式不成立,原因在于jvm优化,new()不是原子的。new操作内部顺序可能是:分配内存,赋值,执行构造函数。构造函数还未完成发生线程切换就有问题了。那么我很奇怪两点
    1,jvm为何不能把顺序调整到最后一部才执行赋值,和C++一样。出于什么样的优化原理要把赋值这步提前?
    2,如果构造函数抛出异常,则赋值已经完成,即便非多线程环境一旦下次再访问该变量也有问题啊。如果jvm在构造函数抛出异常时候必须进行一串清理工作的话,包括取消之前的赋值,那这种乱序的优化反而是累赘了。

  104. 幸存者
    *.*.*.*
    链接

    幸存者 2010-02-27 13:52:00

    @老胡
    我插一句。
    据我所知,Sun的Hotspot JVM在1.5以后都可以保证构造函数执行完成之后再赋值。也就是说,在1.5之后的Hotspot JVM上double check是可以保证线程安全的。
    另外,乱序执行并不是JVM或者C++编译器所作的优化,或许JVM和C++编译器会作一部分乱序优化,但是double check的问题主要是CPU内部流水线进行的优化,与JVM和C++编译器无关。

  105. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2010-02-27 15:54:00

    @老胡
    应该说所有符合规范的Java 5及以后的JVM都必须遵循新的Java Memory Model(JMM),比老的JMM更严格。它特别规定了新的volatile语义,保证volatile read有acquire semantics,volatile write有release semantics,限制了乱序的程度。

    Java语言->class文件的编译器不会引起乱序相关问题。JVM内的JIT编译器则可能引起乱序问题。在编译器后端有一步是“指令调度”(instruction scheduling),这里有很多方面可以优化:尽量少读写内存,尽量让同一变量在寄存器里放久点,尽量少用点寄存器,尽量有效的利用指令级并行(instruction-level parallelism,ILP)之类的;为了达到这些目标,编译器会试图在保持语义的前提下改变指令的顺序。老的JMM对volatile语义规定得比较弱,没有规定要阻止volatile变量读写的乱序。新的JMM则收紧了约束,使编译器不但不能对volatile读写重新安排顺序,而且还必须保证它们在实际执行的时候不能够被CPU的乱序执行单元(out-of-order execution unit)。为此需要根据CPU的具体情况生成memory barrier或者叫memory fence。所以说编译器有可能引起乱序问题,而要阻止乱序也需要编译器的协助。

    在Java 5或以后的Java中要正确实现double checking应该使用volatile变量去保存被缓存的值,以保证读写顺序的正确性。

    具体就不多写了,已经有很多非常详细的相关文章。请参考它们:
    Java theory and practice: Fixing the Java Memory Model, Part 1
    Java theory and practice: Fixing the Java Memory Model, Part 2
    The "Double-Checked Locking is Broken" Declaration

  106. BabyBear
    *.*.*.*
    链接

    BabyBear 2010-03-04 10:33:00

    多了不说,就那一句
    每次提到语言的时候我总是忍不住骂Java是一门生产力低下,固步自封的语言—
    赵哥,找到知音了,眼泪哗哗的…………
    我先学的java后学的.net,心里一直都在想如果C#比java早1个月,不,只要能不晚一年出来,哪里还会有java阿。。最鄙视的就是java总是自诩跨平台,什么啊,不就是做了个linux下面的jvm么?net又不是做不出linux下面的clr,这种跨平台根本不是技术的先进,完全是因为ms要维护windows的地位而已。支持.net,支持C#!!!

  107. netwjx
    117.88.79.*
    链接

    netwjx 2010-03-30 18:36:03

    纯从语言层面, C#吸引眼球能力强大啊, 但是语法越是强大的语言越不容易掌握, 容易被滥用, 再加上C#有很多语法设计的不很巧妙(我是从javascript角度的感觉)

    语言的实现我不是很精通, java下的泛型实现确实很恼人, 到头来还是旧办法

    语言之外的环境

    大量学习dotnet平台的人如楼主所说 封闭

    我接触到的多数学习C#的人都很不注重架构, 不理解底层机制, 主要是太容易上手了, 但要深入始终要理解底层的, 这些学习时间一点也省不掉

    而那些不封闭,关注架构设计不是从C/C++过来的,就是同时学java ruby python....的, 环境风格对人的学习影响太大

    我相信楼主绝对不是从一开始学编程就从C#开始的

    一开始就学习C# dotnet平台的人, 只有少数人能突破出来, 那些突破出来的, 他的性格也是那种无所谓从什么编程语言开始的

  108. asdf
    113.106.106.*
    链接

    asdf 2010-08-04 13:49:24

    前面已经有很多评论指出博主的错误,本来我就不用说的了。 但是博主如此顽固,不更新博文。又让我看到了“Why java sucks and C# rocks”这种哗众取宠的标题。不得不再重提你的错误。

    把jvm当成x86,java当成c++。 既然c++可以在不改变x86指令集的情况下支持泛型(c#的泛型实现就是采用c++的方法) ,java就可以在不改变jvm的情况下提供类c++实现的泛型!!!

    不是能不能,而是有没有这样做!!!

    java的生产力低下?C#之父也不敢这样说!你装什么装。

    我可以写篇“why c# sucks and java rocks”,保证针对你的那个系列并有理有据。你敢不敢放在你博文下的see also?

  109. 老赵
    admin
    链接

    老赵 2010-08-05 19:46:06

    @asdf: java的生产力低下?C#之父也不敢这样说!你装什么装。

    我看过《Masterminds of Programming》,我感觉Anders就是认为Java生产力差么。我欢迎你来写“Why C# Sucks and Java Rocks”,只要有理有据绝对see also,我还可以帮忙推广,如果你愿意的话,嗯嗯。

    至于其他话题,比如泛型之类的,也欢迎你来谈更多。我也没觉得下面的回复指出我什么严重错误啊,回复里都是补充说明,比如说谁谁谁做了什么,呵呵。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我