• 泛型


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

    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# 知道如何解释应用于基元类型的操作符(比如+,- ,* 和/)。但是不能将这些操作符用于泛型类型的变量。编译器在编译时确定不了类型。

     

  • 相关阅读:
    Docker-(三).Dockerfile
    Docker-(二).使用操作
    Docker-(一).安装
    Mac-brew install mysql
    Mac-brew
    Selenium HTMLTestRunner 执行测试成功但无法生成报告
    12.Python爬虫利器三之Xpath语法与lxml库的用法
    11.Python-第三方库requests详解(三)
    10.Python-第三方库requests详解(二)
    9.Python爬虫利器一之Requests库的用法(一)
  • 原文地址:https://www.cnblogs.com/mingjie-c/p/11665501.html
Copyright © 2020-2023  润新知