C#语言中的泛型 II 知识结构:
图1 知识结构 6. 泛型接口 泛型类与泛型接口结合使用是很好的编程习惯,比如用IComparable<T>
而非IComparable
,以避免值类型上的装箱和拆箱操作。若将接口指定为类型参数的约束(接口约束),则要使用实现此接口的类型。在设计泛型接口时,我们需要注意以下几点:
(1)一个接口可以定义多个类型参数,如下所示:
public interface IDictionary<TK, TV> { //... }
(2)可以用多个接口为类型参数指定接口约束,如下所示:
public class Stack <T> where T : IComparable<T>, IEnumerable<T> { //... }
(3)具体类可以实现封闭结构类型的接口,如下所示:
public interface IBaseInterface<T> { //... }public class SampleClass : IBaseInterface<string > { //... }
(4)泛型类可以实现泛型接口但要注意类型参数,如下所示:
public interface IBaseInterface1<T> { //... }public interface IBaseInterface2<T, TU> { //... }public class SampleClass1 <T> : IBaseInterface1<T> { // 泛型类实现开放构造类型的接口 }public class SampleClass2 <T> : IBaseInterface2<T, string > { // 除了与接口共用的类型参数外,必须为所有的其它类型参数指定类型 }
(5)适用于类的继承规则同样适用于接口,如下所示:
public interface IMonth<T> { //... }public interface IMonth<T,U> { //... }public interface IJanuary : IMonth<int > { //... }public interface IFebruary<T> : IMonth<T> { //... }public interface IMarch<T> : IMonth<T, int > { //... }//public interface IApril<T> : IMonth<T, U> //{ // // Error //}
7. 泛型方法 (1)泛型方法的定义
声明了类型参数的方法,称为泛型方法。如下所示:
class Program { private static void Swap<T>(ref T lhs, ref T rhs) { T temp = lhs; lhs = rhs; rhs = temp; } public static void TestSwap () { int a = 1 , b = 2 ; Swap<int >(ref a, ref b); //int作为类型参数,调用方法。 //也可以忽略类型参数<int>,与上面的语句等价,编译器会自动推断它。 Swap(ref a, ref b); Console.WriteLine(a + " " + b); } static void Main (string [] args) { TestSwap();// 1 2 } }
静态方法和实例方法有着同样的类型推断规则。
类型推断对没有参数的方法是无效的,因为编译器无法单独根据约束或返回值来进行推断。
类型推断发生在编译器解析重载方法标志之前,对所有同名的泛型方法应用类型推断逻辑。在决定重载的阶段,编译器只包含哪些类型推断成功的泛型类。
(2)泛型方法的参数类型约束
private void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : IComparable<T> { //只能与实现IComparable<T>的类型参数一起使用。 if (lhs.CompareTo(rhs) > 0 ) { T temp = lhs; lhs = rhs; rhs = temp; } }
(3)泛型方法的重载
泛型方法可以通过多个类型参数来重载。
例如,下面的这些方法可以放在同一个类中:
private void DoWork () { //... }private void DoWork<T>() { //... }private void DoWork<T, TU>() { T a = default (T); TU b = default (TU); //... }
(4)泛型类中的泛型方法
public class SampleClass <T> { private void Swap (ref T lhs, ref T rhs) { // 非泛型方法能访问所在类中的类型参数 } }public class GenericList <T> { private void SampleMethod<T>() { // 警告 CS0693 // 类型参数“T”与外部类型“GenericList<T>”中的类型参数同名 } }public class GenericList2 <T> { private void SampleMethod<TU>() { //No Warning } }
若定义的泛型方法和其所在的类具有相同的类型参数,编译器就会产生警告CS0693,因为在方法范围内为内部T
提供的参数隐藏了为外部T
提供的参数。如果需要使用其它类型参数(而不是实例化类时提供的类型参数)来灵活地调用泛型类方法,要为方法的类型参数提供另一个标识符。
泛型方法实例:
public interface IAccount { string Name { get; } decimal Balance { get; } }public class Account : IAccount { public string Name { get; } public decimal Balance { get; } public Account (string name, decimal balance) { Balance = balance; Name = name; } }public class Algorithm { public static decimal Total<T>(IEnumerable<T> e) where T : IAccount { decimal total = 0 ; foreach (T a in e) { total += a.Balance; } return total; } }class Program { static void Main (string [] args) { List<Account> accounts = new List<Account>(); accounts.Add(new Account("Tom" , 999.99 m)); accounts.Add(new Account("Jonh" , 1241.33 m)); accounts.Add(new Account("Patrick" , 1551.2 m)); accounts.Add(new Account("Candy" , 2647 m)); decimal total = Algorithm.Total(accounts); Console.WriteLine("total:{0:C}" , total); // total:¥6,439.52 } }
8. 泛型委托 (1)泛型委托的定义
public delegate void Del<T>(T item);class Program { public static void Notify (int i) { //... } static void Main (string [] args) { Del<int > m1 = new Del<int >(Notify); // ... } }
使用时与实例化泛型类或调用泛型方法一样,需要指定类型参数。
C# 2.0之后有个新特性为 方法组转换 (Method Group Conversion,从方法组到一个兼容委托类型的隐式转换,“方法组”可以看作一个“方法名”),普通委托和泛型委托都可以使用这一特性进行语法简化,如下所示:
public delegate void Del<T>(T item);class Program { public static void Notify (int i) { //... } static void Main (string [] args) { Del<int > m1 = Notify; // ... } }
(2)在泛型类中定义的委托,可以与类的方法一样使用泛型类的类型参数。
public class Stack <T> { private T[] _items; private int _index; public delegate void StackDelegate (T[] items) ; }class Program { private static void DoWork (float [] items) { //... } static void Main (string [] args) { Stack<float > s = new Stack<float >(); Stack<float >.StackDelegate d = DoWork; // ... } }
(3)泛型委托在定义事件时sender
可以定义为强类型,不用再与Object
类型相互转换。
public delegate void StackEventHandler<T, TU>(T sender, TU eventArgs);public class Stack <T> { public class StackEventArgs : EventArgs { public T X { get; set ; } public T Y { get; set ; } } public event StackEventHandler<Stack<T>, StackEventArgs> StackEvent; public virtual void OnStack (StackEventArgs a) { StackEvent?.Invoke(this , a); } }public class SampleClass { public void HandleStackChange<T>(Stack<T> stack , Stack<T>.StackEventArgs args) { //... Console.WriteLine("Test Delegate<T>:{0},{1}" , args.X, args.Y); } }class Program { static void Main (string [] args) { Stack<string > s = new Stack<string >(); s.StackEvent += new StackEventHandler<Stack<string >, Stack<string >.StackEventArgs> (new SampleClass().HandleStackChange); //s.StackEvent += new SampleClass().HandleStackChange;//与上一行语句等价 Stack<string >.StackEventArgs args1 = new Stack<string >.StackEventArgs(); args1.X = "Hello" ; args1.Y = "World" ; s.OnStack(args1); //Test Delegate<T>:Hello,World } }
9. default关键字 在泛型类和泛型方法中会出现一个问题:如果把缺省值赋给参数化类型,此时无法预先知道以下两点:
对于一个参数化类型T
的变量t
,仅当T
是引用类型时,t = null
语句才是合法的;t = 0
只对数值的有效,而对结构体则不行。
这个问题的解决办法是使用default
关键词,它对引用类型返回null
,对值类型的数值型返回零。而对于结构,它将返回结构每个成员,并根据成员是值类型还是引用类型,返回零或null
。
public class GenericList <T> { private class Node { //... public Node Next; public T Data; } //... private Node head; private Node current; public T GetNext () { T temp = default (T); if (current != null) { temp = current.Data; current = current.Next; } return temp; } }