C# 泛型
C# 泛型
1、引入泛型
当我们有一个需求,需要输出传入参数的类型和值时,可能在.netFramework 1.0时代需要这么写:
public class OutputInfo
{
public void IntOutput(int value)
{
Console.WriteLine("This is {0}\tDataType:{1} \tValue:{2}\t", typeof(OutputInfo).Name, value.GetType().Name, value);
}
public void StringOutput(string value)
{
Console.WriteLine("This is {0}\tDataType:{1} \tValue:{2}\t", typeof(OutputInfo).Name, value.GetType().Name, value);
}
public void DateOutput(DateTime value)
{
Console.WriteLine("This is {0}\tDataType:{1} \tValue:{2}\t", typeof(OutputInfo).Name, value.GetType().Name, value);
}
}
上面这样写不仅不利于扩展,而且还充斥这大量的重复代码。
当然有的人也会写一个通用的函数,来实现上面的功能(原理是根据C#的特性,所有的对象都集成于Object类)。如下:
public void ObjOutput(Object value)
{
Console.WriteLine("This is {0}\tDataType:{1} \tValue:{2}\t", typeof(OutputInfo).Name, value.GetType().Name, value);
}
2、如何声明和使用泛型
在.netFramework 2.0后添加了泛型,然后我们就可以通过以下代码实现上面的功能了:
public void Output<T>(T value)
{
Console.WriteLine("This is {0}\tDataType:{1} \tValue:{2}\t", typeof(OutputInfo).Name, value.GetType().Name, value);
}
关于泛型的调用:
OutputInfo outputInfo = new OutputInfo();
Console.WriteLine("**********使用最原始的方式************");
outputInfo.IntOutput(2);
outputInfo.StringOutput("Oliver");
outputInfo.DateOutput(DateTime.Now);
Console.WriteLine("**********使用传入Object方式************");
outputInfo.ObjOutput(2);
outputInfo.ObjOutput("Oliver");
outputInfo.ObjOutput(DateTime.Now);
Console.WriteLine("**********使用泛型方式************");
outputInfo.Output<int>(2);//<int>可以省略,但是最好写上
outputInfo.Output<string>("Oliver");
outputInfo.Output<DateTime>(DateTime.Now);
3、泛型的好处
- 可以使程序更加灵活,提高代码的重用性;
- 不会对值类型进行拆箱与装箱,提高了程序的性能;(如果使用上面Object 方式实现上面功能时,会出现拆箱、装箱的操作,这样浪费了性能,而使用泛型不会进行拆箱、装箱,和原生的函数性能基本一致)
- 延迟加载
- ············
4、泛型类、泛型方法、泛型接口、泛型委托
泛型类
//最最简单的一个泛型类
public class GenericClass<T, S>//T,S:是什么都可以,实质上就是个占位符。<>实质就是定义泛型,然后后续就可以使用,否则无法使用
{
//一个泛型方法,传入一个泛型,然后在把他通过返回值发回去
public T GetT(T t)
{
return t;
}
//一个泛型方法,泛型类上如果没有声明该泛型R,那么久需要在方法后添加
public void Hi<R>(R r) {
Console.WriteLine(r);
}
}
泛型接口
//定义一个泛型接口
public interface IGenericInterface<T>
{
void Say(T t);//定义一个泛型方法
}
//该类实现了接口IGenericInterface 然后,在实现的过程中直接将泛型设置为int ,所以实现接口方法时,传入的参数的类型也是int类型的
public class MyClass1 : IGenericInterface<int>
{
public void Say(int t)//传入参数为 int类型
{
throw new NotImplementedException();
}
}
//该类也是实现了接口IGenericInterface ,但是在实现的过程中还是将泛型传入类中,所以实现接口方法时,传入的参数也是泛型
public class MyClass2 <T>: IGenericInterface<T>
{
public void Say(T t)//传入的时T 泛型
{
throw new NotImplementedException();
}
}
泛型委托
public delegate void SayHi<T>(T t);//泛型委托
5、泛型约束
约束 | 内容 |
---|---|
T:结构 | 类型参数必须是值类型。可以指定除 Nullable以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。 |
T:类 | 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 |
T:new() | 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 |
T:<基类名> | 类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> | 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 |
T:U | 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。 |
1.接口约束:
public interface IMyInterface {
void hello();
}
public class MyClass3<T>
where T : IMyInterface
{
void Say(T t) {
t.hello();//如泛型约束了必须实现IMyInterface接口,则这句话不会出错,否则报变异错误
}
}
继承约束同接口约束类似
2.基类约束
指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数。这样的约束一经使用,就必须出现在该类型参数的所有其他约束之前。
class MyClass<T, U>
where T : class
where U : struct
{
}
3.构造函数约束
以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。new() 约束出现在 where 子句的最后。
class MyClass<T, U>
where T : class,new()
where U : struct
{
}
5.裸类型约束
用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用。
public class MyClass_A
{
public int ID { get; set; }
}
public class MyClass_B : MyClass_A// MyClass_B 继承于MyClass_A
{
public int Name { get; set; }
}
public class Generic_A<T>//Generic_A 泛型 T
{
public void Say<U>(U u) //Say 泛型 U
where U : T//约束 U必须继承于 T
{
//.....
}
}
调用
Generic_A<MyClass_A> a = new Generic_A<MyClass_A>();
a.Say<MyClass_B>(new MyClass_B()); //调用正确
a.Say<DateTime>(new DateTime());//调用错误:类型“System.DateTime”不能用作泛型类型或方法“Generic_A<MyClass_A>.Say<U>(U)”中的类型参数“U”。没有从“System.DateTime”到“MyGeneric.MyClass_A”的装箱转换。
6、协变与逆变
请看以下代码,我们定义一个 Animal(动物类)和一个 Dog(狗类,继承自Animal)。并在Main方法中进行调用:
class Program
{
static void Main(string[] args)
{
Animal animal = new Dog();//编译执行
List<Animal> animalList = new List<Dog>();//编译错误:无法将类型“System.Collections.Generic.List<MyGeneric.Dog>”隐式转换为“System.Collections.Generic.List<MyGeneric.Animal>”
}
}
/// <summary>
/// 动物类
/// </summary>
public class Animal
{
public string Name { get; set; }
}
/// <summary>
/// 狗,继承自动物
/// </summary>
public class Dog : Animal
{
public void Run()
{
Console.WriteLine("跑····");
}
}
好多人就会诧异了,为何会报编译错误呢?明明Dog类时继承于Animal类的,为何会提示不能隐式转换为呢?
在.netFramework 4.0中修复了这个功能,在使用中会用到 in 和 out 关键字。
协变 out(Foo<父类> = Foo<子类> )
协变主要使用out关键字,在协变时泛型类只能当做参数传入,不能传出
IEnumerable<Animal> animalList = new List<Dog>();//将List修改为IEnumerable 可以正常编译,原因就是使用了协变
分析IEnumerable的源码不难发现,IEnumerable中定义泛型是是通过 out 关键字定义的:
public interface IEnumerable<out T> : IEnumerable//注意代码中的out
逆变 in (Foo<子类> = Foo<父类>)
逆变主要使用in 关键字,在逆变时泛型类只能当做返回值传出,不能传入
public interface IMyListIn<in T> {
void show(T t);
}
public class MyListIn<T> : IMyListIn<T>
{
public void show(T t)
{
throw new NotImplementedException();
}
}
调用
IMyListIn<Dog> dogList = new MyListIn<Animal>();//逆变 :编译不会报错
协变加逆变 代码
/// <summary>
/// IMyList 接口
/// </summary>
/// <typeparam name="inT">协变 inT</typeparam>
/// <typeparam name="outT">逆变 outT</typeparam>
public interface IMyList<in inT, out outT>
{
void Show(inT t);
outT Get();
outT Get(inT t);
}
/// <summary>
/// MyList 类
/// </summary>
/// <typeparam name="T1"></typeparam>
/// <typeparam name="T2"></typeparam>
public class MyList<T1, T2> : IMyList<T1, T2>
{
public T2 Get()
{
throw new NotImplementedException();
}
public T2 Get(T1 t)
{
throw new NotImplementedException();
}
public void Show(T1 t)
{
throw new NotImplementedException();
}
}
调用:
IMyList<Dog, Animal> list1 = new MyList<Dog, Animal>();//正常形态
IMyList<Dog, Animal> list2 = new MyList<Dog, Dog>();//协变
IMyList<Dog, Animal> list3 = new MyList<Animal, Animal>();//逆变
IMyList<Dog, Animal> list4 = new MyList<Animal, Dog>();//逆变+协变
7、泛型缓存
public class GenericCache<T>
{
static GenericCache()
{
Console.WriteLine("This is GenericCache 静态构造函数");
_TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
}
private static string _TypeTime = "";
public static string GetCache()
{
return _TypeTime;
}
}
调用
for (int i = 0; i < 5; i++)
{
Console.WriteLine(GenericCache<int>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<long>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<DateTime>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<string>.GetCache());
Thread.Sleep(10);
Console.WriteLine(GenericCache<GenericCacheTest>.GetCache());
Thread.Sleep(10);
}
调用结果
/*
This is GenericCache 静态构造函数
System.Int32_20180808140949.683
This is GenericCache 静态构造函数
System.Int64_20180808140949.696
This is GenericCache 静态构造函数
System.DateTime_20180808140949.708
This is GenericCache 静态构造函数
System.String_20180808140949.718
This is GenericCache 静态构造函数
MyGeneric.Extend.GenericCacheTest_20180808140949.730
System.Int32_20180808140949.683
System.Int64_20180808140949.696
System.DateTime_20180808140949.708
System.String_20180808140949.718
MyGeneric.Extend.GenericCacheTest_20180808140949.730
System.Int32_20180808140949.683
System.Int64_20180808140949.696
System.DateTime_20180808140949.708
System.String_20180808140949.718
MyGeneric.Extend.GenericCacheTest_20180808140949.730
System.Int32_20180808140949.683
System.Int64_20180808140949.696
System.DateTime_20180808140949.708
System.String_20180808140949.718
MyGeneric.Extend.GenericCacheTest_20180808140949.730
System.Int32_20180808140949.683
System.Int64_20180808140949.696
System.DateTime_20180808140949.708
System.String_20180808140949.718
MyGeneric.Extend.GenericCacheTest_20180808140949.730
*/
上面这个例子,我们会发现,我们定义了一个GenericCache泛型类,在该类中定义了一些static的变量和方法,但是在调用时通过传入不同的T,会使得静态方法执行的返回值是不同的,但是相同的T返回值是一样的。
实质上是每个不同的T,都会生成一份不同的副本。
该特性适合不同类型,需要缓存一份数据的场景,效率高。