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

猜你喜欢

转载自www.cnblogs.com/jizhiqiliao/p/10649076.html