泛型的学习
泛型的学习
一、泛型的引入
- 泛型---泛:宽泛的--不确定的; 型:类型---不确定的类型
- 无处不在的
- 调用普通方法的时候,参数类型在声明的时候就确定了,调用按照类型传递参数即可
a. 如果有100个类型---100个方法?--很累
b. 有没有能够做一个方法可以能够满足不同类型的需求呢?
传统方法
public static class CommonMethod
{
public static void ShowInt(int Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
public static void ShowString(string Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
public static void ShowDateTime(DateTime Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
}
- Object类型作为参数 ----可以传递不同的参数
a. 任何子类出现的地址都可以让父类来代替
b. 万物皆对象---任何一个类型都是继承自Object
使用Object类型
public static class CommonMethod
{
public static void ShowObject(object Parameter)
{
Console.WriteLine($"This is {typeof(CommonMethod).Name} parameter={Parameter},type={Parameter.GetType().Name} ");
}
}
- 问题
a. 性能问题 ---装箱拆箱---在c#语法中,按照声明时决定类型的(栈、托管堆)
b. 类型安全问题
我们通过一个例子来体现性能的问题
public class PerformanceTest
{
public static void Show()
{
int ivalue = 1234;
//消耗的时间
long commonSecond = 0;
long objectSecond = 0;
long genericSecond = 0;
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++) {
ShowInt(ivalue);
}
sw.Stop();
commonSecond = sw.ElapsedMilliseconds;
}
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++)
{
ShowObject(ivalue);
}
sw.Stop();
objectSecond = sw.ElapsedMilliseconds;
}
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000_000_000; i++)
{
Show(ivalue);
}
sw.Stop();
genericSecond = sw.ElapsedMilliseconds;
}
Console.WriteLine($"commonSecond: {commonSecond} objectSecond: {objectSecond} genericSecond :{genericSecond} ");
}
private static void ShowInt(int r)
{
}
private static void ShowObject(object o)
{
}
private static void Show<T>(T parameter)
{
}
}
结果为:
- 有没有既性能好,也能够支持多种类型的方法呢?---泛型方法
a. 声明多了一对尖括号 + 占位符T
b. 调用--也需要多一对尖括号,尖括号中指定的类型要和传递的参数的类型一致。
c. 如果可以参数推到出类型---尖括号可以省略。 - 泛型方法---做到了性能高---可以一个方法满足不同类的需求:---又让马儿跑,又让马吃草
二、泛型的声明
1.泛型方法:在方法名称后面多一对尖括号,尖括号中有占位符
2.延迟声明:声明的时候,只是给一个占位符T,T是什么类型?不知道什么类型---调用的时候,指定你是什么,调用的时候,你说什么就是什么;
3.占位符: T ---类型参数 --- 类型变量
4.类型参数当作方法的参数的时候,明确参数类型。
三、泛型的特点+原理 -- 底层如何支持的?
- 在高级语言中,定义的泛型T,在计算机执行的时候一定要是一个具体的类型。
- 在底层如何支持?---在底层看到,生成的结果是LIST
1[T] Dictionary
2[TKey,TValue] - 在底层---生成了
1、
2、3、
4、5、
6 - 编译器必须要能够支持泛型
- CLR运行时环境也需要要支持泛型
- 泛型当然是框架的升级支持的---泛型不是语法糖---有框架的升级支持的;
- 语法糖:是编译器提供的便捷功能。
四、泛型的多种应用
- 泛型方法---可以一个方法满足不同类型的需求
- 泛型接口---可以一个接口满足不同类型的需求 --- 尖括号+占位符
- 泛型类-----可以一个类型满足不同类型的需求
- 泛型委托---可以一个委托满足不同类型的需求
尖括号中可以有多个类型参数。
public class GenericsTest
{
public interface GenericsInterface<T>
{
public T show();
}
public class GenericsClass<T>
{
public void Show(T t)
{
Console.WriteLine(t);
}
}
public delegate T genericsDelegate<T>();
//1.泛型方法
static void Show<T>(T value)
{
Console.WriteLine(value);
}
//2.泛型接口
GenericsInterface<string> genericsString = null;
GenericsInterface<DateTime> genericsDatetime = null;
//3.泛型类
GenericsClass<string> genericsClassString = null;
GenericsClass<DateTime> genericsClassDateTime = null;
//4.泛型委托
genericsDelegate<string> genericsDelegateString=null;
genericsDelegate<DateTime> genericsDelegateDateTime=null;
}
类型参数一定要为T吗?不一定,也可以是其他的名字(不要使用关键字)。
并且尖括号中可以有多个类型参数
public class GenericsClass<T,S,X,GODLWL>
{
public void Show(T t)
{
Console.WriteLine(t);
}
}
继承抽象泛型类的情况
//使用T报错,为什么不行?要继承的时候,必须要确定类型,
public abstract class Children:FatherClass<T>
{
}
//不报错,在子类实例化的时候,父类的类型也会被确定。
public class Children1<S> : FatherClass<S>
{
}
继承类的状况
public class Children2<S>
{
public S show()
{
return default(S);
}
public S show(S s)
{
return s;
}
}
五、类型安全解读
要有真正的自由--就需要要有约束--开车---交通规则--红绿灯
- Object类型
public class Genericconstraints
{
public class People
{
public int Id { get; set; }
public string Name { get; set; }
}
public static void ShowObject(object oValue)
{
//没有意义的打印输出
//Console.WriteLine(oValue);
//传递一个实体的对象:操作字段和方法
//问题:
//1.无法去属性字段--因为oValue是Object;C#是强类型语言,编译时决定参数是什么类型;
//Console.WriteLine($"People.Id={oValue.Id}");
//Console.WriteLine($"People.Name={oValue.Name}");
//2、强制转换
People people = (People)oValue;
Console.WriteLine($"People.Id={people.Id}");
Console.WriteLine($"People.Name={people.Name}");
}
}
当我们传递一个INT类型的变量的时候,运行的时候就会报错。
为了避免这种类型安全的问题,我们就引入了泛型的约束。
六、泛型的约束
- 如何避免泛型的类型安全问题,引入了泛型约束。
//基类约束
//a.就是把类型参数当做People
//b.调用---就可以传递People或者People的子类型
//c.泛型约束:要么不让你进来;如果让你进来,就一定是没问题的
public static void Show<T>(T tValue) where T : People
{
Console.WriteLine($"People.Id={tValue.Id}");
Console.WriteLine($"People.Name={tValue.Name}");
}
public interface ISports
{
public void run();
}
public class Cat:ISports
{
public void run()
{
Console.WriteLine("Cat run");
}
}
//接口约束
// a.把这个T当做ISports
// b.就只能传递ISports这个接口或者实现这个接口的类
// c.就可以增加功能,也可以获取新的功能。
public static void ShowInterface<T>(T tValue) where T : ISports
{
tValue.run();
}
//引用类型约束
// a.就只能传递类型进来
public static void ShowClass<T>(T tValue) where T : class
{
}
//值类型约束
// a.只能传递值类型进来
public static void ShowValue<T>(T tValue) where T : struct
{
}
//无参数构造函数约束
//
public static void ShowNew<T>(T tValue) where T : new()
{
T t= new T();
}
//枚举约束
public static void ShowEnum<T>(T tValue) where T : Enum
{
}
七、泛型缓存---泛型类
泛型缓存可以根据不同的类型生成一个新的类的副本;生成无数个副本;
public class GenericCacheTest
{
public static void Show()
{
//普通缓存
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(DictionaryCache.GetCache<int>()); //GenericCacheInt
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<long>());// GenericCachelong
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<DateTime>());
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<string>());
Thread.Sleep(10);
Console.WriteLine(DictionaryCache.GetCache<GenericCacheTest>());
Thread.Sleep(10);
}
}
//泛型缓存
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(GenericCache<int>.GetCache()); //GenericCacheInt
Thread.Sleep(10);
Console.WriteLine(GenericCache<long>.GetCache());// GenericCachelong
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);
}
}
}
}
/// <summary>
/// 字典缓存:静态属性常驻内存
/// </summary>
public class DictionaryCache
{
private static Dictionary<Type, string> _TypeTimeDictionary = null;
//静态构造函数在整个进程中,执行且只执行一次;
static DictionaryCache()
{
Console.WriteLine("This is DictionaryCache 静态构造函数");
_TypeTimeDictionary = new Dictionary<Type, string>();
}
public static string GetCache<T>()
{
Type type = typeof(T);
if (!_TypeTimeDictionary.ContainsKey(type))
{
_TypeTimeDictionary[type] = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
}
return _TypeTimeDictionary[type];
}
}
/// <summary>
///泛型缓存:
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
static GenericCache()
{
Console.WriteLine("This is GenericCache 静态构造函数");
//_TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
_TypeTime = $"{typeof(T).FullName}_{DateTime.Now.ToString("yyyyMMddHHmmss.fff")}";
}
private static string _TypeTime = "";
public static string GetCache()
{
return _TypeTime;
}
}
七、泛型的协变和逆变
比如我们有一个动物类以及它的子类猫类
public class Animal
{
public int Id { get; set; }
}
public class Cat : Animal {
public string Name { get; set; }
}
我们可以使用这样的代码
//任何子类都可以使用父类来声明
Animal animal1 = new Cat();
//不一定能用子类来声明父类
Cat cat2 = new Animal();
我们还会遇到这样的问题
一只猫是一堆动物
一堆猫却不是一堆动物---从口语上来说,有点不符合人类的逻辑的思维。
List<Animal> animals = new List<Animal>();
//报错
List<Animal> animals2 = new List<Cat>();
为什么?---二类没有父子级关系;当然不能替换;这是C#语法所决定的。
泛型存在不友好,不协调的地方;
这样就引入我们的协变和逆变;
协变逆变 只针对于泛型接口和泛型委托
//协变 就可以让右边使用子类,能让左边用父类
//out:修饰类型参数:就可以让右边用子类,能让左边用父类
IEnumerable<Animal> animals1=new List<Animal>();
IEnumerable<Animal> animals3= new List<Cat>();
协变: Out类型参数只能做返回值,不能做参数
逆变In 只能做参数,不能做返回值
public interface ICustomerListIn<in T>
{
void show(T t);
}
public class CustomerListIn<T>:ICustomerListIn<T>
{
public void show(T t)
{
}
}
逆变的代码例子
//逆变:就可以让右边用父类;左边用子类;
ICustomerListIn<Cat> customerListIn = new CustomerListIn<Animal>();
ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
为什么要有协变和逆变?
如果没有协变和逆变会怎么样?
public interface ICustomerList<T> {
T Get();
void show(T t);
}
public class CustomerList<T>:ICustomerList<T>
{
public T Get()
{
return default(T);
}
public void show(T t)
{
}
}
}
ICustomerList<Animal> customerList=new CustomerList<Animal>();
ICustomerList<Animal> customerList1 = new CustomerList<Cat>();
泛型的协变和逆变更像是一种高级约束,是为规避
- 把子类做参数,却把父类当参数传入;
- 把子类做返回值,却在返回的时候,返回了一个父类。