• 开始聊聊C#泛型和委托(一)


    上篇文章简单的谈了谈C#编译器和JIT编译器,本来想接着写类型,对象,线程栈和托管堆在运行时的相互关系的,可惜讲解这部分采用画图的方式比较好,所以就先跳过了。


    在JAVA中,泛型只被JAVA编译器支持,并不被JVM所支持,也就是说没有定义新的字节码来表示泛型类型,自然在JVM里面也不会有新的指令来支持新的字节码。类比到.NET来说,也就是被C#编译器支持而不被CLR所支持。这样就产生了很多有趣的问题。我们都知道我们的代码都要经过编译器的翻译改动,JAVA中的泛型就是JAVA编译器采用类型擦除的方式来实现泛型的。定义的泛型类型,都自动提供了一个相应的原始类型(raw type)原始类型的名字就是删去类型参数后的泛型名,擦出掉类型变量,并替换为限定类型(无限定的变量用Object),可以看做是语法糖吧。比如:

    public class MyHashMap<TKey, TValue> {
        private HashMap<TKey, TValue> m_map = new HashMap<TKey, TValue>();
        
        public TValue get(TKey key) {
            return this.m_map.get(key);
        }
        
        public void put(TKey key, TValue value) {
            this.m_map.put(key, value);
        }
    
        public static void main(String[] args) {
            MyHashMap<String, Integer> map = new MyHashMap<String, Integer>();
            map.put("Hello", 5);
            int i = map.get("Hello");
        }    
    }
    



    编译成字节码后,就成了下面这个样子(这里还用JAVA代码来表示)

    public class MyHashMap {
    private HashMap m_map = new HashMap();

    public Object get(Object key) {
    return this.m_map.get(key);
    }

    public void put(Object key, Object value) {
    this.m_map.put(key, value);
    }

    public static void main(String[] args) {
    MyHashMap map
    = new MyHashMap();
    map.put(
    "Hello", 5);
    int i = (Integer)map.get("Hello");
    }
    }

    好吧,看到Object,我承认我又想起装箱了,可以看出Java中的泛型没有解决装箱问题。

    由于JVM并不知道泛型类型,所以JAVA中就是以JAVA编译器的语法糖的形式来表现的。当初我刚接触JAVA的时候,的确会被下面几种错误弄得很困惑。

    public class MyClass<SomeType> {
        public static void myMethod(Object item) {
            if (item instanceof SomeType) { // 报错
                ...
            }
            SomeType st = new SomeType(); // 报错
            SomeType[] myArray = new SomeType[10]; // 报错
        }
    }
    




    在这里我们可以想一下,到底怎么样才算真正的支持泛型呢?在.NET中,最终是由CLR根据元数据来执行IL代码,因此,可以很容易理解:

    1.IL中一定会有一个新指令来识别“类型参数”。

    2.我们知道类型和方法的定义在元数据表中都会有相应的表示,因此为了支持泛型,元数据的格式也会有所改动。

    3.修改JIT编译器来执行新的IL指令。

    也就是说,泛型类型定义能够完整的编译为MSIL类型。

    泛型类型的运行大概的流程如下:

    C#编译器生成IL和元数据,表示泛型类定义,JIT编译器则会把泛型类型定义与一系列的类型参数组合起来。

    具体点来说,IL为初始化某个泛型类型的实例预留了占位符,JIT编译器会在运行的时候,生成机器代码的时候“补全定义”。JIT把相应的IL代码编译成X86指令,同时优化。优化什么内容了呢?比如,在类型参数是引用类型的时候,就能使用相同的机器代码来表示。为啥是引用类型而不是值类型呢?因为引用类型基本上都是指针,本质上来讲结构都是一样的。

    这里又要谈一下类加载。JIT不是在某个类加载时就为其生成完整的X86指令,而是仅在类中的每个方法被第一次调用的时候才开始编译的。(我现在觉得应该先讲讲类型,对象,线程栈和托管堆在运行时的相互关系比较好)。这样,就会先在IL代码上执行一个占位符替换步骤,替换成具体类型,随后再像普通类一样按需编译。


    好吧,你可以看出,在执行之前占位符被替换成具体类型了,因此泛型的匹配度是相当高的。应该说就是精确匹配。这个会影响什么地方呢?在方法重载的时候就会有体现了。对于一个派生于MyBase的对象来说,WriteMEsaage<T>(T obj)要比WriteMEsaage(MyBase obj)在重载匹配上更优先。因为通过将T替换成MyDerived编译器就可以完成一次“精确匹配”,而WriteMEsaage(MyBase obj)则还需要一次隐式转换。于是泛型方法更有优势,除非在调用时进行显式类型转换。下面用代码说明:

    public class MyBase
    
    {
    
    }
    
     
    
    public class MyDerived : MyBase
    
    {
    
        #region IMessageWriter Members
    
        void IMessageWriter.WriteMessage()
    
        {
    
            Console.WriteLine("Inside MyDerived.WriteMessage");
    
        }
    
        #endregion
    
    }
    
     
    
    class Program
    
    {
    
        static void WriteMessage(MyBase b)    {
    
            Console.WriteLine("Inside WriteMessage(MyBase)");
    
        }
    
        static void WriteMessage<T>(T obj)
    
        {
    
            Console.Write("Inside WriteMessage<T>(T):  ");
    
            Console.WriteLine(obj.ToString());
    
        }
    
        static void Main(string[] args)
    
        {
    
            MyDerived d = new MyDerived();
    
            Console.WriteLine("Calling Program.WriteMessage");
    
            WriteMessage(d); //让编译器推断使用哪个匹配方法
    
            Console.WriteLine();
    
            Console.WriteLine("Cast to base object");
    
            WriteMessage((MyBase)d);
    
            Console.WriteLine();
    
        }
    
    }
    

    因此当你想支持某一类及其所有派生类时,基于基类创建泛型并不是最好的选择。同样的,基于接口也是如此。

    那么我想针对,这时就需要通过运行时来判断了,当然,这并不是最好的解决方案,虽然对调用者屏蔽了具体的实现,但同时会带运行时检查的开销。

    Static void WriteMessage<T>(T obj){
    
             If(obje is MyBase){
    
                      WriteMessage(obj as MyBase);  //显式类型转换
    
             }else {
    
                      Conslole.Write(“Invoke WriteMessage<T>”)
    
             }
    
    }
    

  • 相关阅读:
    JAVA常见算法题(二十六)
    JAVA常见算法题(二十五)
    springBoot框架的一些概念
    js延时函数setTimeout
    JAVA常见算法题(二十四)
    JAVA常见算法题(二十三)
    JAVA常见算法题(二十二)
    JAVA常见算法题(二十一)
    JAVA常见算法题(二十)
    JAVA常见算法题(十九)
  • 原文地址:https://www.cnblogs.com/lwzz/p/2078576.html
Copyright © 2020-2023  润新知