概念
通过参数化类型来实现在同一份代码上操作多种数据类型。利用“参数化类型”将类型抽象化,从而实现灵活的复用
泛型是类型的模板
C#中提供5中泛型:类、结构、接口、委托和方法
前四种是类型,而方法是成员
例
声明一个MyIntStack类,该类实现一个int类型的栈,进行入栈和出栈操作
class MyIntStack
{
int StackPointer = 0;
int[] StackArray;
public void Push(int x) {
....
}
public int Pop() {
...
}
}
如果这时需要使用float类型,常用的就是将代码重新复制一份,修改类型,这样就产生很多重复代码,浪费额外空间,而采用泛型就可避免
采用泛型
class MyIntStack<T>
{
int StackPointer = 0;
T[] StackArray;
public void Push(T x) {
....
}
public T Pop() {
...
}
}
这样类型就不限制了
泛型类
声明泛型类
规则
- 在类名之后放置一组尖括号。
- 在尖括号中用逗号分隔的占位符字符串来表示希望提供的类型。这叫做类型参数。
- 在泛型类声明的主体中使用类型参数来表示应该替代的类型。
语法
//T1、T2是类型占位符
class SomeClass<T1, T2>
{
public T1 SomeeVar = new T1();
public T2 OtherVar = new T2();
}
构造类型
用来告诉编译器要使用哪些真实类型来替代占位符
规则
列出类名并在尖括号中提供真实类型来替代类型参数
语法
SomeClass<short, int>
//则会将T1,T2按照参数位置进行替换
class SomeClass<short, int>
{
public short SomeeVar = new short();
public int OtherVar = new int();
}
实例
进行栈查找
class MyStack<T>
{
T[] StackArray;
int StackPointer = 0; //当前栈元素个数
const int MaxStack = 10;
public MyStack()
{
StackArray = new T[MaxStack];
}
//判栈满
bool IsStackFull
{
get
{
return StackPointer >= MaxStack;
}
}
//判栈空
bool IsStackEmpty
{
get
{
return StackPointer <= 0;
}
}
public void Push(T x)
{
if (!IsStackFull)
StackArray[StackPointer++] = x;
}
public T Pop()
{
return (!IsStackEmpty) ? StackArray[--StackPointer] : StackArray[0];
}
public void Print()
{
for(int i = StackPointer - 1; i >= 0; i--)
{
Console.WriteLine($"Value:{StackArray[i]}");
}
}
}
class Program
{
static void Main(string[] args)
{
MyStack<int> StackInt = new MyStack<int>();
MyStack<string> StackString = new MyStack<string>();
StackInt.Push(3);
StackInt.Push(5);
StackInt.Push(7);
StackInt.Push(9);
StackInt.Print();
StackString.Push("输入字符");
StackString.Push("你好");
StackString.Print();
Console.ReadKey();
}
}
类型参数约束
由于泛型是占位符,并不确定类型,所以无法进行一些运算符操作.如:+、-等
例
class Simple<T>
{
static public bool LessThan(T i1, T i2) {
return i1< i2; //错误
}
}
where子句
约束使用where字句列出
用法
- 每一个有约束的类型参数有自己的where子句。
- 如果形参有多个约束,它们在where子句中使用逗号分隔。
语法
//TypeParam类型参数 constraint约束列表
where TypeParam : constraint, constraint...
要点
- 它们在类型参数列表的关闭尖括号之后列出。
- 它们不使用逗号或其他符号分隔。
- 它们可以以任何次序列出。
- where是上下文关键字,所以可以在其他上下文中使用。
例
例如,如下泛型类有3个类型参数。T1是未绑定的,对于T2,只有Customer类型或从Customer继承的类型的类才能用作类型实参,而对于T3,只有实现IComparable接口的类才能用于类型实参。
class MyClass<T1, T2, T3>
where T2: Custome //T2的约束
where T3: IComparable //T3的约束
{
}
约束类型和次序
类型
约束类型 | 描述 |
---|---|
类名 | 只有这个类型的类或从它继承的类才能用作类型实参 |
class | 任何引用类型,包括类、数组、委托和接口都可以用作类型实参 |
struct | 任何值类型都可以用作类型实参 |
接口名 | 只有这个接口或实现这个接口的类型才能用作类型实参 |
new() | 任何带有无参公共构造函数的类型都可以用作类型实参。这叫做构造函数约束 |
次序
- 最多只能有一个主约束,如果有则必须放在第一位。
- 可以有任意多的接口名约束。
- 如果存在构造函数约束,则必须放在最后。
实例
class SortedList<S>
where S : IComparable<S>
{
....
}
class LinkedList<M, N>
where M : IComparable<M>
where N : ICloneable
{
....
}
class MyDictionary<KeyType, ValueType>
where KeyType : IEnumerable,
new()
{
....
}
new约束
指定泛型类声明中的类型实参必须有公共的无参数构造函数
规则
- 若要使用
new
约束,则该类型不能为抽象类型 - 当与其他约束一起使用时,
new()
约束必须最后指定:
例
class ItemFactory<T> where T : new()
{
public T GetNewItem()
{
return new T();
}
}
public class ItemFactory2<T>
where T : IComparable, new()
{ }
泛型方法
声明泛型方法
- 泛型方法有两个参数列表。
- 封闭在圆括号内的方法参数列表。
- 封闭在尖括号内的类型参数列表。
- 要声明泛型方法,需要:
- 在方法名称之后和方法参数列表之前放置类型参数列表;
- 在方法参数列表后放置可选的约束子句。
例
//<S, T>类型参数列表 (S p, T t)方法参数列表
public void PrintData<S, T>(S p, T t) where S:Person
{
...
}
调用
MyMethod<short, int>();
扩展方法和泛型类
规则
- 必须声明为static;
- 必须是静态类的成员;
- 第一个参数类型中必须有关键字this,后面是扩展的泛型类的名字。
实例
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;
}
}
static class ExtendHolder
{
public static void Print<T>(this Holder<T> h)
{
T[] vals = h.GetValues();
Console.WriteLine($"vals0:{vals[0]},vals1:{vals[1]},vals2:{vals[2]}");
}
}
class Program
{
static void Main(string[] args)
{
var intHolder = new Holder<int>(3, 5, 7);
var stringHolder = new Holder<string>("a1", "a2", "a3");
intHolder.Print();
stringHolder.Print();
Console.ReadKey();
}
}
结果
vals0:3,vals1:5,vals2:7
vals0:a1,vals1:a2,vals2:a3
泛型结构
规则与泛型类一样
struct PieceOfData<T>
{
private T _data;
public PieceOfData(T value)
{
_data = value;
}
public T Data
{
get { return _data; }
set { _data = value; }
}
}
class Program
{
static void Main(string[] args)
{
var intData = new PieceOfData<int>(10);
var stringData = new PieceOfData<string>("嗨,你好");
Console.WriteLine($"intData:{intData.Data}");
Console.WriteLine($"stringData:{stringData.Data}");
Console.ReadKey();
}
}
结果
intData:10
stringData:嗨,你好
泛型委托
规则
- 要声明泛型委托,在委托名称后、委托参数列表之前的尖括号中放置类型参数列表。
//R返回类型 <T, R> 类型参数 (T value) 委托形参
delegate R MyDelegate<T, R>(T value);
- 两个参数列表:
委托形参列表
和类型参数列表
- 类型参数范围
- 返回值
- 形参列表
- 约束子句
实例1
delegate void MyDelegater<T>(T value); //泛型委托
class Simple
{
static public void PrintString(string s) //方法匹配委托
{
Console.WriteLine(s);
}
static public void PrintUpperString(string s)
{
Console.WriteLine($"s.ToUpper:{s.ToUpper()}");
}
}
class Program
{
static void Main(string[] args)
{
var myDel = new MyDelegater<string>(Simple.PrintString);
myDel += Simple.PrintUpperString;
myDel("你好");
Console.ReadKey();
}
}
结果
你好
s.ToUpper:你好
实例2
//TR返回类型
delegate TR Func<T1, T2, TR>(T1 p1, T2 p2); //泛型委托
class Simple
{
static public string PrintString(int p1, int p2) //方法匹配委托
{
int total = p1 + p2;
return total.ToString();
}
}
class Program
{
static void Main(string[] args)
{
var myDel = new Func<int, int, string>(Simple.PrintString);
Console.WriteLine($"Total:{myDel(15, 13)}");
Console.ReadKey();
}
}
结果
Total:28
泛型接口
interface IMyIfc<T>
{
T RturnIt(T inValue);
}
class Simple<S> : IMyIfc<S>
{
public S RturnIt(S inValue)
{
return inValue;
}
}
class Program
{
static void Main(string[] args)
{
var trivInt = new Simple<int>();
var trivString = new Simple<string>();
Console.WriteLine($"trivInt:{trivInt.RturnIt(5)}");
Console.WriteLine($"trivString: {trivString.RturnIt("你好")}");
Console.ReadKey();
}
}
结果
trivInt:5
trivString: 你好