认识泛型
泛型使类型参数化,从而实现了算法上的代码重用。
同时由于去掉了转换中装箱和拆箱的操作,使用泛型还可以提高程序的运行速度。
我们先看看C#自带的使用了泛型的类:
1 using System.Collections.Generic; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 List<int> list1 = new List<int>(); 10 list1.Add(100); 11 int i = list1[0]; 12 13 List<string> list2 = new List<string>(); 14 list2.Add("Hello"); 15 string s = list2[0]; 16 } 17 } 18 }
通过使用泛型,我们可以重复利用List提供的功能,而不用每个类型对应去写一个List的类。
泛型在类上的实现
下面我们自己使用泛型编写一个简单的类,如下:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Test<int> test1 = new Test<int>(); 10 test1.myValue = 100; 11 Console.WriteLine(test1.myValue); 12 13 Test<string> test2 = new Test<string>(); 14 test2.myValue = "Hello"; 15 Console.WriteLine(test2.myValue); 16 } 17 } 18 19 public class Test<T> 20 { 21 private T _myValue; 22 23 public T myValue 24 { 25 set { _myValue = value; } 26 get { return _myValue; } 27 } 28 } 29 }
Test类中的尖括号里面的T即为泛型,其可以表示任意的类型。
泛型约束
我们上面示例中的T可以使用任意的类型,那么如果我们只希望T是某类型或某类型的子类该怎么办呢?
public class Test<T> where T : IComparable
如果这样写,则表示T必须是实现了IComparable接口的对象。
多个类型的情况
多个类型的写法如下:
public class Test<T, K> where T : IComparable where K : ICloneable
如上所示,一个类型如果要添加约束就需要写一个where进行对应,所以有多个就会有多个where关键字出现。
创建类型的情况
如果需要使用new创建一个类型,则需要在约束里添加new()的字符串,如下:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Test<Data> test1 = new Test<Data>(); 10 Console.WriteLine(test1.myComparable.s); 11 12 Console.Read(); 13 } 14 } 15 16 public class Test<T> where T : IComparable, new() 17 { 18 private T _myComparable; 19 20 public T myComparable 21 { 22 set { _myComparable = value; } 23 get { return _myComparable; } 24 } 25 26 public Test() 27 { 28 _myComparable = new T(); 29 } 30 } 31 32 public class Data : IComparable 33 { 34 public string s = "Hello World!"; 35 36 public int CompareTo(object obj) 37 { 38 return 0; 39 } 40 } 41 }
但是如果是值类型,则不需要这么写,但是要约束T为值类型,如下:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Test<int> test1 = new Test<int>(); 10 Console.WriteLine(test1.myComparable); 11 12 Console.Read(); 13 } 14 } 15 16 public class Test<T> where T : struct 17 { 18 private T _myComparable; 19 20 public T myComparable 21 { 22 set { _myComparable = value; } 23 get { return _myComparable; } 24 } 25 26 public Test() 27 { 28 _myComparable = new T(); 29 } 30 } 31 }
default关键字
当我们需要对泛型T置空时不能直接写“xxx=null;”因为只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。所以我们使用default关键字就可以解决这个问题,如下:
_myComparable = default(T);
泛型继承
子类也有相同的泛型时:
1 public class A<T> 2 { } 3 4 public class B<T> : A<T> 5 { }
当然,你可以使用另外的名称,只要能对应上即可:
1 public class A<T> 2 { } 3 4 public class B<K> : A<K> 5 { }
子类指定好类型:
1 public class A<T> 2 { } 3 4 public class B : A<string> 5 { }
子类添加新类型:
1 public class A<T> 2 { } 3 4 public class B<T, K> : A<T> 5 { }
泛型在方法上的实现
如果要在方法上添加类上没有指定的类型,可以直接在方法上添加泛型:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 Test<int> test = new Test<int>(); 10 Console.WriteLine(test.Func<string>("Hello")); 11 12 Console.Read(); 13 } 14 } 15 16 public class Test<T> 17 { 18 public K Func<K>(K k) 19 { 20 return k; 21 } 22 } 23 }
泛型在委托上的实现
委托上也可以使用泛型,定义方法和在方法上使用泛型一致,如下:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 new Test(); 10 11 Console.Read(); 12 } 13 } 14 15 public class Test 16 { 17 public delegate T add<T>(T a, T b); 18 19 public Test() 20 { 21 add<int> func1 = AddInt; 22 Console.WriteLine(func1(100, 23)); 23 24 add<float> func2 = AddFloat; 25 Console.WriteLine(func2(1.2f, 0.03f)); 26 } 27 28 private int AddInt(int a, int b) 29 { 30 return a + b; 31 } 32 33 private float AddFloat(float a, float b) 34 { 35 return a + b; 36 } 37 } 38 }
泛型接口
泛型接口的使用和泛型类一致,大家可以查看微软自己的文档:https://msdn.microsoft.com/zh-cn/library/kwtft8ak(VS.80).aspx
泛型和静态字段与方法
泛型同样可以使用在静态字段和方法中,由于静态字段和方法在内存中始终只存在一个,所以当我们使用了泛型的时候,编译器会帮我们自动生成对应的方法。
泛型静态的使用和动态一致就跳过不说了。
类型推断
我们在调用泛型方法时可以省略泛型类型的书写,完全交由编译器根据我们的类型来进行判断,这样可以减小代码量同时也更清晰:
1 using System; 2 3 namespace Study 4 { 5 class Program 6 { 7 static void Main(string[] args) 8 { 9 //没有类型推断 10 Console.WriteLine(CompareTo<int>(100, 100)); 11 12 //使用类型推断 13 Console.WriteLine(CompareTo(12.3f, 12.33f)); 14 Console.WriteLine(CompareTo('a', 'a')); 15 16 Console.Read(); 17 } 18 19 private static int CompareTo<T>(T a, T b) where T : IComparable 20 { 21 return a.CompareTo(b); 22 } 23 } 24 }
泛型的可变性
我们先看一个例子:
1 using System; 2 using System.Collections.Generic; 3 4 namespace Study 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 List<B> listB = new List<B>(); 11 12 //下面这句代码会报错 13 //Cannot convert source type 'System.Collections.Generic.List<Study.B>' to 14 //target type 'System.Collections.Generic.List<Study.A>' 15 List<A> listA = listB; 16 17 Console.Read(); 18 } 19 } 20 21 public class A 22 {} 23 24 public class B : A 25 {} 26 }
我们发现虽然B继承于A,但是List<A>和List<B>之间是不能相互转换的。
为了解决这个问题,微软在C#4.0中添加了对泛型的可变性的支持。
协变性
协变性指的是泛型类型参数可以从一个派生类隐式地转换为其基类。
out关键字
协变使用out关键字标识,如下:
public interface MyInterface<out T>
示例:
1 using System; 2 using System.Collections.Generic; 3 4 namespace Study 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 MyInterface<B> myB = new MyClass<B>(); 11 MyInterface<A> myA = myB; 12 13 Console.Read(); 14 } 15 } 16 17 public interface MyInterface<out T> 18 {} 19 20 public class MyClass<T> : MyInterface<T> 21 {} 22 23 public class A 24 {} 25 26 public class B : A 27 {} 28 }
逆变性
逆变性指的是泛型类型参数可以从一个基类隐式的转换为其派生类。
in关键字
public interface MyInterface<in T>
示例:
1 using System; 2 using System.Collections.Generic; 3 4 namespace Study 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 MyInterface<A> myA = new MyClass<A>(); 11 MyInterface<B> myB = myA; 12 13 Console.Read(); 14 } 15 } 16 17 public interface MyInterface<in T> 18 {} 19 20 public class MyClass<T> : MyInterface<T> 21 {} 22 23 public class A 24 {} 25 26 public class B : A 27 {} 28 }
注意事项
- 只有接口和委托支持协变和逆变,类或方法都不支持协变和逆变;
- 协变和逆变只支持引用类型,值类型不支持协变和逆变;
- 必须显示的使用out或in来标记协变和逆变;
- 委托的协变和逆变不要在多播委托中使用;
- 协变和逆变不能同时使用,只能选择一种;