zoukankan      html  css  js  c++  java
  • CLR笔记:16.泛型

    泛型:支持值类型和引用类型,不支持枚举。
                没有泛型属性。

    泛型的好处:
        源代码保护。使用泛型算法不需要访问算法的源码——相对于C++模板
        类型安全——相对于ArrayList
        更加清晰的源码——不需要拆箱,显示转换
        更佳的性能——不用装箱。测试:循环1000万次,泛型List<T>与ArrayList分别用时0.1s和2s

    16.1    FCL中的泛型
    List<T> 取代ArrayList
    Directory<TKey, TValue>取代HashTable
    Stack<T>,Queue<T>分别取代Stack,Queue

    IList,IDirectory,ICollection,IEnumerator,IEnumerable,IComparer,IComparable分别由相应的泛型接口(加上<T>)

    16.3    泛型基础结构
    这一节的前言很有意思:如何在已有的CLR中添加泛型:
        创建新的IL泛型指令
        修改元数据格式,以支持泛型参数/类型/方法
        修改各种语言C#/VB.NET
        修改编译器csc,使之生成新的IL泛型指令/元数据
        修改JITer,使之可以处理新的IL泛型指令
        创建新的反射成员:泛型参数/类型/方法
        修改调试器
        修改vs2005智能感知

    CLR为应用程序使用的每个类型创建一个内部数据结构,称为“类型对象”。
    具有泛型类型参数的一个类型class<T>,仍然是一个类型,也具有一个类型对象,称为“开放式类型”,CLR禁止构造开放式类型的实例;
    当T是一个实际类型时class<Guid>,称为“封闭式类型”,CLR中可以创建封闭式类型的实例。
    语法Activator.Creator(type):根据type获取其一个实例。有
        typeof(Dictionary< , >)    //运行期会报错,使用了“开放式类型”

        t = typeof(Dictionary<String , String>)    
        Console.WriteLine(t.ToString());    //输出  System.Collections.Generic.Dictionary`2[System.String,System.String],这就是泛型Dictionary<String , String>的类型,在IL中也生成诸如Dictionary`2的方法,这里,2表示类型参数的数量。

    每个封闭式类型都有自己的静态字段,也就是说,List<String>和List<Int32>具有各自的静态字段和cctor。
    在泛型上定义cctor的目的:为类型参数加限制条件。例如,希望泛型只用于处理enum——解决了无法用约束控制Enum:

        internal class GenericOnlyForEnum<T>
        
    {
            
    static GenericOnlyForEnum()
            
    {
                
    if (!typeof(T).IsEnum)
                
    {
                    
    throw new Exception();
                }

            }

        }

    泛型类型与继承
    由于List<T>从System.Object派生,所以List<String>也是从System.Object派生。

    书中演示了一个例子——定义并使用一个链表节点类,很值得琢磨,原理就是利用上面这句话。
    方法1:

        class Node<T>
        
    {
            
    public T m_data;
            
    public Node<T> m_next;

            
    public Node(T data) : this(data, null{ }

            
    public Node(T data, Node<T> next)
            
    {
                m_data 
    = data;
                m_next 
    = next;
            }

        }


        
    public static class Program
        
    {
            
    static void Main()
            
    {
                Node
    <Char> head = new Node<char>('C');
                head 
    = new Node<char>('B', head);
                head 
    = new Node<char>('A', head);
            }

        }

    评论:这个方法不灵活,只能构造同一种泛型Node<char>。

    其实呢,是因为Node<T>这个类定义的太大了,可以一拆为二:Node类和TypeNode<T>派生类,其中Node类只记录next节点。而在其子类TypeNode<T>中,设置具体数据,如下方法2:

        class Node
        
    {
            
    protected Node m_next;

            
    public Node(Node next)
            
    {
                m_next 
    = next;
            }

        }


        
    class TypeNode<T> : Node
        
    {
            
    public T m_data;

            
    public TypeNode(T data) : this(data, null{ }

            
    public TypeNode(T data, Node next)
                : 
    base(next)
            
    {
                m_data 
    = data;
            }

        }

    这时候,就可以任意设置数据了,因为这次head声明为Node基类型,所以可以改变其子类:

            static void Main()
            
    {
                Node head 
    = new TypeNode<Char>('A');
                head 
    = new TypeNode<DateTime>(DateTime.Now, head);
            }

    泛型类型同一性:
    using XXX = System.Collections.Generic.List<System.DateTime>;
        接下来就可以使用XXX来代替List<System.DateTime>
        ——这只是对封闭性类型而言的,一种简写

    代码爆炸:
    对于泛型,CLR为每种不同的方法/类型组合生成本地代码,乘法原理——Assembly越来越大
    由此,CLR有专门优化措施:
        对于引用类型的类型实参,只编译一次,以后可以共享代码——因为都是指针,可以看作相同类型;对于值类型,不能共享,还是要“爆炸”的。

    16.4    泛型接口
    以FCL中的IEnumerator<T>为例:

        public interface IEnumerator<T> : IDisposable, IEnumerable
        
    {
            T Current 
    get;}
        }

    有两种实现方式:

        //1.指定T
        class A : IEnumerable<String>
        
    {
            
    private String current;

            
    public String Current get return current; } }
        }


        
    //2.不指定T
        class B : IEnumerable<T>
        
    {
            
    private T current;

            
    public T Current get return current; } }
        }

    16.5    泛型委托
    泛型委托允许值类型实例传递给回调方法时不装箱。

    16.6    泛型方法
    如果泛型类中有泛型方法,则各自声明的类型参数不能重名。
    泛型类的ctor等于同名非泛型类的ctor
    使用泛型方法和ref进行Swap操作

    类型推导:自动判断泛型方法要使用的类型,如下:

            static void Swap<T>(ref T o1, ref T o2) { }

    可以直接放入两个Int32:

                Int32 n1 = 1, n2 = 2;
                Swap(
    ref n1, ref n2);

    但是,以下使用会在编译期报错,即使是实际引用相同的类型,但是推导是根据声明类型来判断的:

                String s1 = "s1";
                Object s2 
    = "s2";
                Swap(
    ref s1, ref s2);

    泛型方法重载:

            static void Display(String s)
            
    {
                Console.WriteLine(s);
            }


            
    static void Display<T>(T o)
            
    {
                Display(o.ToString());
            }

    这时,调用方法就有学问了:
                Display("s1");
                编译器总是优先选择更显示的匹配,于是以上语句直接调用非泛型方法;否则会陷入死循环。

    16.7    泛型和其他成员
    C#中,属性/索引/事件/操作符方法/ctor/finalzer不能有泛型方式。
    允许有的:class/struct/interface/funcction/delegate

    16.8    可验证性和限制
    在泛型中,类型参数T,只能使用System.Object的方法,比如说ToString();不能使用其他任何方法,因为不知道T类型是否支持该方法。
    使用where约束,从而可以使用T的更多方法:
    where T:Icomparable<T>    告诉编译器,T表示的类型,必须实现相应的Icomparable<T>接口,从而T类型实例可以使用该接口的CompareTo方法。
    where可以用于class和function

    泛型方法重载原则:只能基于类型参数的个数进行重载,个数为0表示非泛型方法。

    对于class,可以有:

        class AType { }
        
    class AType<T> { }
        
    class AType<T1, T2> { }

        
    //class AType<T1, T2> where T : IComparable<T> { }

    被注释的类不可以再声明——class也是根据类型参数的个数,但是不考虑where约束的有无

    对于泛型虚方法,重写的版本必须指定相同数量的参数类型,会继承基类方法的约束,但是不可以再指定任何新的约束。

    3种约束:
        1.主要约束:class和struct,分别约束类型参数为引用类型和值类型

        class ForStruct<T> where T : struct
        
    {
            
    public T Test()
            
    {
                
    return new T();     //只有值类型可以new T();
            }

        }


        
    class ForClass<T> where T : class
        
    {
            
    public void Test()
            
    {
                T temp 
    = null;      //只有引用类型可以使用null
            }

        }

    对于T:class约束,T可以是类/接口/委托/数组类型

        2.次要约束:接口约束和裸类型约束
            接口约束见14章笔记,这里主要讲裸类型约束。
            裸类型约束,在类型参数间存在一个关系where T1:T2,这里可以写为where String:String,即兼容于自身

        3.构造器约束:类型参数一定实现了public的无参构造器:where  T:new()
            不能同时指定构造器约束和struct约束——这是多余的,因为所有值类型都隐式实现public无参构造器
            上面提到:只有值类型可以new T(); 但是,如果有了new()约束,引用类型也可以使用new T()了,因为肯定有构造器,所以new的动作总是可以执行。示例如下:

        public class ConstructConstraint<T> where T : new()
        
    {
            
    public static T Factory()
            
    {
                
    return new T();
            }

        }


        
    public static class Program
        
    {
            
    static void Main()
            
    {
                ConstructConstraint
    <A>.Factory();
            }

        }

    其他可验证问题:
        1.不能显示将泛型类型变量转型,要先转为Object,再显示转换;对于值类型,要使用as操作符来控制运行期错误

            static void CastingGeneric<T>(T obj)
            
    {
                
    //Int32 x = (Int32)obj;     不能这么写
                
                
    //值类型,应该这么写
                Int32 x = (Int32)(Object)obj;

                
    //引用类型,最好这么写
                String s = obj as String;
            }

        2.使用default关键字来为泛型变量设置默认值:
            T temp = default(T);    //null或者0

        3.将泛型变量与null进行比较
            对于引用类型,无论是否被约束,都可以使用==和!=比较null
            对于值类型,如果未被约束,则永远为null,因为if(obj==null)永远为真,这里并不会报错;
                                    如果被约束为struct,则不能比较null,因为if(obj==null)结果永远为真,但是这里会报错。
        
        4.两个泛型类型变量的比较,使用==和!=
            引用类型是可以比较的;
            两个值类型不可以使用==比较,除非重载了==操作符。
            如果未被约束,则不能比较,除非重载了==操作符,约束为引用可以比较。

        5.泛型类型变量作为操作数使用
            不能使用操作符(+,-=,*,<等等)操作泛型类型变量


     

  • 相关阅读:
    Tina系统的安装
    检查有无相机的权限
    BarEasy打印小程序_CS
    js的websocket
    生成GUID
    读取excel数据到数据库里
    字符串trim
    使用 runOnUiThread在线程内更新UI
    PdfDocument生成PDF,总是产生空文件
    打印36进制的条码序列号
  • 原文地址:https://www.cnblogs.com/Jax/p/903182.html
Copyright © 2011-2022 走看看