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.泛型类型变量作为操作数使用
        不能使用操作符(+,-=,*,<等等)操作泛型类型变量


 

posted @ 2007-09-23 13:05  包建强  Views(1293)  Comments(0Edit  收藏  举报