示例一
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传递一个IEnumerableInt32 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等)操作符(比如+,-,*和/)应用于泛型类型变量。因为编译器在编译时确定不了类型。