zoukankan      html  css  js  c++  java
  • 泛型

    程序= 算法 + 数据。编程人员设计好一种算法,例如排序、比较、交换等等。这些算法应该应用于不同的数据类型,而不是为每个数据类型都写一个专有的算法。

    CLR 允许创建:泛型引用类型,泛型值类型,泛型接口,泛型委托。

    CLR 不允许创建:泛型枚举类型。

    在FCL 中泛型最明显的应用就是集合类,FLC 在 System.Collections.Generic 和 System.Collections.ObjectModel 命名空间中提供了多个泛型集合类。System.Collections.Concurrent 命名空间则提供了线程安全的泛型集合类。

     

    开放类型和封闭类型

    具有泛型类型参数的类型任然是类型,CLR 同样会为它创建内部的类型对象。这一点适合引用类型(类)、值类型(结构)、接口类型和委托类型。具有泛型类型的参数类型称为开放类型,CLR 禁止构造开放类型的任何实例。这类似于CLR 禁止构造接口类型的实例。

    代码引用泛型类型时可指定一组泛型类型实参。为所有类型参数都传递了实际的数据类型,类型就成了封闭类型。CLR允许构造封闭类型的实例。

    using System;
    using System.Collections.Generic;
    // A partially specified open type
    internal sealed class DictionaryStringKey<TValue> :
        Dictionary<String, TValue> {
    }
    public static class Program {
        public static void Main() {
                Object o = null;
                // Dictionary<,> is an open type having 2 type parameters
                Type t = typeof(Dictionary<,>);
                // Try to create an instance of this type (fails)
                o = CreateInstance(t);
                Console.WriteLine();
                // DictionaryStringKey<> is an open type having 1 type parameter
                t = typeof(DictionaryStringKey<>);
                // Try to create an instance of this type (fails)
                o = CreateInstance(t);
                Console.WriteLine();
                // DictionaryStringKey<Guid> is a closed type
                t = typeof(DictionaryStringKey<Guid>);
                // Try to create an instance of this type (succeeds)
                o = CreateInstance(t);
                // Prove it actually worked
                Console.WriteLine("Object type=" + o.GetType());
            }
            private static Object CreateInstance(Type t) {CHAPTER 12 Generics 273
            Object o = null;
            try {
                o = Activator.CreateInstance(t);
                Console.Write("Created instance of {0}", t.ToString());
            }
            catch (ArgumentException e) {
                Console.WriteLine(e.Message);
            }
            return o;
        }
    }

    编译并允许上述代码得到下面的结果:

    Cannot create an instance of System.Collections.Generic.
    Dictionary`2[TKey,TValue] because Type.ContainsGenericParameters is true.
    Cannot create an instance of DictionaryStringKey`1[TValue] because
    Type.ContainsGenericParameters is true.
    Created instance of DictionaryStringKey`1[System.Guid]
    Object type=DictionaryStringKey`1[System.Guid]

     

    泛型类型和继承

    泛型类型任然是类型,所以能从其他任何类型派生。使用泛型类型并指定类型参数时,实际是在CLR 中定义了一个新的类型对象,这个新的类型对象从泛型类型派生自的哪个类型派生。例如: List<T> 从 Object 派生,所以List<String> 和 List<Giud> 也从Object 派生。指定类型实参不影响继承层次结构,理解这一点,有助于你判断哪些强制类型转换是允许的,哪些不允许。

     

    泛型类型同一性

    同一性就是为了方便使用泛型类型,可以像C++中的宏定义那样将一个泛型类型用其他的符号代表。C# 允许使用简化的语法来引用泛型封闭类型,同时不会影响类型的相等性。这个语法要求在源文件顶部使用传统的using 指令,例如:

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

    执行下面的代码验证一下:

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

    代码爆炸

    使用泛型类型参数的方法在进行JIT 编译时,CLR 获取方法的IL,用指定的类型实参替换,然后创建恰当的本机代码(这些代码是为操作指定数据类型“量身定制的”),这正是泛型的重要特点。但这样做也有一个缺点: CLR 要为每种不同的方法/类型 组合生成本机代码。我们将这个现象称为代码爆炸。它可能会造成应用程序的工作集显著增大,从而损害性能。

    CLR 内建了一些措施能缓解代码爆炸。

    1、一次编译,重复使用。为特定的类型实参调用了一个方法后,以后再调用相同的类型实参的方法时,CLR只会在第一次编译代码。

    2、CLR 认为所有引用类型实参都完全相同,所以代码能够共享。对于任何引用类型的实参,都会调相同的代码。

    但是假如某个类型实参是值类型,CLR 就必须专门为哪个值类型生成本机代码。

     

    泛型接口

    如果没有泛型接口,每次用非泛型接口(如 IComparable)来操纵值类型都会发生装箱,而且会失去编译时的类型安全性。因此,CLR 提供了对泛型接口的支持。引用类型或值类型可指定类型实参实现泛型接口。也可保持类型实参的未指定状态来实现泛型接口。

      

    泛型委托

    CLR 支持泛型委托,目的是保证任何类型的对象都能以类型安全的方式传给回调方法。泛型委托允许值类型实例在传给回调方法时不进行任何装箱。

     

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

    委托的每个泛型类类型参数都可以标记为协变量或逆变量。利用这个功能,可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数可以是以下任何一种形式。

    • 不变量,意味着泛型类型参数不能改变。

    • 逆变量,意味着泛型类型参数可以从一个类更改为它的某个派生类。C# 用in 关键字标识逆变量。

    • 协变量,意味着泛型类型参数可以从一个类更改为它的某个基类。C# 用 out 关键字标识协变量。

     

    泛型和其他成员

    在C# 中,属性、索引器、事件、操作符方法、构造器和终结器本身不能有类型参数。但它们能在泛型类型中定义,而且这些成员中的代码能使用类型的类型参数。

     

    可验证型和约束

    约束的作用是限制能指定成泛型实参的类型数量。通过限制类型的数量,可以对哪些类型执行更多的操作。

    编译器/CLR 允许向类型参数应用各种约束。可以用一个主要约束、一个次要约束以及/或者一个构造器约束来约束类型参数。

    主要约束

    主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:SystemObject,SystemArray,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum 或者 System.Void。

    指定引用类型约束时,相当于向编译器承诺:一个指定的类型实参要么是与约束类型相同的类型,要么是从约束类型派生的类型。例如以下泛型类:

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

    有两个特殊的主要约束:class 和 struct。其中,class 约束向编译器承诺类型实参是引用类型。任何类类型、接口类型、委托类型或者数组类型都满足这个约束。

    struct 约束向编译器承诺类型实参是值类型。包括枚举在内的任何值类型都满足这个约束。但是编译器和 CLR 将任何System.Nullable<T> 值类型视为特殊类型,不满足这个 struct 约束。

     

    次要约束

    次要约束代表接口类型。这种约束向编译器承诺类型实参实现了接口。这种约束向编译器承诺类型实参实现了接口。由于能指定多个接口约束,所以类型实参必须实现了所有接口约束。

    还有一种次要约束称为 类型参数约束,有时也称为 裸类型约束。它允许一个泛型类型或方法规定:指定的类型实参要么就是约束的类型,要么就是约束的类型的派生类。一个类型参数可以指定零个或者多个类型参数约束。下面这个泛型方法演示了如何使用类型参数约束:

    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;
    }

    ConvertList 方法指定了两个类型参数,其中T 参数由Tbase 类型参数约束。意味着不管为T 指定什么类型实参,都必须兼容于为 TBase 指定的类型实参。

     

    构造器约束

    类型参数可指定零个或一个构造器约束,它向编译器承诺类型实参是实现了公共无参构造器的非抽象类型。以下示例类使用构造器约束来约束它的类型参数:

    internal sealed class ConstructorConstraint<T> where T : new() {
        public static T Factory() {
            // Allowed because all value types implicitly
            // have a public, parameterless constructor and because
            // the constraint requires that any specified reference
            // type also have a public, parameterless constructor
            return new T();
        }
    }

    在上述例子中 new T() 是合法的,因为已知 T 是拥有公共无参构造器的类型。对所有值类型来说,这一点(拥有公共无参构造器)肯定成立。对于作为类型实参指定的任何引用类型,这一点也成立,因为构造器约束要求它必须成立。

     

    其他可验证性问题

    1、将泛型类型的变量转型为其他类型是非法的,除非转型为与约束兼容的类型。

    private static void CastingAGenericTypeVariable1<T>(T obj) {
        Int32 x = (Int32) obj; // Error
        String s = (String) obj; // Error
    }
    private static void CastingAGenericTypeVariable2<T>(T obj) {
        Int32 x = (Int32) (Object) obj; // No error
        String s = (String) (Object) obj; // No error
    }

    2、将泛型类型变量设为 null 是非法的,除非将泛型类型约束成引用类型。

    3、无论泛型类型是否被约束,使用== 或 != 操作符将泛型类型变量与 null 进行比较都是合法的。如果T 被约束成 struct , C# 编译器会报错。值类型的变量不能与null 进行比较,因为结果始终一样。

    4、如果泛型类型参数不能肯定是引用类型,对同一个泛型类型的两个变量进行比较是非法的:

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

    5、将操作符用于泛型类型的操作会引发大量问题。C# 知道如何解释应用于基元类型的操作符(比如+,- ,* 和/)。但是不能将这些操作符用于泛型类型的变量。编译器在编译时确定不了类型。

     

  • 相关阅读:
    chrome浏览器中安装以及使用Elasticsearch head 插件
    windows10 升级并安装配置 jmeter5.3
    linux下部署Elasticsearch6.8.1版本的集群
    【Rollo的Python之路】Python 爬虫系统学习 (八) logging模块的使用
    【Rollo的Python之路】Python 爬虫系统学习 (七) Scrapy初识
    【Rollo的Python之路】Python 爬虫系统学习 (六) Selenium 模拟登录
    【Rollo的Python之路】Python 爬虫系统学习 (五) Selenium
    【Rollo的Python之路】Python 爬虫系统学习 (四) XPath学习
    【Rollo的Python之路】Python 爬虫系统学习 (三)
    【Rollo的Python之路】Python sys argv[] 函数用法笔记
  • 原文地址:https://www.cnblogs.com/mingjie-c/p/11665501.html
Copyright © 2011-2022 走看看