泛型方法
泛型方法是使用类型参数声明的方法,如下所示:
static void Swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
下面的代码示例演示一种使用int作为类型参数的方法调用方式:
public static void TestSwap()
{
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);
System.Console.WriteLine(a + " " + b);
}
也可以省略类型参数,编译器将推断出该参数。下面对Swap的调用等效于前面的调用:
Swap(ref a, ref b);
相同的类型推断规则也适用于静态方法以及实例方法。编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数。因此,类型推断不使用于没有参数的方法。类型推断在编译时、编译器尝试解析任何重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推断逻辑。在重载解析步骤中,编译器仅包括类型推断取得成功的那些泛型方法。
在泛型类中,非泛型方法可以访问类级别类型参数,如下所示:
class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}
如果定义的泛型方法接受与包含类相同的类型参数,编译器将生成警告CS0693,因为在方法范围内,为内部T提供的参数将隐藏为外部T提供的参数。除了类初始化时提供的类型参数之外,如果需要灵活调用具有类型参数的泛型类方法,请考虑为方法的类型参数提供其他标识符,如下面示例中的GenericList2<T>所示。
class GenericList<T>
{
// CS0693
void SampleMethod<T>() { }
}
class GenericList2<T>
{
//No warning
void SampleMethod<U>() { }
}
使用约束对方法中的类型参数启用更专门的操作。此版本的Swap<T>现在称为SwapIfGreater<T>,它只能与实现IComparable<T>的类型参数一起使用。
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T>
{
T temp;
if (lhs.CompareTo(rhs) > 0)
{
temp = lhs;
lhs = rhs;
rhs = temp;
}
}
泛型方法可以使用许多类型参数进行重载。例如,下列方法可以全部存在于同一个类中:
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }
泛型和数组
在C# 2.0中,下限为零的一维数组自动实现IList<T>。这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法。此技术主要对读取集合中的数据很有用。IList<T>接口不能用于在数组中添加或移除元素;如果试图在此上下文中调用IList<T>方法(如数组的RemoveAt),将引发异常。
下面的代码示例演示带有 IList<T> 输入参数的单个泛型方法如何同时循环访问列表和数组,本例中为整型数组。class Program {
static void Main(){
int[] arr = {0,1,2,3,4};
List<int> list = new List<int>();
for (int x = 5; x < 10; x++){
list.Add(x);
}
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
static void ProcessItems<T>(IList<T> coll){
foreach (T item in coll){
System.Console.Write(item.ToString() + " ");
}
System.Console.WriteLine();
}
}
注意
尽管ProcessItems方法无法添加或移除项,但对于ProcessItems内部的T[],IsReadOnly属性返回false,因为该数组本身未声明ReadOnly特性。
泛型委托
委托可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,就像实例化泛型类或调用泛型方法一样,如下例所示:
public delegate void Del <T>(T item);
public static void Notify(int i) { }
Del <int> m1 = new Del <int>(Notify);
C# 2.0版具有称为方法组转换的新功能,此功能适用于具体委托类型和泛型委托类型,并使您可以使用如下简化的语法写入一行:
Del <int> m2 = Notify;
在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。
class Stack<T>
{
T[] items;
int index;
public delegate void StackDelegate(T[] items);
}
引用委托的代码必须指定包含类的类型变量,如下所示:
private static void DoWork(float[] items) { }
public static void TestStack()
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
}
根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 Object ,或反向强制转换。class Stack < T >
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
protected virtual void OnStackChanged(StackEventArgs a)
{
stackEvent(this, a);
}
}
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
public static void Test()
{
Stack<double> s = new Stack<double>();
SampleClass o = new SampleClass();
s.stackEvent += o.HandleStackChange;
}
泛型代码中的默认关键字
在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型T:
l T是引用类型还是值类型
l 如果T为值类型,则它是数值还是结构
给定参数化类型 T 的一个变量 t ,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。以下来自 GenericList<T> 类的示例显示了如何使用 default 关键字。{
private class Node
{
//...
public Node Next;
public T Data;
}
private Node head;
//...
public T GetNext()
{
T temp = default(T);
Node current = head;
if (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}
C++模板和C#泛型之间的区别
C#泛型和C++模板都是用于提供参数化类型支持的语言功能。然而,这两者之间存在许多差异。在语法层面上,C#泛型是实现参数化类型的更简单方法,不具有C++模板的复杂性。此外,C#并不尝试提供C++模板所提供的所有功能。在实现层面,主要区别在于,C#泛型类型替换是在运行时执行的,从而为实例化的对象保留了泛型类型信息。
以下是C#泛型和C++模板之间的主要差异:
l C#泛型未提供与C++模板相同程度的灵活性。例如,尽管在C#泛型类中可以调用用户定义的运算符,但不能调用算术运算符
l C#不允许非类型模板参数,如template C<int i>{}
l C#不支持显式专用化,即特定类型的模板的自定义实现
l C#不支持部分专用化:类型参数子集的自定义实现
l C#不允许将类型参数用作泛型类型的基类
l C#不允许类型参数具有默认类型
l 在C#中,尽管构造类型可用作泛型,但泛型类型参数自身不能是泛型。C++确实允许模板参数。
l C++允许那些可能并非对模板中的所有类型参数都有效的代码,然后将检查该代码中是否有用作类型参数的特定类型。C#要求相应地编写类中的代码,使之能够使用任何满足约束的类型。例如,可以在C++中编写对类型参数的对象使用算术运算符+和-的函数,这会在使用不支持这些运算符的类型来实例化模板时产生错误。C#不允许这样;唯一允许的语言构造是那些可以约束推导出来的构造。