• Generic


        • 1.什么是泛型
        • 2.构造器的定义
        • 3.默认值的指定
        • 4.元组
        • 5.泛型类
          • 5.1 声明泛型类
          • 5.2 创建构造类
          • 5.3 静态成员
          • 5.4 创建变量和实例
        • 6.类型参数的约束
          • 6.1 Where子句
          • 6.2 约束类型和次序
        • 7.泛型方法
          • 7.1 声明泛型方法
          • 7.2 调用泛型方法
          • 7.3泛型方法的实例
        • 8.扩展方法和泛型类
        • 9.泛型结构
        • 10.泛型委托
        • 11.泛型接口
          • 11.1 使用泛型接口的示例
          • 11.2 泛型接口的实现必须唯一
        • 12.协变与逆变
          • 12.1 委托的协变与逆变
          • 12.2 接口的协变与逆变

    1.什么是泛型

    泛型(generic)特性提供了一种更优雅的方式,可以让多个类型共享一组代码,泛型允许声明类型参数化的代码,可以使用不同的类型进行实例化,在创建类的实例时指明真实类型。泛型类是类型的模板,关系如下图所示:
    image
    C#提供了5中泛型:类、结构、接口、委托和方法。前4种是类型,而方法是成员。下图演示了泛型类型如何用于其他类型。
    image
    泛型的简单实例如下所示:

    class MyStack<T>
    {
        int stackPointer = 0;
        T[] stactArray;
        public void Push(T x) {...}
        public T Pop() {...}
    }
    

    泛型的优点如下:

    • 保证类型安全
    • 减少装箱、拆箱操作,无需从object进行强制类型转换
    • 多个类型共享一组代码,提高代码的复用性

    命名规范:参数类型形参应包含T前缀。


    2.构造器的定义

    泛型类或结构的构造器不要求类型参数。

    public struct Pair<T>
    {
        public Pair(T first, T second)
        {
            First = first;
            Second = second;
        }
        public T First{ get; set; }
        public T Second { get; set; }
    }
    

    3.默认值的指定

    public struct Pair<T>
    {
        public Pair(T first) // struct需要初始化所有字段
        {
            First = first;
            Second = default(T); // 不指定default报错
        }
        public T First{ get; set; }
        public T Second { get; set; }
    }
    

    4.元组

    通过元数的不同来重载类型定义:

    public class Tuple {...}
    public class Tuple<T1>: ... {...}
    public class Tuple<T1, T2>: ... {...}
    public class Tuple<T1, T2, T3>: ... {...}
    public class Tuple<T1, T2, T3, T4>: ... {...}
    public class Tuple<T1, T2, T3, T4, T5>: ... {...}
    public class Tuple<T1, T2, T3, T4, T5, T6>: ... {...}
    public class Tuple<T1, T2, T3, T4, T5, T6, T7>: ... {...}
    public class Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>: ... {...}
    
    class Program
    {
        static void Main(string[] args)
        {
            string outparam = "";
            int returnvalue = FunOutParamDemo(out outparam); // 使用out拿到多个返回值
            Console.WriteLine(returnvalue + "    " + outparam);
            Tuple<int, string> r = FunTupleParamDemo(); // 使用元组拿到多个返回值
            Console.WriteLine(r.Item1 + "    " + r.Item2);
            Console.ReadKey();
        }
        public static int FunOutParamDemo(out string o)
        {
            o = "returnValue";
            return 10;
        }
        public static Tuple<int, string> FunTupleParamDemo()
        {
            return new Tuple<int, string>(10, "returnValue");
        }
    }
    

    output

    10    returnValue
    10    returnValue
    
    public static Tuple<int , int> MinMax(int a, int b)
    {
        return new Tuple<int, int>(Math.Min(a, b), Math.Max(a, b)); // Item1:Min, Item2:Max
    }
    
    Main:
    var r = MinMax(1, 2);
    Console.WriteLine($"min = {r.Item1}, max = {r.Item2}"); // 通过注释注明项 Item# 的意义
    

    output

    min = 1, max = 2
    

    其中TRest中可以存储另一个Tuple,由此Tuple可以无限大。

    var t = Tuple.Create(0, 1, 2, 3, 4, 5, 6, Tuple.Create(7, 8));
    Console.WriteLine($"{t.Item1}, {t.Item2}, {t.Item3}, {t.Item4}, {t.Item5}, " +
        $"{t.Item6}, {t.Item7}, {t.Rest.Item1.Item1}, {t.Rest.Item1.Item2}");
    

    使用Tuple的Create()工厂方法:

    // 用静态 Tuple 重写 MinMax()
    public static Tuple<int , int> MinMax(int a, int b)
    {
        return Tuple.Create(Math.Min(a, b), Math.Max(a, b));
    }
    
    Tuple<string, Contact> t1;
    t1 = Tuple.Create("123456", new Contact("kyle"));
    Tuple<string, Contact> t2;
    t2 = new Tuple<string, Contact>("123456", new Contact("kyle"));
    
    class Program
    {
        static void Main(string[] args)
        {
            Tuple<int> test1 = new Tuple<int>(34);
            Tuple<string, int> test2 = Tuple.Create("str", 2);
            Tuple<int, int> test3 = new Tuple<int, int>(2, 2);
            //8个元素的元组(注意,Tuple<类型...>基本"类型"最多7个, 第八个元素类型必须也为元组)
            Tuple<int, int, int, int, int, int, int, Tuple<int>> test4 =
            new Tuple<int, int, int, int, int, int, int, Tuple<int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int>(8));
            Console.WriteLine(test1.Item1);
            Console.WriteLine(test2.Item1 + test2.Item2);
            Console.WriteLine(test4.Item1 + test4.Item2);
            Console.WriteLine(test4.Item1 + test4.Item2 + test4.Item3 + test4.Item4 + test4.Item5 + test4.Item6 + test4.Item7 + test4.Rest.Item1);
            Console.ReadKey();
        }
    }
    

    output

    34
    str2
    3
    36
    

    由此可见,使用Tuple的Create()工厂方法无需指定类型参数。


    5.泛型类

    创建和使用常规非泛型的类有两个步骤:声明类和创建类的实例。而对于泛型类需要先构建实际的类类型,然后才能创建实例。

    • 在某些类型上使用占位符来声明一个类
    • 为占位符提供真实类型,该类型称为构造类型
    • 创建构造类型的实例

    泛型类创建流程如下图所示:
    image


    5.1 声明泛型类

    声明一个简单的泛型类和声明普通类差不多,区别如下:

    • 在类名之后放一组尖括号
    • 在尖括号中用逗号分隔的占位符字符串来表示提供的类型,这叫做类型参数
    • 在泛型类声明的主体中使用类型参数来表示应该替代的类型

    如下代码声明了一个叫做SomeClass的泛型类。

    class SomeClass <T1, T2>
    {
        public T1 someVar = new T1();
        public T2 otherVar = new T2();
    }
    

    在泛型类型申明中并没有特殊关键字,取而代之的是尖括号中的类型参数列表,它可以区分泛型类与普通类的声明。


    5.2 创建构造类

    一旦创建了泛型类型,就要告诉编译器使用哪些真实类型来替代占位符,替代类型参数的真实类型叫做类型实参。

    class SomeClass<T1, T2> {...} // T1T2是类型参数(开放类型,不可创建实例)
    SomeClass<short, int> // shortint是类型实参(封闭类型,可创建实例)
    

    5.3 静态成员

    泛型类的静态成员只能在类的一个实例中共享,

    public class StaticDemo<T>
    {
        public static int x;
    }
    StaticDemo<string>.x = 4;
    StaticDemo<int>.x = 5;
    Console.WriteLine(StaticDemo<string>.x);
    

    output

    4
    

    5.4 创建变量和实例

    如下代码所示非泛型类与泛型类创建对象:

    MyNonGenClass myNGC = new MyNonGenClass(); // 非泛型类声明对象
    SomeClass<short, int> mySc1 = new SomeClass<short, int>(); //泛型类声明对象
    var mySc2 = SomeClass<short, int>(); // 匿名对象
    

    与非泛型类相同,声明和实例可以分开进行:

    SomeClass<short, int> mySc1; // 声明
    mySc1 = new SomeClass<short, int>(); //实例化
    

    非泛型类与泛型类直接的区别如下表所示。

     非泛型泛型
    源代码大小 更大:需要为每一种类型编写代码 更小:不管多少类型,只需一个是实现
    可执行大小 所有类型的实现代码都会被编译 只会根据提供的类型实参具体实现
    写的难易度 易于书写,因为它更具体 比较难写,因为它更抽象
    维护的难易度 同一个修改,所有可用类型上都要实现 易于维护,只要修改一处

    6.类型参数的约束

    要让泛型变得更有用,需要提供额外的信息让编译器知晓可以接受哪些类型。
    如下代码所示,编译器将会产生一个错误信息。

    class Simple<T>
    {
        static void bool LessThan(T i1, T i2)
        {
            return i1 < i2; // 错误
        }
    }
    

    可以提供的额外信息叫做约束(constrain),只有符合约束的类型才可以作为类型实参。

    6.1 Where子句

    约束使用Where子句列出。

    • 每一个有约束的类型参数都有自己的where子句
    • 如果类型参数有多个约束,则它们在where子句中使用逗号分隔

    where子句的语法如下:

    where TypeParam : constrain1, constrain2, ...
    

    有关where子句的要点如下。

    • 它们在类型参数列表的关闭尖括号后列出
    • 它们不使用逗号或其他符号分隔
    • 它们可以以任何次序列出
    • where是上下文关键字,所以可以在其他上下文中使用

    如下所示,其中T1未绑定,T2、T3具有约束。

    class MyCalss <T1, T2, T3> where T2: Customer where T3: IComparable {...}
    

    6.2 约束类型和次序

    共有5种类型的约束,如下表所示:

    约束类型描述
    类名 只有这个类型的类或从它继承的类才能用作类型实参
    class 任何引用类型,包括类、数组、委托和接口都可以用作类型实参
    struct 任何值类型都可以用作类型实参
    接口名 只有这个接口或者实现这个接口的类型才能用作类型实参
    new() 任何带有无参公共构造函数的类型都可以用作类型实参,这叫做构造函数约束

    where子句可以以任何次序列出,但where子句中的约束必须有特定的顺序

    • 最多只有一个主约束,如果有则必须放在第一个
    • 可以有任意多个接口名约束
    • 如果存在构造函数约束,则必须放在最后

    约束顺序如下所示:

    Primary
    (0 or 1)
    Secondary
    (0 or more)
    Constructor
    (0 or 1)
    ClassName
    class
    struct
    InterfaceName new()

    7.泛型方法

    与其他泛型不一样,方法是成员,不是类型。泛型方法可以在泛型类和非泛型类以及结构和接口中声明,如下图所示:
    image

    7.1 声明泛型方法

    泛型方法具有类型参数列表和可选的约束。

    • 泛型方法有两个参数列表
      • 封闭在原括号内的方法参数列表
      • 封闭在见括号内的类型参数列表
    • 要声明泛型方法需要:
      • 在方法名称后和方法参数列表前放置类型参数列表
      • 在方法参数列表后放置可选的约束子句
    public void PrintData<S, T> (S p, T t) where S: Person {...}
    

    7.2 调用泛型方法

    void Do<T1, T2>(T1 t1, T2 t2)
    {
        T1 someVar = t1;
        T2 otherVar = t2;
    }
    Do<int, double>(aVal, bVal);
    

    推断类型 编译器有时可以从方法参数中推断出赋给泛型方法的类型实参,

    public void MyMethod<T>(T t){...}
    int myInt = 5;
    MyMethod(myInt) // MyMethod<int>(myInt)简化
    

    但当泛型方法的方法参数列表为空时,则必须在类型参数列表中指明类型参数。

    public void MyMethod<T>()
    {
        Console.Write(typeof(T));
    }
    MyMethod<int>();
    

    7.3泛型方法的实例

    class Simple
    {
        public static void ReverseAndPrint<T>(T[] arr)
        {
            Array.Reverse(arr);
            foreach (T item in arr)
                Console.Write(item.ToString() + " ");
            Console.WriteLine();
        }
    }
    class Program
    {
        static void Main()
        {
            var intArray = new int[] { 3, 5, 7, 9, 11 };
            var stringArray = new string[] { "first", "second", "third" };
            var doubleArray = new double[] { 1.23, 2.34, 5.33 };
            Simple.ReverseAndPrint<int>(intArray);
            Simple.ReverseAndPrint(intArray);
            Simple.ReverseAndPrint(stringArray);
            Simple.ReverseAndPrint(doubleArray);
            Console.ReadKey();
        }
    }
    

    output:

    11 9 7 5 3
    3 5 7 9 11
    third second first
    5.33 2.34 1.23
    

    8.扩展方法和泛型类

    泛型类的扩展方法需满足以下要求:

    • 必须声明为static
    • 必须是静态类的成员
    • 第一个参数类型中必须有关键字this,后面是扩展的泛型类的名字
    static class ExtendHolder
    {
        public static void Print<T>(this Holder<T> h)
        {
            T[] vals = h.Getvalues();
            Console.WriteLine($"{vals[0]}, {vals[1]}, {vals[2]}");
        }
    }
    class Holder<T>
    {
        T[] vals = new T[3];
        public Holder(T v0, T v1, T v2)
        {
            vals[0] = v0;
            vals[1] = v1;
            vals[2] = v2;
        }
        public T[] Getvalues()
        {
            return vals;
        }
    }
    class Program
    {
        static void Main()
        {
            var intHolder = new Holder<int>(3, 5, 7);
            var stringHolder = new Holder<string>("a1", "a2", "a3");
            intHolder.Print();
            stringHolder.Print();
            Console.ReadKey();
        }
    }
    

    output

    3, 5, 7
    a1, a2, a3
    

    9.泛型结构

    与泛型类相似,泛型结构可以有类型参数和约束,其规则与条件也与泛型类相同。

    struct PieceOfdata<T>
    {
        public PieceOfdata(T value) { data = value; }
        private T data;
        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }
    class Program
    {
        static void Main()
        {
            var intData = new PieceOfdata<int>(10);
            var stringData = new PieceOfdata<string>("hello");
            Console.WriteLine($"intData = {intData.Data}");
            Console.WriteLine($"stringdata = {stringData.Data}");
            Console.ReadKey();
        }
    }
    

    output

    intData = 10
    stringdata = hello
    

    10.泛型委托

    • 要声明泛型委托,在委托名称后、委托参数列表前的尖括号中放置类型参数列表
    • 有两个参数列表:委托形参列表和类型参数列表
    • 类型参数的范围包括:
      • 返回值
      • 形参列表
      • 约束子句
    delegate void MyDelegate<T>(T value);
    class Simple
    {
        static public void PrintString(string s)
        {
            Console.WriteLine(s);
        }
        static public void PrintUpperString(string s)
        {
            Console.WriteLine(s.ToUpper());
        }
    }
    class Program
    {
        static void Main()
        {
            var myDel = new MyDelegate<string>(Simple.PrintString);
            myDel += Simple.PrintUpperString;
            myDel("hi there");
            Console.ReadKey();
        }
    }
    

    output

    hi there
    HI THERE
    

    11.泛型接口

    interface IMyIfc<T> // 泛型接口
    {
        T ReturnIt(T inValue);
    }
    class Simple<S> : IMyIfc<S> // 泛型类
    {
        public S ReturnIt(S inValue) // 实现泛型接口
        {
            return inValue;
        }
    }
    class Program
    {
        static void Main()
        {
            Simple<int> trivInt = new Simple<int>();
            Simple<string> trivString = new Simple<string>();
            Console.WriteLine(trivInt.ReturnIt(5));
            Console.WriteLine(trivString.ReturnIt("hello"));
            Console.ReadKey();
        }
    }
    

    output

    5
    hello
    

    11.1 使用泛型接口的示例

    • 与其他泛型相似,实现不同类型参数的泛型接口是不同的接口
    • 可以在非泛型类型中实现泛型接口
    interface IMyIfc<T>
    {
        T ReturnIt(T inValue);
    }
    class Simple: IMyIfc<int>, IMyIfc<string>
    {
        public int ReturnIt(int inValue)
        {
            return inValue;
        }
        public string ReturnIt(string inValue)
        {
            return inValue;
        }
    }
    class Program
    {
        static void Main()
        {
            Simple simple = new Simple();
            Console.WriteLine(simple.ReturnIt(5));
            Console.WriteLine(simple.ReturnIt("hello"));
            Console.ReadKey();
        }
    }
    

    output

    5
    hello
    

    11.2 泛型接口的实现必须唯一

    实现泛型类接口时,必须保证类型实参组合不会在类型中产生两个重复的接口。

    class Simple<S>: IMyIfc<int>, IMyIfc<S> //错误,存在潜在冲突
    {
        public int ReturnIt(int inValue)
        {
            return inValue;
        }
        public S ReturnIt(S inValue) // 当S为int时产生冲突
        {
            return inValue;
        }
    }
    

    泛型接口不会与非泛型接口产生冲突,

    interface IMyIfc
    {
        int ReturnIt(int inValue);
    }
    interface IMyIfc<T>
    {
        T ReturnIt(T inValue);
    }
    
    class Simple<S>: IMyIfc<S>, IMyIfc //无冲突
    {
        public S ReturnIt(S inValue)
        {
            return inValue;
        }
        public int ReturnIt(int inValue)
        {
            return inValue + 10 ;
        }
    }
    class Program
    {
        static void Main()
        {
            Simple<double> simple1 = new Simple<double>(); // 调用泛型接口
            Simple<int> simple2 = new Simple<int>(); // 调用非泛型接口
            Console.WriteLine(simple1.ReturnIt(5.1));
            Console.WriteLine(simple2.ReturnIt(5));
            Console.ReadKey();
        }
    }
    

    output

    5.1
    15
    

    12.协变与逆变

    在.NET中,参数类型是协变的,假定有Shape类和Rectangle类,Rectangle派生自Shape类。现声明Display()方法接受Shape类型的对象作为其参数

    public void Display(Shape o) {...}
    

    此时Display的参数可以传入派生自Shape基类的任意对象,

    var r = new Rectangle( Width = 5, Length = 10 );
    Display(r);
    

    方法的返回类型是逆变的。当方法返回一个Shape时,不能把它赋予Rectangle,因为Shape不一定是Rectangle,反之可行,

    public Rectangle GetRectangle();
    Shape s = GetRectangle();
    

    若果要在泛型中实现协变与逆变,则需要out与in关键字标注。
    如果泛型类型用out关键字标注,泛型委托(接口)就是协变的,这也意味着返回类型只能是T。

    public delegate T Dele<out T>();
    public interface IMyIfc<out T>
    

    如果泛型类型用in关键字标注,泛型委托(接口)就是逆变的,委托(接口)只能把泛型类型T用作其方法的输入。

    public delegate T Dele<in T>();
    public interface IMyIfc<in T>
    

    协变与逆变的不同如下图所示:
    image

    12.1 委托的协变与逆变

    将派生类的对象实例赋值给基类的变量,叫做赋值兼容性。

    class Animal
    {
        public int numberOfLegs = 4;
    }
    class Dog: Animal
    {
    }
    class Program
    {
        static void Main()
        {
            Animal a1 = new Animal();
            Animal a2 = new Dog(); // 将派生类对象实例赋给基类变量
            Console.WriteLine($"number of animal legs: {a1.numberOfLegs}");
            Console.WriteLine($"number of dog legs: {a2.numberOfLegs}");
            Console.ReadKey();
        }
    }
    

    output

    number of animal legs: 4
    number of dog legs: 4
    

    下面对代码进行扩展,添加一个委托,

    class Animal
    {
        public int numberOfLegs = 4;
    }
    class Dog: Animal
    {
    }
    delegate T Factory<T>();
    class Program
    {
        static Dog MakeDog()
        {
            return new Dog();
        }
        static void Main()
        {
            Factory<Dog> dog = MakeDog; // 创建委托对象
            Factory<Animal> animal = dog; // 尝试赋值委托对象
            Console.WriteLine(animal().numberOfLegs);
            Console.ReadKey();
        }
    }
    

    以上使用委托赋值失败,是因为Factory<Dog>与Factory<Animal>都派生自delegate类,两个委托对象是同级关系。
    如果派生类只是用于输出值,那么这种结构化的委托有效性之间的常数关系叫做协变,使用关键字out指定类型参数的协变。

    delegate T Factory<out T>();
    

    在期望传入基类时允许传入派生对象的特性叫做逆变,可以在类型参数中显式使用in关键字来实现。

    delegate T Factory<in T>();
    

    12.2 接口的协变与逆变

    class Animal { public string Name; }
    class Dog : Animal { }
    interface IMyIfc<out T>
    {
        T GetFirst();
    }
    class SimpleReturn<T> : IMyIfc<T>
    {
        public T[] items = new T[2];
        public T GetFirst() { return items[0]; }
    }
    class Program
    {
        static void Do(IMyIfc<Animal> returner)
        {
            Console.WriteLine(returner.GetFirst().Name);
        }
        static void Main()
        {
            SimpleReturn<Dog> dogReturner = new SimpleReturn<Dog>();
            dogReturner.items[0] = new Dog() { Name = "Bob" };
            IMyIfc<Animal> animalReturner = dogReturner;
            Do(dogReturner);
            Console.ReadKey();
        }
    }
    

    output

    Bob
  • 相关阅读:
    flask -服务端项目搭建
    蓝图 Blueprint
    flask-session
    flask-数据库操作 / ORM/Flask-SQLAlchemy/数据表操作/数据操作/数据库迁移
    在 Flask 项目中解决 CSRF 攻击
    Flask-Script 扩展/自定义终端命令/Jinja2模板引擎
    Flask项目创建/http的会话控制/Cookie/Session/请求钩子/异常捕获/context
    redtiger sql injection 练习
    流畅的python--序列构成的数组
    流畅的python--python的数据模型
  • 原文地址:https://www.cnblogs.com/jizhiqiliao/p/10649076.html
Copyright © 2020-2023  润新知