泛型

  • ​​什么是泛型​​
  • ​​C#中的泛型​​
  • ​​泛型类​​
  • ​​声明泛型类​​
  • ​​创建构造类型​​
  • ​​创建变量和实例​​
  • ​​类型参数的约束​​
  • ​​泛型方法​​
  • ​​扩展方法和泛型类​​
  • ​​泛型结构​​
  • ​​泛型委托​​
  • ​​泛型接口​​

什么是泛型

  泛型(generic)特性提供了一种更优雅的方式,可以让多个类型共享一组代码。泛型允许我们声明类型参数化(type-parameterized)的代码,可以用不同的类型进行实例化。也就是说,我们可以用“类型占位符”来写代码,然后在创建类的实例时指明真实的类型

C#中的泛型

  • C#提供了5中泛型:类、结构、接口、委托和方法。前四中是类型,方法是成员

    泛型类型是类型的模板

    泛型和用户自定义类型

泛型类

  泛型类不是实际的类,而是类的模板,所以我们必须先从它们构建实际的类类型,然后创建这个构建后的类类型的实例

  • 在某些类型上使用占位符来声明一个类
  • 为占位符提供真实类型。这样就有了真实类的定义,填补了所有的“空缺”。该类型称为构造类型(constructedtype)
  • 创建构造类型的实例

CSharp(C#)语言_高级篇(泛型集合)_Microsoft


从泛型类型创建实例

声明泛型类

声明一个简单的泛型类和声明普通类差不多

区别如下:

  • 在类名之后放置一组尖括号
  • 在尖括号中用逗号分隔的占位符字符串来表示希望提供的类型。这叫做类型参数(typcparameter)
  • 在泛型类声明的主体中使用类型参数来表示应该替代的类型
//          类型参数
class SomeClass < T1, T2 >
{
// T1、T2表示在当前位置使用类型
public T1 SomeVar = new T1();
public T2 OtherVar = new T2();
}

创建构造类型

  一旦创建了泛型类型,我们就需要告诉编译器能使用哪些真实类型来替代占位符(类型参数)。编译器获取这些真实类型并创建构造类型(用来创建真实类对象的模板)

  创建构造类型的语法如下,包括列出类名并在尖括号中提供真实类型来替代类型参数。要替代类型参数的真实类型叫做类型实参(typeargument)

​SomeClass < short, int >​

CSharp(C#)语言_高级篇(泛型集合)_后端_02


类型参数类型实参的区别(下图)

CSharp(C#)语言_高级篇(泛型集合)_c#_03

创建变量和实例

MyNonGenClass        myNGC = new MyNonGenClass       ();
// 构造类 构造类
SomeClass < short, int > mySc1 = new SomeClass < short, int >();
var mySc2 = new SomeClass < short, int >();

ps: 可以从同一个泛型类构建出很多不同的类类型。每一个都有独立的类类型,就好像它们都有独立的非泛型声明一样

非泛型栈和泛型栈之间的区别

-

非泛型

泛 型

源代码大小

更大:我们需要为每一种类型编写一个新的实现

更小:不管构造类型的数量有多少,我们只需要一个实现

可执行大小

无论每一个版本的栈是否会被使用,都会在编译的版本中出现

可执行文件中只会出现有构造类型的类型

写的难易度

易于书写,因为它更具体

比较难写,因为它更抽象

维护的难易度

更容易出问题,因为所有修改需要应用到每一个可用的类型上

易于维护,因为只需要修改一个地方

类型参数的约束

约束使用where字句列出

  • 每一个有约束的类型参数有自己的where子句
  • 如果形参有多个约束,它们在where子句中使用逗号分隔
    ​​​where 参数类型 : 约束1, 约束2, ...​

where 子句的要点:

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

约束类型和次序

类名

只有这个类型的类或从它继承的类才能用作类型实参

class

任何引用类型,包括类、数组、委托和接口都可以用作类型实参

struct

任何值类型都可以用作类型实参

接口名

只有这个接口或实现这个接口的类型才能用作类型实参

new()

任何带有无参公共构造函数的类型都可以用作类型实参。这叫做构造函数约束

ps:where子句可以以任何次序列出。然而,where子句中的约束必须有特定的次序

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

泛型方法

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

CSharp(C#)语言_高级篇(泛型集合)_泛型集合_04


声明泛型方法

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

  • 泛型方法有两个参数列表
  • 封闭在圆括号内的方法参数列表
  • 封闭在尖括号内的类型参数列表
  • 要声明泛型方法,需要:
  • 在方法名称之后 和方法参数之前放置类型参数列表
  • 在方法参数列表后放置可选的约束子句

eg:

public void PrintData <S, T>(string str, int t) where S:Person
{
...
}

调用泛型方法
  要调用泛型方法,应该在方法调用时提供类型实参
  ​​​MyMethod <short,int>();​

泛型方法的示例

class Simple                    // 非泛型类
{
static public void ReverseAndPrint<T>(T[] arr) // 泛型方法
{
Array.Reverse(arr);
foreach (T iten in arr) // 使用类型参T
Console.krite("(0),", item.ToString());
Console.Mriteline(*);
}
}

class Program
{
static void Kain()
{
//创建各种类型的数糕
var intArray … new int[] {3, 5, 7, 9, 11 };
var stringArray - new string[] { "first", "second", "third" };
var doubleArray * nex double[]{ 3.567, 7.891, 2.345 };

Simple.ReverseAndPrintcint>(intArray); // 调用方法
Simple.ReverseAndPrint(intArray); // 推断类型并调用

Simple.ReverseAndPrint<string>(stringArray); // 调用方法
Simple.ReverseAndPrint(stringArray); // 推断类型并调用

Simple.ReverseAndPrintcdouble>(doubleArray); // 调用方法
Simple.ReverseAndPrint(doubleArray); // 推断类型并调用
}
}

上述案例的结果:

  11, 9, 7, 5, 3
  3, 5, 7, 9, 11

  third, second, first
  first, second, third

  2.345, 7.891, 3.567
  3.567, 7.891, 2.345

扩展方法和泛型类

泛型类的扩展方法

  • 必须声明为static
  • 必须是静态类的成员
  • 第一个参数类型中必须有关键字this,后面是扩展的泛型类的名字

案例

static class ExtendHolder
{
public static void Print<T>(this Holder<T> h)
{
T[] vals= h.GetValues();
Console.NriteLine("{0},\t{1},\t{2}",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(string[] args)
{
var intHolder = new Holder<int>(3, 5, 7);
var stringHolder = new Holder<string>("a1", "b2", "c3");
intHolder.Print();
stringHolder.Print();
}
}

上述案例的结果:

3, 5, 7
a1, b2, c3

泛型结构

  • 与泛型类相似,范型结构可以有类型参数和约束
  • 范型结构的规则和条件与泛型类是一样的

案例

struct PieceOfData<T> //泛型结构
{
public PieceOfData(T value)
{
_data = value;
}

private T _data;
public T Data
{
set _data = value;
{
}

class Program
{
static void Main()
{
// 构造类型
var intData = new Piece0fData<int>(10);
// 构造类型
var stringData = new PieceOfData<string>("Hi there.");

Console.WriteLine("intData = {0}", intData.Data);
Console.WriteLine("stringData = {0}", stringData.Data);
}
}

案例结果:

intData = 10
stringData = Hi there

泛型委托

  • 泛型委托和非泛型委托非常相似,不过类型参数决定了能接受什么样的方法
  • 要声明泛型委托,在委托名称后、委托参数列表之前的尖括号中放置类型参数列表

案例

delegate void MyDelegate<T>(T value);         // 泛型委托
class Simple
{
static public void PrintString(string s) // 方法匹配委托
{
Console.WriteLine(s);
}
static public void PrintUpperString(string s) // 方法匹配委托
{
Console.MriteLine("(O)",s.ToUpper());
}
}

class Program
{
static void Main()
{
//创建委托的实例
var myDel = new MyDelegate<string>(Simple.PrintString);
//添加方法
myDel += Simple.PrintUpperString;
//调用委托
myDel("Hi There.");
}
}

案例结果:

Hi There
Hi There

泛型接口

  泛型接口允许我们编写参数和接口成员返回类型是泛型类型参数的接口。泛型接口的声明和非泛型接口的声明差不多,但是需要在接口名称之后的尖括号中放置类型参数

案例

interface IMyIfc<T> // 泛型接口
{
T RetrnIt( T inValue);
}

// 类型参数 泛型接口
class Simple<S> :IMyIfc<S> // 泛型类
{
public S ReturnIt(S inValue) //实现泛型接口
{
return inValue
}
}

class Program
{
static void Main()
{
var trivInt = new Simple<int>();
var trivString = new Simple<string>();

Console.WriteLine("{0)",trivInt.ReturnIt(5));
Console.NriteLine("{o)",trivString.ReturnIt("Hi there."));
}
}

案例结果:

5
Hi there.

ps:泛型接口的实现必须唯一