泛型
- 基本形式
using namespace System.Collections.Generic
// 泛型基本形式(以List为例)
List<ClassName> obj = new List<ClassName>();
- 能用泛型就不要用非泛型
非泛型主要有2个问题:
- 由于装箱/拆箱 造成的效率低下
- 不是类型安全的
一个例子(《C# 之泛型详解》)
// 非泛型的处理方式
// 一个只能处理 int 的栈
public class Stack
{
private int[] m_item;
public int Pop(){...}
public void Push(int item){...}
public Stack(int i)
{
this.m_item = new int[i];
}
}
// 为了能够处理不同类,定义为 object
public class Stack
{
private object[] m_item;
public object Pop(){...}
public void Push(object item){...}
public Stack(int i)
{
this.m_item = new[i];
}
}
可以看到,经过改造的 Stack 虽然变得灵活,但
1. 特定类型到 object 的转换(装箱/拆箱)会造成效率低下
2. 由于 object 是所有类的基类,任何类型输入都可以成功编译,但因为类型不支持,可能会造成运行时错误
泛型的处理方式
// 泛型的处理方式
public class Stack<T>
{
private T[] m_item;
public T Pop(){...}
public void Push(T item){...}
public Stack(int i)
{
this.m_item = new T[i];
}
}
// 使用具体的类型实例化该类即可
Stack<int> a = new Stack<int>(100);
a.Push("a string"); // 编译不通过
支持泛型的类/接口
使用命名空间:System.Collections.Generic
名称 | 属性 | 作用 |
---|---|---|
ICollection< T > | 接口 | 所有泛型集合类型定义基本特性 |
IComparer< | 接口 | 对象比较 |
IDictionary< TKey,TValue > | 接口 | 字典 |
IEnumerable< T > | 接口 | 返回 IEnumerator |
IEnumerator< | 接口 | 允许泛型集合以 foreach 形式迭代 |
IList< T > | 接口 | 列表 |
ISet< | 接口 | 集合 |
Dictionary< TKey,TValue > | 类 | 泛型字典 |
List< T > | 类 | 泛型列表 |
LinkedList< | 类 | 泛型双向链表 |
Queue< T > | 类 | 泛型队列 |
SortedDictionary< | 类 | 排序的泛型字典 |
SortedSet< T > | 类 | 排序的不重复的泛型集合 |
Stack< | 类 | 泛型栈 |
初始化语法
// 初始化 int 的泛型 List<>
List<int> myList = new List<int>{0, 2, 1, 3};
SortedSet< T > 类
该类的项是排序的,在插入和移除项后也能自动确保排序正确
对于自定义的类,需要实现接口 IComparer<
// sortedset<T>
// 自定义一个排序的类实现 IComparer 接口的 Compare 方法
class SortPerson : IComparer<Person>
{
public int Compare(Person p1, Person p2)
{
// 根据年龄排序
if(p1.Age > p2.Age)
return 1;
if(p1.Age < p2.Age)
return -1;
else
reutrn 0;
}
}
// 生成的 SortSet<Person> 可以根据 age 自动排序
// 注意 SortSet 的元素是 Person,类 SortPerson 只是实现一个排序规则
SortSet<Person> setOfPeople = new SortSet<Person>(new SortPerson())
{
new Person {...},
new Perosn {...},
...
};
创建自定义泛型
感觉和 C++ 里的模板很像,两者的差别以后再研究
// 泛型方法实现任意两个类型参数的 swap
static void Swap<T>(ref T a, ref T b)
{
T tmp = a;
a = b;
b = tmp;
}
使用 default 设置默认值:因为在实例化前不知道具体类型
// 泛型中 default 设置默认值
public void resetData<T>(ref T data)
{
// default 可以根据 T 的实际类型设置默认值
data = default(T);
}
委托(Delegate)
C#的委托类似于 C/C++ 的函数指针,一般用于事件和回调方法
相较于函数指针,委托具有以下两个优势
- 函数指针只能指向静态函数,而委托类型既可引用静态函数,又可引用非静态成员
- 与函数指针相比,委托类型是面向对象、类型安全的受控对象,无须担心地址越界
委托类型的定义
// 使用关键字 delegate 声明委托类型
public delegate void BuyTicketEventHandle(int num);
- 可以把委托看成是一个命令(参考文章大白话系列之C#委托与事件讲解(一)),以上语句声明了一个输入为 int 输出为 void 的命令
- 编译器会自动生成一个派生自 System.MulticastDelegate 的密封类。因此实际上 BuyTicketEventHandle 是一个类
委托类型的使用
// 委托
// 执行类
public class buybuybuy
{
public static void BuyTicket(int num)
Console.WriteLine("{0} tickets bought");
}
// 命令类
public class order
{
// 声明一个委托
public delegate void BuyTicketEventHandle(int num);
public static void Main(string[] args)
{
// 绑定执行函数到委托类
BuyTicketEventHandle BuyTickets = new BuyTicketEventHandle(buybuybuy.BuyTicket);
BuyTickets(3);
}
}
委托的多路广播(委托链)
可以理解为一个命令链,给一个委托对象添加多个方法(当然输入输出形式是一样的)
// 委托的多路广播
// 使用 += 和 -=
// 实际上调用的是 Delegate.Combine() 和 Delegate.Remove()
// 执行类
public class buybuybuy
{
public static void BuyTicket(int num){
Console.WriteLine("{0} tickets bought", num);
}
public static void SellTicket(int num){
Console.WriteLine("{0} tickets sold", num);
}
}
// 命令类
public class order
{
// 声明一个委托
public delegate void BuyTicketEventHandle(int num);
public static void Main(string[] args)
{
// 绑定执行函数到委托类
BuyTicketEventHandle ListBuyTickets = new BuyTicketEventHandle(buybuybuy.BuyTicket);
// 添加另一个方法
ListBuyTickets += buybuybuy.SellTicket;
// 会依次执行 BuyTicket(3)、SellTicket(3)
ListBuyTickets(3);
// 删掉一个方法
ListBuyTickets -= buybuybuy.BuyTicket;
// 仅执行 SellTicket(4)
ListBuyTickets(4);
Console.ReadKey();
}
}
可以看到,这里有一个问题,由于方法 BuyTicket 和 SellTicket 是带参数的
因此链式执行只能使用同一组参数
所以在设计的时候,尽量将委托设为 void ,作为调用函数的输入参数
参考
【1】C#与.NET 4高级程序设计(第5版)