首页 > Python基础教程 >
-
C#教程之泛型(2)
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyGeneric 8 { 9 public class Cat :Animal 10 { 11 public string Name { get; set; } 12 } 13 }
在Main()方法可以这样调用:
1 // 直接声明Animal类 2 Animal animal = new Animal(); 3 // 直接声明Cat类 4 Cat cat = new Cat(); 5 // 声明子类对象指向父类 6 Animal animal2 = new Cat(); 7 // 声明Animal类的集合 8 List<Animal> listAnimal = new List<Animal>(); 9 // 声明Cat类的集合 10 List<Cat> listCat = new List<Cat>();
那么问题来了:下面的一句代码是不是正确的呢?
1 List<Animal> list = new List<Cat>();
可能有人会认为是正确的:因为一只Cat属于Animal,那么一群Cat也应该属于Animal啊。但是实际上这样声明是错误的:因为List<Cat>和List<Animal>之间没有父子关系。
这时就可以用到协变和逆变了。
1 // 协变 2 IEnumerable<Animal> List1 = new List<Animal>(); 3 IEnumerable<Animal> List2 = new List<Cat>();
F12查看定义:
可以看到,在泛型接口的T前面有一个out关键字修饰,而且T只能是返回值类型,不能作为参数类型,这就是协变。使用了协变以后,左边声明的是基类,右边可以声明基类或者基类的子类。
协变除了可以用在接口上面,也可以用在委托上面:
1 Func<Animal> func = new Func<Cat>(() => null);
除了使用.NET框架定义好的以为,我们还可以自定义协变,例如:
1 /// <summary> 2 /// out 协变 只能是返回结果 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public interface ICustomerListOut<out T> 6 { 7 T Get(); 8 } 9 10 public class CustomerListOut<T> : ICustomerListOut<T> 11 { 12 public T Get() 13 { 14 return default(T); 15 } 16 }
使用自定义的协变:
1 // 使用自定义协变 2 ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>(); 3 ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
在来看看逆变。
在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变。请看下面的自定义逆变:
1 /// <summary> 2 /// 逆变 只能是方法参数 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public interface ICustomerListIn<in T> 6 { 7 void Show(T t); 8 } 9 10 public class CustomerListIn<T> : ICustomerListIn<T> 11 { 12 public void Show(T t) 13 { 14 } 15 }
使用自定义逆变:
1 // 使用自定义逆变 2 ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>(); 3 ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
协变和逆变也可以同时使用,看看下面的例子:
1 /// <summary> 2 /// inT 逆变 3 /// outT 协变 4 /// </summary> 5 /// <typeparam name="inT"></typeparam> 6 /// <typeparam name="outT"></typeparam> 7 public interface IMyList<in inT, out outT> 8 { 9 void Show(inT t); 10 outT Get(); 11 outT Do(inT t); 12 } 13 14 public class MyList<T1, T2> : IMyList<T1, T2> 15 { 16 17 public void Show(T1 t) 18 { 19 Console.WriteLine(t.GetType().Name); 20 } 21 22 public T2 Get() 23 { 24 Console.WriteLine(typeof(T2).Name); 25 return default(T2); 26 } 27 28 public T2 Do(T1 t) 29 { 30 Console.WriteLine(t.GetType().Name); 31 Console.WriteLine(typeof(T2).Name); 32 return default(T2); 33 } 34 }
使用:
1 IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>(); 2 IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>();//协变 3 IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>();//逆变 4 IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>();//逆变+协变
七、泛型缓存
在前面我们学习过,类中的静态类型无论实例化多少次,在内存中只会有一个。静态构造函数只会执行一次。在泛型类中,T类型不同,每个不同的T类型,都会产生一个不同的副本,所以会产生不同的静态属性、不同的静态构造函数,请看下面的例子:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyGeneric 8 { 9 public class GenericCache<T> 10 { 11 static GenericCache() 12 { 13 Console.WriteLine("This is GenericCache 静态构造函数"); 14 _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); 15 } 16 17 private static string _TypeTime = ""; 18 19 public static string GetCache() 20 { 21 return _TypeTime; 22 } 23 } 24 }
然后新建一个测试类,用来测试GenericCache类的执行顺序:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace MyGeneric 9 { 10 public class GenericCacheTest 11 { 12 public static void Show() 13 { 14 for (int i = 0; i < 5; i++) 15 { 16 Console.WriteLine(GenericCache<int>.GetCache()); 17 Thread.Sleep(10); 18 Console.WriteLine(GenericCache<long>.GetCache()); 19 Thread.Sleep(10); 20 Console.WriteLine(GenericCache<DateTime>.GetCache()); 21 Thread.Sleep(10); 22 Console.WriteLine(GenericCache<string>.GetCache()); 23 Thread.Sleep(10); 24 Console.WriteLine(GenericCache<GenericCacheTest>.GetCache()); 25 Thread.Sleep(10); 26 } 27 } 28 } 29 }
Main()方法里面调用:
1 GenericCacheTest.Show();
结果:
从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,所以静态构造函数会执行5次。 而且每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。