即”算法重用”,对于泛型可谓是又爱又恨

   
C#2.0引进了泛型这几个特性,由于泛型的引进,在断定程度上相当大的升高了C#的肥力,能够成功C#1.0时索要编写制定复杂代码才得以变成的一对作用。可是作为开荒者,对于泛型可谓是又爱又恨,爱的是其有力的成效,以及该本性带来的频率的进级,恨的是泛型在纷纷的时候,会议及展览现极度复杂的语法结构。这种复杂不唯有是对此初学者,对于某个有付出经历的.NET开采者,也是贰个不那么轻易理解的特征。

  泛型(generic)是CLXC90和编制程序语言提供一种极度体制,它匡助另一种形式的代码重用,即”算法重用”。

   接下来大家来精晓一下C#2.0参预的表征:泛型。

  轻易地说,开辟人士先定义好叁个算法,比如排序、寻觅、沟通等。不过定义算法的开辟职员并不设定该算法要操作什么数据类型;该算法可普及地应用于不相同档期的顺序的靶子。然后,另多个开垦职员只要内定了算法要操作的现实数据类型,就足以采纳这么些现有的算法了。

一.泛型的骨干特色概述:

   
在骨子里项目支出中,任何API只要将object作为参数类型和重回类型应用,就恐怕在某些时候提到强类型转换。提到强类型转变,预计相当多开辟者第一感应正是“效用”那一个次,对于强类型的利害首要看使用者利用的境遇,天底下未有断然的坏事,也尚无相对的孝行,有关强类型的主题材料不是此次的根本,不做要紧介绍。

   
泛型是CLR和C#提供的一种特别体制,接济另一种情势的代码重用,即“算法重用”。泛型达成了项目和章程的参数化,泛型类型和措施也能够让参数告诉使用者利用什么项目。

   
泛型所带来的功利:越来越好的编写翻译时检查,越来越多在代码中能间接表现的音讯,更加的多的IDE扶助,越来越好的性质。大概有人会疑窦,为何泛型会带来这么多好处,使用一个不能够分别差别门类的常规API,也正是在一个动态情状中拜谒十分API。

   
CL中华V允许创建泛型引用和泛型值类型,可是分裂意创设泛型枚举,况且CL凯雷德允许制造泛型接口和泛型委托,CLR允许在援引类型、值类型或接口中定义泛型方法。定义泛型类型或方法时,为品种内定了别样变量(如:T)都称为类型参数。(T是三个变量名,在源代码中可见采用多少个数据类型的任何职责,都能够使用T)在C#中泛型参数变量要么成为T,要么至少一大写T开端。

  泛型有两种展现格局:泛型类型泛型方法

二.泛型类、泛型接口和泛型委托概述:

  泛型类型:大非常多算法都封装在三个种类中,CL福睿斯允许创立泛型援引类型和泛型值类型,但区别意成立泛型枚举类型。除此而外,CLENCORE还允许创制泛型接口和泛型委托。

   1.泛型类:

   
 泛型类型照旧是项目,所以能够从其它项目派生。使用贰个泛型类型并点名项目实参时,实际是在CL陆风X第88中学定义二个新类型对象,新品类对象是从泛型派生自的可怜类型派生的。使用泛型类型参数的二个情势在基尼险那多少个JIT编写翻译时,CLEscort获取IL,用内定的门类实参举行替换,然后创造伏贴的本地代码。

   
若无为泛型类型参数提供项目实参,那就么正是未绑定泛型类型。假若内定了花色实参,该项目便是已构造类型。已构造类型能够是开荒或密闭的,开辟项目还隐含八个类ixngcanshu,而密闭类型则不是付出的,类型的每一个部分都是由此可见的。全数代码实际都以在三个密封的已构造类型的光景文中实施。

 
 泛型类在.NET的利用主要在会集类中,大多数集结类在System.Collections.Generic和System.Collections.ObjectModel类中。上边轻巧的牵线一种泛型集合类:

   
 (1).SynchronizedCollection:提供叁个线程安全集合,个中积累泛型参数所钦定项目标对象作为成分.

 [ComVisible(false)]
  public class SynchronizedCollection<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
  {
    /// <summary>
    /// 初始化 <see cref="T:System.Collections.Generic.SynchronizedCollection`1"/> 类的新实例。
    /// </summary>
    public SynchronizedCollection();
    /// <summary>
    /// 通过用于对线程安全集合的访问进行同步的对象来初始化 <see cref="T:System.Collections.Generic.SynchronizedCollection`1"/> 类的新实例。
    /// </summary>
    /// <param name="syncRoot">用于对线程安全集合的访问进行同步的对象。</param><exception cref="T:System.ArgumentNullException"><paramref name="syncRoot"/> 为 null。</exception>
    public SynchronizedCollection(object syncRoot);
    /// <summary>
    /// 使用指定的可枚举元素列表和用于对线程安全集合的访问进行同步的对象来初始化 <see cref="T:System.Collections.Generic.SynchronizedCollection`1"/> 类的新实例。
    /// </summary>
    /// <param name="syncRoot">用于对线程安全集合的访问进行同步的对象。</param><param name="list">用于初始化线程安全集合的元素的 <see cref="T:System.Collections.Generic.IEnumerable`1"/> 集合。</param><exception cref="T:System.ArgumentNullException"><paramref name="syncRoot"/> 或 <paramref name="list"/> 为 null。</exception>
    public SynchronizedCollection(object syncRoot, IEnumerable<T> list);
    /// <summary>
    /// 使用指定的元素数组和用于对线程安全集合的访问进行同步的对象来初始化 <see cref="T:System.Collections.Generic.SynchronizedCollection`1"/> 类的新实例。
    /// </summary>
    /// <param name="syncRoot">用于对线程安全集合的访问进行同步的对象。</param><param name="list">用于初始化线程安全集合的 <paramref name="T"/> 类型元素的 <see cref="T:System.Array"/>。</param><exception cref="T:System.ArgumentNullException"><paramref name="syncRoot"/> 或 <paramref name="list"/> 为 null。</exception>
    public SynchronizedCollection(object syncRoot, params T[] list);
    /// <summary>
    /// 将项添加到线程安全只读集合中。
    /// </summary>
    /// <param name="item">要添加到集合的元素。</param><exception cref="T:System.ArgumentException">设置的值为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    public void Add(T item);
    /// <summary>
    /// 从集合中移除所有项。
    /// </summary>
    public void Clear();
    /// <summary>
    /// 从特定索引处开始,将集合中的元素复制到指定的数组。
    /// </summary>
    /// <param name="array">从集合中复制的 <paramref name="T "/>类型元素的目标 <see cref="T:System.Array"/>。</param><param name="index">复制开始时所在的数组中的从零开始的索引。</param>
    public void CopyTo(T[] array, int index);
    /// <summary>
    /// 确定集合是否包含具有特定值的元素。
    /// </summary>
    /// 
    /// <returns>
    /// 如果在集合中找到元素值,则为 true;否则为 false。
    /// </returns>
    /// <param name="item">要在集合中定位的对象。</param><exception cref="T:System.ArgumentException">设置的值为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    public bool Contains(T item);
    /// <summary>
    /// 返回一个循环访问同步集合的枚举数。
    /// </summary>
    /// 
    /// <returns>
    /// 一个 <see cref="T:System.Collections.Generic.IEnumerator`1"/>,用于访问集合中存储的类型的对象。
    /// </returns>
    public IEnumerator<T> GetEnumerator();
    /// <summary>
    /// 返回某个值在集合中的第一个匹配项的索引。
    /// </summary>
    /// 
    /// <returns>
    /// 该值在集合中的第一个匹配项的从零开始的索引。
    /// </returns>
    /// <param name="item">从集合中移除所有项。</param><exception cref="T:System.ArgumentException">设置的值为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    public int IndexOf(T item);
    /// <summary>
    /// 将一项插入集合中的指定索引处。
    /// </summary>
    /// <param name="index">要从集合中检索的元素的从零开始的索引。</param><param name="item">要作为元素插入到集合中的对象。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception><exception cref="T:System.ArgumentException">设置的值为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    public void Insert(int index, T item);
    /// <summary>
    /// 从集合中移除指定项的第一个匹配项。
    /// </summary>
    /// 
    /// <returns>
    /// 如果从集合中成功移除了项,则为 true;否则为 false。
    /// </returns>
    /// <param name="item">要从集合中移除的对象。</param>
    public bool Remove(T item);
    /// <summary>
    /// 从集合中移除指定索引处的项。
    /// </summary>
    /// <param name="index">要从集合中检索的元素的从零开始的索引。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception>
    public void RemoveAt(int index);
    /// <summary>
    /// 从集合中移除所有项。
    /// </summary>
    protected virtual void ClearItems();
    /// <summary>
    /// 将一项插入集合中的指定索引处。
    /// </summary>
    /// <param name="index">集合中从零开始的索引,在此处插入对象。</param><param name="item">要插入到集合中的对象。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception><exception cref="T:System.ArgumentException">设置的值为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    protected virtual void InsertItem(int index, T item);
    /// <summary>
    /// 从集合中移除指定 <paramref name="index"/> 处的项。
    /// </summary>
    /// <param name="index">要从集合中检索的元素的从零开始的索引。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception>
    protected virtual void RemoveItem(int index);
    /// <summary>
    /// 使用另一项替换指定索引处的项。
    /// </summary>
    /// <param name="index">要替换的对象的从零开始的索引。</param><param name="item">要替换的对象。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception>
    protected virtual void SetItem(int index, T item);
    /// <summary>
    /// 返回一个循环访问同步集合的枚举数。
    /// </summary>
    /// 
    /// <returns>
    /// 一个 <see cref="T:System.Collections.Generic.IEnumerator`1"/>,用于访问集合中存储的类型的对象。
    /// </returns>
    IEnumerator IEnumerable.GetEnumerator();
    /// <summary>
    /// 从特定索引处开始,将集合中的元素复制到指定的数组。
    /// </summary>
    /// <param name="array">从集合中复制的 <paramref name="T"/> 类型元素的目标 <see cref="T:System.Array"/>。</param><param name="index">复制开始时所在的数组中的从零开始的索引。</param>
    void ICollection.CopyTo(Array array, int index);
    /// <summary>
    /// 向集合中添加一个元素。
    /// </summary>
    /// 
    /// <returns>
    /// 新元素的插入位置。
    /// </returns>
    /// <param name="value">要添加到集合中的对象。</param>
    int IList.Add(object value);
    /// <summary>
    /// 确定集合是否包含具有特定值的元素。
    /// </summary>
    /// 
    /// <returns>
    /// 如果在集合中找到元素 <paramref name="value"/>,则为 true;否则为 false。
    /// </returns>
    /// <param name="value">要在集合中定位的对象。</param><exception cref="T:System.ArgumentException"><paramref name="value"/> 不是集合所含类型的对象。</exception>
    bool IList.Contains(object value);
    /// <summary>
    /// 确定集合中某个元素的从零开始的索引。
    /// </summary>
    /// 
    /// <returns>
    /// 如果在集合中找到,则为 <paramref name="value"/> 的索引;否则为 -1。
    /// </returns>
    /// <param name="value">集合中要确定其索引的元素。</param>
    int IList.IndexOf(object value);
    /// <summary>
    /// 将某个对象插入到集合中的指定索引处。
    /// </summary>
    /// <param name="index">从零开始的索引,将在该位置插入 <paramref name="value"/>。</param><param name="value">要在集合中插入的对象。</param><exception cref="T:System.ArgumentOutOfRangeException">指定的 <paramref name="index"/> 小于零或大于集合中的项数。</exception><exception cref="T:System.ArgumentException">设置的 <paramref name="value"/> 为 null,或者不是集合的正确泛型类型 <paramref name="T"/>。</exception>
    void IList.Insert(int index, object value);
    /// <summary>
    /// 从集合中移除作为元素的指定对象的第一个匹配项。
    /// </summary>
    /// <param name="value">要从集合中移除的对象。</param>
    void IList.Remove(object value);

  }

   (2).KeyedByTypeCollection:提供贰个成团,该集合的项是用作键的花色。

 [__DynamicallyInvokable]
  public class KeyedByTypeCollection<TItem> : KeyedCollection<Type, TItem>
  {
    /// <summary>
    /// 初始化 <see cref="T:System.Collections.Generic.KeyedByTypeCollection`1"/> 类的新实例。
    /// </summary>
    public KeyedByTypeCollection();
    /// <summary>
    /// 根据指定的对象枚举初始化 <see cref="T:System.Collections.Generic.KeyedByTypeCollection`1"/> 类的新实例。
    /// </summary>
    /// <param name="items">泛型类型 <see cref="T:System.Object"/> 的 <see cref="T:System.Collections.Generic.IEnumerable`1"/>,用于初始化集合。</param><exception cref="T:System.ArgumentNullException"><paramref name="items"/> 为 null。</exception>
    public KeyedByTypeCollection(IEnumerable<TItem> items);
    /// <summary>
    /// 返回集合中第一个具有指定类型的项。
    /// </summary>
    /// 
    /// <returns>
    /// 如果为引用类型,则返回类型 <paramref name="T"/> 的对象;如果为值类型,则返回类型 <paramref name="T"/> 的值。 如果集合中不包含类型 <paramref name="T"/> 的对象,则返回类型的默认值:如果是引用类型,默认值为 null;如果是值类型,默认值为 0。
    /// </returns>
    /// <typeparam name="T">要在集合中查找的项的类型。</typeparam>
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public T Find<T>();
    /// <summary>
    /// 从集合中移除具有指定类型的对象。
    /// </summary>
    /// 
    /// <returns>
    /// 从集合中移除的对象。
    /// </returns>
    /// <typeparam name="T">要从集合中移除的项的类型。</typeparam>
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public T Remove<T>();
    /// <summary>
    /// 返回 <see cref="T:System.Collections.Generic.KeyedByTypeCollection`1"/> 中包含的类型 <paramref name="T"/> 的对象的集合。
    /// </summary>
    /// 
    /// <returns>
    /// 一个类型 <paramref name="T"/> 的 <see cref="T:System.Collections.ObjectModel.Collection`1"/>,包含来自原始集合的类型 <paramref name="T"/> 的对象。
    /// </returns>
    /// <typeparam name="T">要在集合中查找的项的类型。</typeparam>
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public Collection<T> FindAll<T>();
    /// <summary>
    /// 从集合中移除所有具有指定类型的元素。
    /// </summary>
    /// 
    /// <returns>
    /// <see cref="T:System.Collections.ObjectModel.Collection`1"/>,包含来自原始集合的类型 <paramref name="T"/> 的对象。
    /// </returns>
    /// <typeparam name="T">要从集合中移除的项的类型。</typeparam>
    [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
    public Collection<T> RemoveAll<T>();
    /// <summary>
    /// 获取集合中包含的某个项的类型。
    /// </summary>
    /// 
    /// <returns>
    /// 集合中指定的 <paramref name="item"/> 的类型。
    /// </returns>
    /// <param name="item">集合中要检索其类型的项。</param><exception cref="T:System.ArgumentNullException"><paramref name="item"/> 为 null。</exception>
    [__DynamicallyInvokable]
    protected override Type GetKeyForItem(TItem item);
    /// <summary>
    /// 在集合中的特定位置插入一个元素。
    /// </summary>
    /// <param name="index">从零开始的索引,应在该位置插入 <paramref name="item"/>。</param><param name="item">要在集合中插入的对象。</param><exception cref="T:System.ArgumentNullException"><paramref name="item"/> 为 null。</exception>
    [__DynamicallyInvokable]
    protected override void InsertItem(int index, TItem item);
    /// <summary>
    /// 使用一个新对象替换指定索引处的项。
    /// </summary>
    /// <param name="index">要替换的 <paramref name="item"/> 的从零开始的索引。</param><param name="item">要添加到集合中的对象。</param><exception cref="T:System.ArgumentNullException"><paramref name="item"/> 为 null。</exception>
    [__DynamicallyInvokable]
    protected override void SetItem(int index, TItem item);
  }

  泛型方法:方法临时也卷入有用的算法,所以CLEnclave允许援引类型、值类型或接口中定义泛型方法。

   2.泛型接口和泛型委托:

   
 泛型的基本点功用正是概念泛型的援引类型和指类型。多少个援引类型或值类型可透过点名项目实参的章程贯彻泛型接口,也得以维持类型实参的未钦命状态实现一个泛型接口。

   
 具体看一下泛型接口IEnumerable:公开枚举数,该枚举数扶助在非泛型会集上扩充简易迭代。

 [ComVisible(true)]
  [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
  [__DynamicallyInvokable]
  public interface IEnumerable
  {
    /// <summary>
    /// 返回一个循环访问集合的枚举数。
    /// </summary>
    /// 
    /// <returns>
    /// 一个可用于循环访问集合的 <see cref="T:System.Collections.IEnumerator"/> 对象。
    /// </returns>
    /// <filterpriority>2</filterpriority>
    [DispId(-4)]
    [__DynamicallyInvokable]
    IEnumerator GetEnumerator();
  }

   
CL途乐援助泛型委托,目标是保障别的类型的靶子都能以一种等级次序安全的不二秘技传给一个回调方法。泛型委托允许二个男女类型实例在传给三个回调方法时不实践其余装箱管理。委托时机只提供了4个点子:一个构造器,一个Invlke方法,贰个BeginInvoke方法和二个EndInvoke方法。假如定义的三个寄托项目内定了档期的顺序参数,编写翻译器会定义委托类的秘技,用钦命的品种参数替换方法的参数类型和值类型。

 
 以上是对泛型类、泛型接口和泛型委托的简约驾驭,本文的指标重如若执教泛型方法,上面大家现实通晓一些泛型泛型的学问。

  两个都是象征API的主导办法(不管是指贰个泛型方法或然三个一体化的泛型类型),以致日常梦想出现三个平常品种的地点出现三个品类参数。比方,List<T>,在类名之后增添一个<T>,申明它操作的是二个未钦定的数据类型。定义泛型类型和办法时,它为品种钦点的其他变量(比如T)都叫作品种参数(type
parameter)。T代表二个变量名,在源代码中能够使用一个数据类型的其他职责,都能使用T。

三.泛型方法分析:

  品类参数是实际类型的占位符。在泛型评释中,花色参数要放在一批尖括号内,并以逗号分隔。所以,在Dictionary<TKey,
TValue>中,花色参数是TKey和TValue。使用泛型类型或艺术时,要选择真实的类型替代。那些真正的门类称为花色实参(type argument)。

 1.泛型方法概述:   

   
定义泛型类、结构或接口时,类型中定义的其余措施都可援用类型内定的三个品种参数。类型参数能够视作艺术的参数,作为艺术的再次回到值,或许当作艺术内部定义的三个片段变量来利用。CL奥德赛允许三个格局钦定它独有的种类参数,这个种类参数可用来参数、重返值、或然有个别变量。

 
 C#编写翻译器协理在调用三个泛型方法时实行项目估计。实践项目预计时,C#运用变量的数据类型,实际不是由变量援引的靶子的莫过于类型。三个品种能够定义多少个点子,让里面一个办法接受现实的数据类型,让另一个主意接受泛型类型参数。

    泛型方法亲自过问:

List<TOutput> ConverAll<TOutput>(Conver<T,TOutput> conv)

List<TOutput>:返回类型(一个泛型列表)。

ConverAll:方法名。

<TOutput>:类型参数。

Conver<T,TOutput>:参数类型(泛型委托)。

conv:参数名。

   
对以上的身体力行代码剖析,供给调控:为每一个品种参数使用四个例外的门类,在总体应用这个项目参数。

 
(1).首先替换包罗方法(List<T>的T部分)的不胜类型的体系参数,如将T替换为string:

List<TOutput> ConverAll<TOutput>(Conver<string,TOutput> conv)

 
(2).管理完T后,再必要处理的正是TOutput,能够看来它是多少个措施类型参数,这里运用guid替换TOutput。

List<Guid> ConverAll(Conver<string,Guid> conv)

 
对TOutput赋予类型实参后,能够移除生命中的类型参数<TOutput>,将艺术可以称作非泛型方法,如上。以上的身体力行能够管理二个字符串列表,用一个转变器来生成贰个Guid列表。

 
将原始列表中的每种成分都调换到指标项目,将改造后的因素增添到三个列表中,最终回到这些列表。以上的管理情势,首要将其泛型方法的参数举行依次的细化,无论在怎么课程,都急需将复杂的标题开展轻松化,将抽象的问题具体化,那也是一种常用的管理情势。

  泛型为开垦职员提供了以下优势:

 2.类型约束:

   
约束的效用是限量能钦点成泛型实参的类型数量。通过限制类型的数码,大家得以对那多少个类型实践越来越多的操作。约束能够动用于一个泛型类型的品种参数,也可以选取于一个泛型方法的花色参数。CL宝马X5不容许基于项目参数名称或约束进行重载,只可以依照元数对项目或艺术实行重载。不容许为重写方法的种类参数钦定别的约束,可是项目实参的名号是足以改换的。

   
泛型约束的操作,约束要松手泛型方法或泛型类型注解的结尾,并由上下文关键where引进。

  1)源代码敬重  使用二个泛型算法的开采人士无需拜会算法的源代码。然则,使用C++模板的泛型技能时,算法的源代码必需提供给策动利用算法的客户。

   (1).援引类型约束:

     
引用类型约束:用于确定保障使用的项目实参是援引类型。(表示为:T:class,且必得为品种参数钦定的率先个约束。)

  2)类型安全  将八个泛型算法应用于八个实际的类型时,编写翻译器和CL宝马X5能驾驭开拓职员的盘算,并确定保证独有与制订数据类型包容的靶子才具随同算法使用。

   (2).值类型约束:

     
值类型约束:用于确定保证使用的类型参数是指类型。(表示为:T:struct,可空类型不分包在内)

  3)更彰着的代码  由于编写翻译器强制类型安全性,所以降低了源代码中必得进行的转型次数。

   (3).构造函数类型约束:

     
构造函授类型约束:钦点全部品类参数的末尾二个约束,它检查项目实参是或不是有两个可用来成立实例的无参构造函数。(表示为:T:new())适用于具备值类型,全部未有展现注脚构造函数的非静态、非抽象类,全部展现注脚了三个集体无参构造函数的非抽象类。

  4)更佳的属性  在有泛型在此以前,要想定义八个常规化的算法,它的全数成员都要定义成操作Object数据类型。这里面将要有装箱和拆箱之间的习性损失。由于明天能创建三个泛型算法来操作二个切实可行的值类型,所以值类型的实例能以传值的议程传递,CL奇骏不再须求只需任何装箱操作。由于不再供给转型,所以CL福睿斯不必检查尝试一遍转型操作是不是类型安全,同样增进了代码的允许速度。

   (4).调换类型约束:

   
 转变类型约束:允许你钦赐另二个档期的顺序,类型实参必得能够因而一致性、援引或装箱调换隐式地改换为该类型。还足以鲜明类型实参必得能够调换为另叁个品类实参。(例:class
Sample<T> where T:Stream)

一、
Framework类库中的泛型

   (5).组合约束:

   
 组合约束:所个约束组合在一同的自律,可是结合约束也许有限制标准。因为尚未其余项目正是引用类型,又是值类型。由于每二个值都有三个无参构造函数,所以只要已经有一个值类型约束,就不容许再钦赐二个构造函数约束。就算存在几个项目约束,何况个中一个为类,那么它应该出现在接口的前头,并且大家无法屡次点名同二个接口。不一样的品类参数能够用分歧的牢笼,分别由一个where引入。

   备注:类型猜度只适用于泛型方法,不适用于泛型类型。

 
以上是对泛型方法的有关概念和封锁做了简便的分析,接下去看一下.NET中部分批发格局的有血有肉落到实处:

 /// <summary>
  /// 封装一个方法,该方法具有四个参数并且不返回值。
  /// </summary>
  /// <param name="arg1">此委托封装的方法的第一个参数。</param><param name="arg2">此委托封装的方法的第二个参数。</param><param name="arg3">此委托封装的方法的第三个参数。</param><param name="arg4">此委托封装的方法的第四个参数。</param><typeparam name="T1">此委托封装的方法的第一个参数类型。</typeparam><typeparam name="T2">此委托封装的方法的第二个参数类型。</typeparam><typeparam name="T3">此委托封装的方法的第三个参数类型。</typeparam><typeparam name="T4">此委托封装的方法的第四个参数类型。</typeparam><filterpriority>2</filterpriority>
  [TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
  [__DynamicallyInvokable]
  public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);

  /// <summary>
  /// 表示比较同一类型的两个对象的方法。
  /// </summary>
  /// 
  /// <returns>
  /// 一个有符号整数,指示 <paramref name="x"/> 与 <paramref name="y"/> 的相对值,如下表所示。 值 含义 小于 0 <paramref name="x"/> 小于 <paramref name="y"/>。 0 <paramref name="x"/> 等于 <paramref name="y"/>。 大于 0 <paramref name="x"/> 大于 <paramref name="y"/>。
  /// </returns>
  /// <param name="x">要比较的第一个对象。</param><param name="y">要比较的第二个对象。</param><typeparam name="T">要比较的对象的类型。</typeparam><filterpriority>1</filterpriority>
  [__DynamicallyInvokable]
  public delegate int Comparison<in T>(T x, T y);

  泛型最显明的使用就是集结类。FCL已经定义了多少个泛型集结类。当中绝大多数类能在Sysytem.Collections.Generic和System.Collections.ObjectModel命名空间中。要运用线程安全的泛型集合类,能够去System.Collections.Concurrent命名空间搜索。

四.泛型方法运用代码示例:

   以上疏解的关于泛型方法的剧情,这里提供贰个关于泛型方法操作XML的代码:

  

    /// <summary>
    /// 泛型方法:编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数
    /// </summary>
    public class ObjectXmlSerializer
    {
        /// <summary>
        /// 文件的反序列化
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="fileName"></param>
        /// <returns>
        /// 如果日志启用,则发生异常时,异常写入日志,若日志没有开启,则直接抛出异常信息
        /// loggingEnabled==true: Null is returned if any error occurs.
        /// loggingEnabled==false: throw exception
        /// </returns>
        public static T LoadFromXml<T>(string fileName) where T : class
        {
            return LoadFromXml<T>(fileName, true);
        }

        /// <summary>
        /// 文件反序列化,若发生异常,异常信息写入日志
        /// </summary>
        /// <typeparam name="T">加载类的类型</typeparam>
        /// <param name="fileName">文件名字</param>
        /// <param name="loggingEnabled">启用日志记录</param>
        /// <returns></returns>
        public static T LoadFromXml<T>(string fileName, bool loggingEnabled) where T : class
        {
            FileStream fs = null;
            try
            {
                var serializer = new XmlSerializer(typeof(T));
                fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
                //反序列化对象
                return (T)serializer.Deserialize(fs);
            }
            catch (Exception e)
            {
                if (loggingEnabled)
                {
                    //文件异常,写入日志
                    LogLoadFileException(fileName, e);
                    return null;
                }
                else
                {

                    throw new Exception(e.Message);
                }
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }

        /// <summary>
        /// 序列化一个对象到文件中.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fileName">文件名</param>
        /// <param name="data">待序列化的数据</param>
        /// <returns>
        /// 如果日志启用,则发生异常时,异常写入日志,若日志没有开启,则直接抛出异常信息
        /// loggingEnabled==true: log exception
        /// loggingEnabled==false: throw exception
        /// </returns>
        public static void SaveToXml<T>(string fileName, T data) where T : class
        {
            SaveToXml(fileName, data, true);
        }

        /// <summary>
        /// 文件反序列化,若发生异常,异常信息写入日志
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fileName">文件名</param>
        /// <param name="data">发序列化对象</param>
        /// <param name="loggingEnabled">是否启用日志</param>
        public static void SaveToXml<T>(string fileName, T data, bool loggingEnabled) where T : class
        {
            FileStream fs = null;
            try
            {
                var serializer = new XmlSerializer(typeof(T));
                fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
                //序列化对象
                serializer.Serialize(fs, data);
            }
            catch (Exception e)
            {
                if (loggingEnabled) LogSaveFileException(fileName, e);
                else
                {
                    throw new Exception(e.Message);
                }
            }
            finally
            {
                if (fs != null) fs.Close();
            }
        }

        /// <summary>
        /// 序列化
        /// XML & Datacontract Serialize & Deserialize Helper
        /// </summary>
        /// <typeparam name="T">T指定必须为class类型</typeparam>
        /// <param name="serialObject"></param>
        /// <returns></returns>
        public static string XmlSerializer<T>(T serialObject) where T : class
        {
            var ser = new XmlSerializer(typeof(T));
            //MemoryStream实现对内存的读写,而不是对持久性存储器进行读写
            //MemoryStream封装以无符号字节数组形式存储的数据,该数组在创建MemoryStream对象时被初始化,
            //或者该数组可创建为空数组。可在内存中直接访问这些封装的数据。
            //内存流可降低应用程序中对临时缓冲区和临时文件的需要。
            var mem = new MemoryStream();
            var writer = new XmlTextWriter(mem, UTF8);
            ser.Serialize(writer, serialObject);
            writer.Close();
            return UTF8.GetString(mem.ToArray());
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="str"></param>
        /// <returns></returns>
        public static T XmlDeserialize<T>(string str) where T : class
        {
            var mySerializer = new XmlSerializer(typeof(T));
            var mem2 = new StreamReader(new MemoryStream(UTF8.GetBytes(str)), UTF8);
            return (T)mySerializer.Deserialize(mem2);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xmlData"></param>
        /// <returns>返回值类型为传入的类型</returns>
        public static T DataContractDeserializer<T>(string xmlData) where T : class
        {
            var stream = new MemoryStream(UTF8.GetBytes(xmlData));
            var reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
            var ser = new DataContractSerializer(typeof(T));
            var deserializedPerson = (T)ser.ReadObject(reader, true);
            reader.Close();
            stream.Close();
            return deserializedPerson;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="myObject"></param>
        /// <returns></returns>
        public static string DataContractSerializer<T>(T myObject) where T : class
        {
            var stream = new MemoryStream();
            var ser = new DataContractSerializer(typeof(T));
            ser.WriteObject(stream, myObject);
            stream.Close();
            return UTF8.GetString(stream.ToArray());
        }

        /// <summary>
        /// 序列化时异常日志
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="ex">异常</param>
        [Conditional("TRACE")]
        private static void LogLoadFileException(string fileName, Exception ex)
        {
            var sb = new StringBuilder();
            sb.Append("Fail to load xml file: ");
            sb.Append(fileName + Environment.NewLine);
            sb.Append(ex);
            //写入日志记录中方法
            //  Logger.LogEvent(LogCategory, LogEventLoadFileException, sb.ToString());
        }

        /// <summary>
        /// 反序列化时异常日志
        /// </summary>
        /// <param name="fileName">文件名</param>
        /// <param name="ex">异常</param>
        [Conditional("TRACE")]
        private static void LogSaveFileException(string fileName, Exception ex)
        {
            var sb = new StringBuilder();
            sb.Append("Fail to save xml file: ");
            sb.Append(fileName + Environment.NewLine);
            sb.Append(ex);

        }


        /// <summary>
        /// 将xml字符串序列化为数据流(数据流编码为ASCII,UTF8)
        /// </summary>
        /// <returns>字符串转换到流</returns>
        public static MemoryStream StringXmlToStream(string strXml,Encoding encod)
        {
            MemoryStream memoryStream = null;
            try
            {
                Encoding encoding;
                if (Equals(encod, ASCII))
                {
                     encoding = new ASCIIEncoding();
                }
                else
                {
                     encoding = new UTF8Encoding();  
                }
                var byteArray = encoding.GetBytes(strXml);
                memoryStream = new MemoryStream(byteArray);
                memoryStream.Seek(0, SeekOrigin.Begin);
                return memoryStream;
            }
            catch (IOException ex)
            {
                throw new IOException(ex.Message);
            }
            finally
            {
                if (memoryStream != null) memoryStream.Close();
            }



        }

    }

   以上的代码就不做赘述,须求次代码的能够选用。

 

五.总结:

   
本文讲解了C#2.0引入的泛型知识,主要包涵泛型类、泛型接口、泛型委托,何况重要讲授了泛型方法,已经泛型的自律分类。最终给了有的行使泛型方法操作xml的措施。希望以上的疏解能够支持到正在想深造的人。

  Microsoft建议开拓人士使用泛型集结类,并依靠多少个地方的从头到尾的经过,不鼓舞使用非泛型集结类。首先,非泛型不可能获得类型安全性、更清晰的代码和更佳的脾气。其次,泛型具备越来越好的靶子模型。

 

  集结类达成了无数接口,放入集合中的对象也说不定达成了接口,会集类可采纳那些接口实践像排序那样的操作。FCL内建了累累泛型接口定义,所以在采取接口时,也能体会到泛型带来的裨益。常用的接口富含在Sysytem.Collections.Generic命名空间中。

  

  新的泛型接口而不是洲统一组织一盘算用来完全代表非泛型接口。

 

  System.Array类(即全部数组的基类)提供了汪洋静态泛型方法,比如,AsReadonly、FindAll、Find、FindIndex等。

 

二、Wintellect的Power
Collections库
  Power
Collections库由Wintellect制作,那个库有一多级集结类构成,任什么人都得以免费下载和选用。  

集合类名称 说明
BigList<T>  有序T对象集合。操作100个以上的数据项是,效率非常高
Bag<T> 无序T对象的集合,集合进行了哈希处理,并允许重复项
OrderedBag<T> 有序T对象的集合,允许重复值
Set<T> 无序T数据项集合,不允许重复项。添加重复项后,会只保留一个
OrderedSet<T> 有序T数据项的集合,不允许重复项
Deque<T> 双端队列(double-ending queue)。类似于一个列表,但在起始处添加/删除数据项时,比列表更高效
OrderedDictionary<TKey,TValue> 字典,其中的键进行了排序,每个键都有一个对应的值
MultiDictionary<TKey,TValue> 字典,其中每个键都可以有多个值,对键进行了哈希处理,允许重复,而且数据项是无序的
OrderedMultiDictionary<TKey,TValue>

字典,其中的键进行了排序,每个键都可以有多个值(同样进行了排序)。允许重复的键

 

 

 

 

三、泛型的底蕴结构

  为了是泛型能够专门的学问,Microsoft必得产生以下工作:

    1)创制新的IL指令,使之能够分辨类型实参

    2)修改现存元数据表的格式,以便表示具有泛型参数的体系名称和办法

   
3)修改各样编制程序语言(C#等),以支持新的语法,允许开辟职员定义个引进泛型类型和办法

    4)修改编写翻译器,使之能生成新的IL指令和退换元数据格式

   
5)修改JIT编写翻译器,使之能够处理新的、扶助项目实参的IL指令,以便生成不易的地面代码

   
6)缔造新的反射成员,使开拓人士能查询类型和分子,以推断它们是还是不是具有泛型参数。其余,还非得定义新的反光成员,使开辟职员能在运维时成立泛型类型和方法定义。

    7)修改调节和测验器以以突显和操作泛型类型、成员、字段以及一些变量。

    8)修改VisualStudio 的”智能感知”(AMDliSense)特性。

 

 1.盛开类型查封类型

  前面我们谈谈过CLKuga怎样为应用程序的种种品种创制一个内部数据结构,这种数据结构称为类型对象。

  具备泛型类型参数的品类依旧是系列,CLQashqai同样会为它成立二个中间类型对象。无论是援用类型(类)、值类型(结构)、接口类型,仍旧委托项目,这或多或少都以确立的。

  若无为别的类型参数提供类型实参,注解的正是二个未绑定泛型类型

  假诺内定了类型实参**,该类型就称为已构造类型**。

  大家领略,类型能够看作是目的的蓝图。一样的,未绑定泛型类型是已构造类型的蓝图。它是一种额外的抽象层。

  已构造类型能够是绽开类型查封类型

  ”盛放类型“(open
type)是指还隐含二个项目参数,CL中华V禁止构造开放类型的其他实例。这一点类似于CLPRADO禁止构造接口类型的实例。

  代码援用贰个泛型类型时,可钦定一组泛型类型实参。假诺为富有类型实参传递的都以实际数据类型,类型就称为”密闭类型“(closed
type)。也等于说,具有泛型”项目实参“的品类称为”密闭类型“。CL福特Explorer允许构造密闭类型的实例。

  今世码援引二个泛型类型时,大概会留给一些泛型类型实参未钦命。那会在CLPRADO中开创一个新的盛开类型的对象,并且不能够创设该项指标实例。比方:

internal static class Program
    {
        private static void Main(string[] args)
        {
            Object o = null;

            // Dictionary<,> 是一个开放类型,有两个类型参数
            Type t = typeof(Dictionary<,>);

            // 尝试创建该类型的一个实例 (失败)
            o = CreateInstance(t);
            Console.WriteLine();

            // DictionaryStringKey<> 是一个开放类型,有一个类型参数
            t = typeof(DictionaryStringKey<>);

            // 尝试创建该类型的一个实例 (失败)
            o = CreateInstance(t);
            Console.WriteLine();

            // DictionaryStringKey<Guid> 是一个封闭类型
            t = typeof(DictionaryStringKey<Guid>);

            // 尝试创建该类型的一个实例 (成功)
            o = CreateInstance(t);

            // Prove it actually worked
            Console.WriteLine("Object type=" + o.GetType());

            Console.ReadKey();
        }

        private static Object CreateInstance(Type t)
        {
            Object o = null;
            try
            {
                o = Activator.CreateInstance(t);
                Console.Write("已创建 {0} 的实例", t.ToString());
            }
            catch (ArgumentException e)
            {
                Console.WriteLine(e.Message);
            }
            return o;
        }

        // A partially specified open type
        internal sealed class DictionaryStringKey<TValue> :
            Dictionary<String, TValue>
        {
        }
    }

  最终突显地结果为:

图片 1

图片 2  可以看到,Activator的CreateInstance方法会在布局开荒项目的实例时抛出一个ArgumentException极度。注意,在老大的字符串新闻中,指明类型中依然蕴藏一些泛型参数。

 

  从出口结果能够看到,类型名是以二个”`“字符和四个数字结尾的。这一个数字代表类型的元数,也正是类别供给的品种参数的个数。譬如,Dictionary类的元数为2,它供给为TKey和电视alue这些品种参数钦命具体品种。

 

  还要注意的是,CL翼虎会在品种对象内部分配项目标静态字段。因而,各个密闭类型都有协和的静态字段。换言之,假诺List<T>定义了其余静态字段,那么些字段不会在一个List<DataTime>和List<String>之间分享;各个密闭类型对象都有它和睦的静态字段。其余,若是贰个泛型类型定义了八个静态构造器,那么针对各种封闭类型,这么些构造器都会施行一回。在泛型类型上定义三个静态构造器的指标是保证传递的档期的顺序参数满意一定的原则。比如,如若愿意一个泛型类型值用于拍卖枚举类型,能够如下概念:

internal sealed calss GenericTypeThatReqiresAnEnum<T> {
    static GenericTypeThatReqiresAnEnum() {
        if ( !typeof (T).IsEnum) {
            throw new ArgumentException("T must be an enumerated type")
        }
    }
}

  CLWrangler提供了五个名称叫”约束”(constraint)的成效,可应用它越来越好地定义叁个泛型类型来提议哪个项目实参是一蹴而就的。

 

 2.泛型类型和承接

  泛型类型依旧是体系,所以它能从任何任何项目派生。使用多个泛型类型并点名项目实参时,实际上是在CLLX570中定义一个新的品类对象,新的类型对象是从派生该泛型类型的非常类型派生的。也正是说,由于List<T>是从Object派生的,那么List<String>和List<Guid>也是从Object派生的。

 

 3. 泛型类型同一性

  有时,泛型语法会将开采职员搞糊涂,所以有个别开采人士定义了三个新的非泛型类类型,它从二个泛型类型派生,并点名了有着的花色实参。举例,为了简化一下代码:

List<DateTime> dt = new List<DateTime>();

一部分开荒职员可能率先定义上边那样的三个类:

internal sealed class DateTimeList : List<DataTime> {
        //这里无需放任何代码!
}

接下来就能够更进一竿简化创制:

DateTimeList  dt = new DateTimeList ();

  这样做表面上是便利了,但是决定不要一味处于增加源代码的易读性类那样定义一个新类。那样会丧失类型同一性(identity)和相等性(equivalence)。如下:

Boolean sameType = (typeof(List<DateTime>) == (typeof(DateTimeList));

  上述代码运营时,sameType会初阶化为false,因为正如的是多个不等类其他指标。也正是说,假设七个方式的原型接受几个DateTimeList,那么无法将一个List<DateTime>传给它。但是,假设艺术的原型接受三个List<DateTime>,那么能够将二个DateTimeList传给它,因为DateTimeList是从List<DateTime>派生的。

  C#提供一种办法,允许行使简化的语法来援用一个泛型密封类型,同时不会潜移暗化类的相等性——使用using指令。举例:

using DateTimeList = System.Collections.Generic.List<System.DateTime>;

  现在只想上面那行代码时,sameType会初叶化为true:

Boolean sameType = (type(List<DateTime>) == (ypeof(DateTimeList));

  还大概有,能够使用C#的隐式类型局地变量功效,让编写翻译器依据表明式的门类来推论一个办法的一部分变量的项目。

 

 4.代码爆炸

 

  使用泛型类型参数的八个措施在开展JIT编写翻译时,CL福特Explorer获取格局的IL,用内定的门类实参进行替换,然后创设妥当的地头代码。但是,那样做有叁个劣势:CLEnclave要为每个不一致的方式/类型组合生成本地代码。我们将这么些情景叫做”代码爆炸”。它可能导致引用程序集的醒目增大,进而影响属性。

 

  CL福睿斯内建了部分优化措施,能缓慢解决代码爆炸。首先,借使为二个特定的种类实参调用了二个办法,以往重新利用同一的品类实参来调用那些艺术,CL兰德酷路泽只会为这些格局/类型组合编写翻译二遍。所以,假若三个程序集应用List<DateTime>,三个截然两样的次序集也使用List<Date提姆e>,CLLX570只会为List<DateTime>编写翻译二次艺术。

 

  CL大切诺基还提供了一个优化措施,它以为具有援引类型实参都以完全同样的,所以代码能够分享。之所以能那样,是因为有着援引类型的实参或变量时间只是实践堆上的靶子的指针,而指标指针全是以相同的章程操作的。

 

  不过,假诺某些项目实参是值类型,CL本田CR-V就务须极其为特别值类型生花费地代码。因为值类型的大小不定。固然类型、大小同等,CLTucson依旧鞭长莫及分享代码,恐怕需求用不相同的地面CPU指令操作这么些值。

 

四、泛型接口

 

  泛型的显要意义正是概念泛型的引用类型和值类型。不过,对泛型接口的支撑对CLTiggo来说也相当重大。没有泛型接口,每一遍筹算动用三个非泛型接口(如IComparable)来操作叁个值类型,都会时有产生装箱,並且会失掉编译时的花色安全性。那将严重制约泛型类型的使用。因而,CL奇骏提供了对泛型接口的支撑。三个援用类型或值类型能够透过点名项目实参的办法来促成泛型接口。也足以保险类型实参的未内定状态来达成一个泛型接口。

 

  以下是泛型接口定义是FCL的一部分:

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

  上边包车型地铁演示类型完成上述泛型接口,并且点名了品种实参。

internal sealed class Triangle : IEnumerator<Point> {
    private Point[] m_Vertice;

    public Point Current { get { ... }  } 
}

  上面实现了一样的泛型接口,但保持类型实参的未钦命状态:

internal sealed class ArrayEnumerator<T> :  IEnumerator<T> {
    private T[] m_Vertice;

    public TCurrent { get { ... }  } 
}

 

五、泛型委托

  CLCRUISER帮衬泛型委托,目标是保险其余类型的对象都能以一种档期的顺序安全的方法传给贰个回调方法。

别的,泛型委托允许二个值类型的实例在传给多少个回调方法时不实行其余装箱操作。

  委托实际只提供了4个章程的二个类定义。那4个措施包蕴叁个构造器、一个Invoke方法、叁个BeginInvoke和叁个EndInvoke方法。要是定义的多个寄托项目钦点了品种参数,编写翻译器会定义委托类的点子,用内定的种类参数代替形式中的参数类型和重返值类型。

 

  举个例子,假定向上面那样定义二个泛型委托:

 public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key, TValue value);

  编写翻译器会将它转化成三个类,该类在逻辑上得以这么表示:

public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate {
    public CallMe(Object object, IntPtr method);
    public virtual TReturn Invoke(TKey key, TValue value);
    public virtual IAsycResult BeginInvoke(TKey key, TValue value, AsyncCallback callback, Object object);
    public virtual TReturn EndInvoke(IAsycResult  result);
}

  反编写翻译后

图片 3

   建议尽量采纳在FVL中预订义的泛型Action和Func委托。

图片 4

  

 六、 委托和接口的逆变和协变泛型类型实参

 

  委托的各类泛型类型参数都可标记为协变量可能逆变量。利用这么些职能,可将泛型委托项目标四个变量转型为同叁个信托项目标另三个变量,前者的泛型参数类型分裂。泛型类型参数能够是须臾间其余一种样式:

  1)不改变量(invariant)    意味着泛型类型参数无法改换。

  2)逆变量(Contravarriant)    意味着泛型类型参数能够从一个基类改变为此类的派生类。在C#中,用
in
关键字标志逆变量方式的泛型类型参数。逆变量泛型参数只现出在输入地方,举个例子作为艺术的参数。

  3)协变量(Convarianr)  
 意味着泛型类参数能够从二个派生类改造为它的基类。在C#可行 out
关键字标识协变量情势的泛型类型参数。协变量泛型参数只可以现身在出口地方,比如作为艺术的归来类型。

 

  举例,今后存在以下委托类型定义(它在FCL中是存在的) 

public delegate TResult Func<in T, Out TResult>(T arg);

  其中,泛型类型参数T用in关键字标识,那使它产生叁个逆变量;泛型类型参数TResulr则用out关键字标识,那是它成为叁个体协会变量。

  所以,即便像下边那样声多美滋个变量:

Func<Object,ArgumenException> fn1 = null;

  就能够将它转型为另叁个泛型类型参数不一样的Func类型:

Func<String,Exception> fn2 = fn1;    //不需要显示转型
Exception e = fn("");

  使用要获得泛型参数和再次来到值的嘱托时,指出尽量为逆变性和协变性钦命in和out关键字。那样做不会有不良反应,并使您的寄托能在越多的景色中使用。

 

  和寄托相似,具备泛型类型参数的接口也可将它的档次参数标识为逆变量和协变量。比方:

public interface IEnumerator<out T> : IEnumerator {
    Boolean MoveNext();
    T Current{ get; }
}

  由于T是逆变量,所以以下代码能够安枕而卧编写翻译:

//这个方法接受任意引用类型的一个IEnumerable
Int32 Count(IEnumerable<Object> collection) { ... }
//以下调用向Count传递一个IEnumerable<String>
Int32 c = Count(new[] { "Grant" }); 

 

七、泛型方法

  定义泛型类、结构或接口时,那么些类别中定义的别的措施都可援用由项目钦点的贰个体系参数。类型参数能够当作艺术的参数,作为艺术的重回值,大概当作艺术内部定义的三个有的变量来使用。

  CL君越还同意二个方式内定它独有的种类参数。那些体系参数可用来参数、重返值可能某些变量。

 

  在上边包车型客车例证中,一个类型定义了三个项目参数,二个方法规定义了它和煦的专项使用项目参数:

internal sealed class FenericType<T> {
    privete T m_value;

    public GenericType(T value) { m_value = value; }

    public TOutput Converter<TOutput>() {
        TOutput resulr= (TOurput) Convert.ChangeType(m_value,typeof(TOutput));
        return result;
    }
}

 1.泛型方法和品种揣度

   为了改进代码的创办,同事巩固可读性和维护性,C#编写翻译器帮助在调用壹个泛型方法时张开项目推断(type
inference)。那意味编写翻译器会在调用三个泛型方法时自动判别出要利用的品类。

private static void CallingSwapUsingInference() {
    Int32 n1 = 1, n2 = 2;
    Swap(ref n1, ref n2);    //调用Swap<Int32>

    String s1 = "A";
    Object s2 = "B";
    Swap(ref s1, ref s2);    //错误,不能推断类型
}

  施行项目测度时,C#应用变量的数据类型,并不是由变量援用的靶子的实际类型。

八、泛型和其余成员

  在C#中,属性、索引器、事件、操作符方法、构造器和终结器(finalizer)本人不可能有项目参数。可是,它们能在叁个泛型类型中定义,何况这一个分子中的代码能应用项指标种类参数。

 

九、可验证性和自律

  C#编写翻译器和CL昂Cora补助叁个名称叫”约束”(constraint)的体制,可应用它使泛型变得真的实用。约束的意义是限量能内定成泛型实参的参数数量。通过限制类型的数据,我们得以对那多少个类型实践更加多的操作。

public static T Min<T>(T o1, T o2) where T : IComparable<T> {
    if (o1.CompareTo(o2))<0 return o1;
    return o2;
}

  C#的where关键字告诉编写翻译器,为T钦点的任何项目都不能够不贯彻同种类(T)的泛型IComparable接口。有了这几个约束,就足以在措施中调用CompareTo,因为已知IComparable<T>接口定义了CompareTo。

   约束可采取于二个泛型类型的连串参数,也可使用于三个泛型方法的连串参数(就好像Min所呈现的)。CLTiggo分歧意基于项目参数名称或约束来展开重载;只可以依照元数(类型参数的个数)对品种或格局举办重载。

  

internal sealed class OverloadingByArity {
      // 可以定义一下类型
      internal sealed class AType { }
      internal sealed class AType<T> { }
      internal sealed class AType<T1, T2> { }

      // 错误: 与没有约束的 AType<T> 起冲突
      internal sealed class AType<T> where T : IComparable<T> { }

      // 错误: 与 AType<T1, T2> 起冲突
      internal sealed class AType<T3, T4> { }

      internal sealed class AnotherType {
         // 可以定义一下方法,参数个数不同:
         private static void M() { }
         private static void M<T>() { }
         private static void M<T1, T2>() { }

         // 错误: 与没有约束的 M<T> 起冲突
         private static void M<T>() where T : IComparable<T> { }

         // 错误: 与 M<T1, T2> 起冲突
         private static void M<T3, T4>() { }
      }
   }

 

  重写二个虚泛型方法时,重写的法子必得钦点同样数量的门类参数,并且这么些品种参数会延续在基类方法上点名的约束。事实上,根本不允许为重写方法的花色参数钦定其余约束。可是,类型参数的名号是足以转移的。类似的,完成一个接口方法时,方法必得钦定与接口方法等量的品类参数,那么些品种参数将延续由接口的形式在它们后边钦定的束缚。下例使用虚方法演示了这一条条框框:

internal static class OverridingVirtualGenericMethod {
      internal class Base {
         public virtual void M<T1, T2>()
            where T1 : struct
            where T2 : class {
         }
      }

      internal sealed class Derived : Base {
         public override void M<T3, T4>()
            /*where T3 : struct
            where T4 : class  */
         {
         }
      }
   }

 1 首要约束

  类型参数能够钦点零个或三个第一约束。重要约束能够是一个援用类型,它申明了三个尚未密闭的类。不能够内定以下特殊援引类型:System.Object,System.Array,System.Delagate,System.MulticastDelegate,System.ValueType,System.Enum和System.Void。

 

  指向多个引用类型约束时,也便是向编写翻译器承诺:三个钦赐的门类实参要么是与约束类型同样的项目,要么是从约束类型派生的三个体系。如下泛型类:

 internal static class PrimaryConstraintOfStream<T> where T : Stream {
         public static void M(T stream) {
            stream.Close();   // OK
         }
      }

  有多少个非常的基本点约束:class和struct。在那之中,class约束是指类型实参是一个引用类型。任何类项目、接口类型、委托项目或然数组类型都满意那几个约束。比如:

internal static class PrimaryConstraintOfClass<T> where T : class {
         public static void M() {
            T temp = null;    // 允许,T为引用类型
         }
      }

  struct约束向编写翻译器承诺贰个点名的种类实参是值类型。满含枚举在内的任何值类型都满意那一个约束。然则,编写翻译器和CL奔驰G级将其余System.Nullable<T>值类型都说是万分类型。

internal static class PrimaryConstraintOfStruct<T> where T : struct {
         public static T Factory() {
            // 允许,因为值类型都有一个隐式无参构造器
            return new T();
         }
      }

 

 2 次要封锁

  八个种类参数能够钦定零个要么多个扶助约束,次要约束代表的是一个接口类型。钦点三个接口类型约束时,是向编写翻译器承诺三个钦定的项目实参是是落到实处了接口的三个门类。由于能钦定四个接口约束,所以为品种实参内定的花色必得贯彻了颇具接口约束。

 

  还会有一种说不上约束称为类型参数约束,不经常也称裸类型约束。这种约束用的比接口约束少得多。它同意多个泛型类型或方式规定:在钦赐的档期的顺序实参之间,必需存在二个提到。一个类别参数能够钦赐零个照旧七个种类参数约束。上面那一个泛型方法言传身教了怎么采纳项目参数约束:

internal static class SecondaryConstraints
    {
        private static List<TBase> ConvertIList<T, TBase>(IList<T> list)
           where T : TBase
        {

            List<TBase> baseList = new List<TBase>(list.Count);
            for (Int32 index = 0; index < list.Count; index++)
            {
                baseList.Add(list[index]);
            }
            return baseList;
        }

        private static void CallingConvertIList()
        {
            //构造并初始化一个List<String>(它实现了IList<String>)
            IList<String> ls = new List<String>();
            ls.Add("A String");

            // 将IList<String>转换成IList<Object>
            IList<Object> lo = ConvertIList<String, Object>(ls);

            // 将IList<String>转换成IList<IComparable>
            IList<IComparable> lc = ConvertIList<String, IComparable>(ls);

            // 将IList<String>转换成IList<IComparable<String>>
            IList<IComparable<String>> lcs =
               ConvertIList<String, IComparable<String>>(ls);

            // 将IList<String>转换成IList<Exception>
            //IList<Exception> le = ConvertIList<String, Exception>(ls);    // 错误
        }
    }

 

 3 构造器约束

  贰个连串参数能够钦定零个如故三个构造器约束。钦点构造器约束也正是向编写翻译器承诺:一个点名的类型实参是落到实处了公共无参构造器的一个非抽象类型。

internal sealed class ConstructorConstraints
    {
        internal sealed class ConstructorConstraint<T> where T : new()
        {
            public static T Factory()
            {
                // 允许,因为值类型都有隐式无参构造器
                // 而约束要求任何引用类型也要有一个无参构造器
                return new T();
            }
        }
    }

  方今,Microsoft只协助无参构造器约束。

 

 4 别的可验证难题

 

  1)泛型类型变量的转型

  将贰个泛型类型的变量转型为另贰个种类是不法的,除非将其转型为与二个羁绊包容的档次:

 private void CastingAGenericTypeVariable1<T>(T obj)
        {
            Int32 x = (Int32)obj;    // 错误
            String s = (String)obj;  // 错误
        }

  上述两行错误是因为T能够是另外其他类型,不大概有限帮衬成功。

private void CastingAGenericTypeVariable2<T>(T obj)
{
      Int32 x = (Int32)(Object)obj;    // 不报错
      String s = (String)(Object)obj;  // 不报错
}

  以后固然能编写翻译通过,但运维时也不可能确定保证是没有错的。

  2)将二个泛型类型变量设为默许值

  将泛型类型变量设为null是不合法的,除非将泛型 类型约束成八个引用类型:

internal sealed class SettingAGenericTypeVariableToADefaultValue
    {
        private void SettingAGenericTypeVariableToNull<T>()
        {
            //T temp = null;    // 错误, 值类型不能设置为null,可考虑使用default('T')
        }

        private void SettingAGenericTypeVariableToDefaultValue<T>()
        {
            T temp = default(T);    // 正确
        }
    }

 

  3)将多个泛型类型变量与null进行相比较

  无论泛型类型是还是不是非约束,使用==或!=操作符将二个泛型类型变量与null实行比较都以合法的。

private void ComparingAGenericTypeVariableWithNull<T>(T obj)
{
     if (obj == null) { /* 对值类型来说,永远不会执行 */ }
}

  如若T被封锁成三个struct,C#编写翻译器会报错。

 

  4)八个泛型类型变量相相互比较

  要是泛型类型参数不是三个援引类型,对同一个泛型类型的三个变量实行相比是私下的:

private void ComparingTwoGenericTypeVariables<T>(T o1, T o2)
{
      //if (o1 == o2) { }    // 错误
}

  5)泛型类型变量作为操作书使用

  将操作符应用于泛型类型的操作数,会产出大量主题素材。无法将操作符应用于泛型,因为编写翻译器在编译时无能为力分明项目。

internal static class UsingGenericTypeVariablesAsOperands {
   private T Sum<T>(T num) where T : struct {
      T sum = default(T);
      for (T n = default(T); n < num; n++)
         sum += n;
      return sum;
   }
}

  上边代码会精华多谬误,比方:运算符”<“不能使用于”T”和”T”类型的操作数等。

  那是CLPRADO的泛型支持体系的叁个严重限制。

 

 

相关文章