示例一
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace GenericityTest
{
class Program
{
static void Main(string[] args)
{
Calculator<int, string> cal = new Calculator<int, string>(120, "aaa");
cal.SetCallBack(CallBack);
Console.Read();
}
static void CallBack(int i, string s)
{
Console.WriteLine("i={0}, s={1}", i, s);
}
}
public class Calculator<T, V>
{
public delegate void CallBack(T t, V v);
T t;
V v;
public Calculator(T t, V v)
{
this.t = t;
this.v = v;
}
public void SetCallBack(CallBack callback)
{
callback(t, v);
}
}
}
运行测试
示例二: 利用where限制泛型类型
using System;
namespace TTest
{
class Program
{
static void Main(string[] args)
{
Fun<IClass, IClass>(new ClassA(), new ClassB());
Console.Read();
}
public static void Fun<T, V>(T t, V v)
where T : IClass
where V : IClass
{
Console.WriteLine("Fun");
}
}
class IClass
{
}
class ClassA : IClass
{
}
class ClassB : IClass
{
}
}
示例三: 用new()限定泛型必须要有public构造方法
public static void Fun<T, V>(T t, V v)
where T : IClass, new()
where V : IClass, new()
{
Console.WriteLine("Fun");
}
集合向上转型(协变)
IEnumerable<int> collection1 = new List<int>();
IEnumerable<object> collection2 = collection1;
//由于声明了out关键字,所以可实现集合向上转型(协变)
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
泛型(generic)是CLR和编程语言提供的一种特殊机制,它支持另一种形式的代码重用,即“算法重用”。
CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型。此外,CLR还允许创建泛型接口和泛型委托。方法偶尔也封装有用的算法,所以CLR允许在引用类型、值类型或接口中定义泛型方法。
简化语法
using DateTimeList = System.Collections.Generic.List<System.DateTime>; Boolean sameType = (typeof(List<DateTime>) == typeof(DateTimeList));//true
实现泛型接口
// 实现泛型接口,并指定类型实参
internal sealed class Triangle : IEnumerator<Point> {
private Point[] m_vertices;
// IEnumerator<Point>的Current属性是Point类型
public Point Current { get { ... } }
...
}
// 实现泛型接口,未指定实参
internal sealed class Triangle : IEnumerator<T< {
private T[] m_vertices;
// IEnumerator<T>的Current属性是T类型
public T Current { get { ... } }
...
}
泛型委托
//定义泛型委托
public delegate TReturn CallMe<TReturn, TKey, TValue>(TKey key, TValue value);
//编译器会将CallMe转换成如下所示的类:
public sealed class CallMe<TReturn, TKey, TValue> : MulticastDelegate {
public CallMe(Object object, IntPtr method);
public virtual TReturn Invoke(TKey key, TValue value);
public virtual IAsyncResult BeginInvoke(TKey key, TValue value,
AsyncCallback callback, Object object);
public virtual TReturn EndInvoke(IAsyncResult result);
}
注意 建议尽量使用FCL预定义的泛型Action和Func委托。
委托和接口的逆变和协变泛型类型实参
委托的每个泛型类型参数都可标记为协变量或逆变量。利用这个功能,可将泛型委托类型的变量转换为相同的委托类型(但泛型参数类型不同)。泛型类型参数可以是以下任何一种形式。
● 不变量(invariant)意味着泛型类型参数不能更改。
● 逆变量(contravariant)意味着泛型类型参数可以从一个类更改为它的某个派生类。在C#是用in关键字标记逆变量形式的泛型类型参数。逆变量泛型类型参数只出现在输入位置,比如作为方法的参数。
● 协变量(covariant)意味着泛型类型参数可以从一个类更改为它的某个基类。C#是用out关键字标记协变量形式的泛型类型参数。协变量泛型类型参数只能出现在输出位置,比如作为方法的返回类型。
例如,假定存在以下委托类型定义(顺便说一下,它真的存在):
public delegate TResult Func<in T, out TResult>(T arg);
其中,泛型类型参数T用in关键字标记,这使它成为逆变量;泛型类型参数TResult用out关键字标记,这使它成为协变量。
所以,如果像下面这样声明一个变量:
Func<Object, ArgumentException> fn1 = null;
就可将它转型为另一个泛型类型参数不同的 Func 类型:
Func<String, Exception> fn2 = fn1; // 不需要显式转型
Exception e = fn2("");
上述代码的意思是说:fn1变量引用一个方法,获取一个 Object,返回一个ArgumentfException。而 fn2 变量引用另一个方法,获取一个 String,返回一个 Exception,由于可将一个 String 传给期待 Object 的方法(因为 String 从 Object 派生),而且由于可以获取返回ArgumentException的一个方法的结果,并将这个结果当成一个Exception(因为Exception是ArgumentException的基类),所以上述代码能正常编译,而且编译时能维持类型安全性。
使用要获取泛型参数和返回值的委托时,建议尽量为逆变性和协变性指定in和out关键字。这样做不会有不良反应,并使你的委托能在更多的情形中使用。
和委托相似,具有泛型类型参数的接口也可将类型参数标记为逆变量和协变量。下面的示例接口有一个逆变量泛型类型参数:
public interface IEnumerator<in T> : IEnumerator {
Boolean MoveNext();
T Current { get; }
}
由于T是逆变量,所以以下代码可顺利编译和运行:
// 这个方法接受任意引用类型的一个IEnumerable
Int32 Count(IEnumerable<Object> collection) { ... }
...
// 以下调用向Count传递一个IEnumerable
Int32 c = Count(new [] { "Grant" });
泛型方法
internal sealed class GenericType<T> {
private T m_value;
public GenericType(T value) {m_value = value;}
public TOutput Converter<TOutput>() {
TOutput result = (TOutput) Convert.ChangeType(m_value, typeof(TOutput));
return result;
}
}
public static class Interlocked {
public static T Exchange<T>(ref T location1, T value) where T: class;
public static T CompareExchange<T>(
ref T location1, T value, T comparand) where T: class;
}
可验证性和约束
public static T Min<T>(T o1, T o2) where T : IComparable<T> {
if (o1.CompareTo(o2) < 0) return o1;
return o2;
}
泛型只能基于元数(类型参数个数)对类型或方法进行重载
// 可以定义以下类型
internal sealed class AType {}
internal sealed class AType<T> {}
internal sealed class AType<T1, T2> {}
// 错误:与没有约束的AType<T>冲突
internal sealed class AType<T> {} where T : IComparable<T> {}
// 错误:与AType<T1, T2>冲突
internal sealed class AType<T3, T4> {}
internal sealed class AnotherType {
// 可以定义以下方法,参数个数不同:
private static void M() {}
private static void M<T>() {}
private static void M<T1, T2>() {}
// 错误:与没有约束的M<T>冲突
private static void M<T>() where T : IComparable<T> {}
// 错误:与M冲突
private static void M<T3, T4>()
}
重写虚泛型方法时,重写的方法必须指定相同数量的类型参数,而且这些类型参数会继承在基类方法上指定的约束。事实上,根本不允许为重写方法的类型参数指定任何约束。但类型参数的名称是可以改变的。类似地,实现接口方法时,方法必须指定与接口方法等量的类型参数,这些类型参数将继承由接口方法指定的约束。下例使用虚方法演示了这一规则:
internal class Base {
public virtual void M<T1, T2>()
where T1 : struct
where T2 : class {
}
}
internal sealed class Derived : Base {
//类型参数名称可以改变
//重写方法已经从基类继承了约束,无法重新指定约束
public override void M<T3, T4>()
where T3 : EventArgs //错误
where T4 : class //错误
{}
}
主要约束
类型参数可以指定零个或者一个主要约束。主要约束可以是代表非密封类的一个引用类型。不能指定以下特殊引用类型:System.Object,System.Array,System.Delegate,System.MulticastDelegate,System.ValueType,System.Enum或者System.Void。
有两个特殊的主要约束:class 和 struct。class表示引用类型约束(任何类类型、接口类型、委托类型、数组等),struct表示值类型约束(数字、枚举等)。CLR将任何System.Nullable<T>值类型视为特殊类型,不满足这个struct约束。原因是Nullable<T>类型将它的类型参数约束为struct,而CLR希望禁止像Nullable<Nullable<T>>这样的递归类型。
所有值类型都隐式地有一个无参构造器,当T被约束为struct时,new T()是合法的。
// 公共无参构造器约束 new()
internal sealed class ContructorConstraint<T> where T : new() {
public static T Factory() {
// 允许,因为所有值类型都隐式有一个公共无参构造器
// 而如果指定的是引用类型,约束也要求它提供公共无参构造器
return new T();
}
}
目前,CLR(以及C#编译器)只支持无参构造器约束。Microsoft认为这已经能满足几乎所有情况。
用default关键字设置默认值,例如 T temp = default(T) 如果T是引用类型将返回null,如果T是值类型将返回0。
未做任何约束的泛型类型也可以使用==或!=操作符与null进行比较。
不能将基元类型(Byte, Int16, Int32, Int64, Decimal等)操作符(比如+,-,*和/)应用于泛型类型变量。因为编译器在编译时确定不了类型。