1、什么是泛型?
宽泛的,不确定的类型,如:
List<string> list1 = new List<string>();
List<int> list2 = new List<int>();
2、为什么要用泛型?意义是什么?
在操作一致,但是数据类型不一致时,泛型的意义就显现出来了
3、泛型的声明与使用
泛型就是在声明的时候带上一个,这个T就是泛型参数,在调用的时候才会确定参数类型
4、泛型的延迟思想
泛型实际上就是延迟声明的思想,在开发中,延迟一切可以延迟的处理,能晚点做就晚点做
类型参数在声明的时候定义一个占位符,在调用的时候才会确定类型
5.采用object类型也可以做到不同类型的替换
原因:
① 性能损耗:需要“装箱”、“拆箱”操作
基本类型变量都存在于栈内存中,引用类型变量存在于堆内存中,object类型属于引用类型
所谓“装箱”是指,将基本类型变量从栈内存复制到堆内存变成object类型变量
所谓“拆箱”是指,将object类型变量从堆内存复制到栈内存变成基本类型变量
② 存在类型安全问题
6.泛型的应用
在声明的时候用一个类型参数T代替不确定的数据类型,这就是反省
在声明的时候数据类型不确定,只有再调用的时候才会确定具体的数据类型
泛型在编译后,会根据泛型参数个数生成占位符
最终会根据调用生成不同的
泛型在.Net Framework2.0版本就已经出来了,需要以下升级的支持
① 编译器升级,能够支持类型参数用占位符来表达
② 公共语言运行库(CLR)升级才能支持占位符,运行时确定实际类型
7.泛型的应用对象:类、方法、接口、委托
泛型类:一个类满足不同类的需求
泛型方法:一个方法满足不同类型的需求
泛型接口:一个接口满足不同接口的需求
泛型委托:一个委托满足不同委托的需求
/// <summary>
/// 打印int类型数据
/// </summary>
/// <param name="iParam"></param>
public static void ShowInt(int iParam)
{
Console.WriteLine("This is {0},parameter={1},type={2}", typeof(BasicKnowledge).Name, iParam, iParam.GetType());
}
/// <summary>
/// 打印string类型数据
/// </summary>
/// <param name="sParam"></param>
public static void ShowString(string sParam)
{
Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={sParam},type={sParam.GetType()}");
}
/// <summary>
/// 打印datetime类型数据
/// </summary>
/// <param name="dParam"></param>
public static void ShowDateTime(DateTime dParam)
{
Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={dParam},type={dParam.GetType()}");
}
/// <summary>
/// 采用里氏替换原则打印各种类型数据
/// </summary>
/// <param name="oParam"></param>
public static void ShowObject(object oParam)
{
Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={oParam},type={oParam.GetType()}");
}
/// <summary>
/// 应用泛型打印各种类型数据
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="tParam"></param>
public static void ShowGeneric<T>(T tParam)
{
Console.WriteLine($"This is {typeof(BasicKnowledge).Name},parameter={tParam},type={tParam.GetType()}");
}
/// <summary>
/// 对比普通方法、里氏替换、泛型方法的性能
///
/// 经过实验,里氏替换方法性能最低,耗时大概是泛型方法的三倍左右,泛型方法与普通方法相差不多,泛型方法耗时稍微少一点
/// </summary>
/// <param name="iParam"></param>
public static void ShowCompare(int iParam)
{
long commonTime = 0;
long objectTime = 0;
long genericTime = 0;
Stopwatch commonWatch = new Stopwatch();
Stopwatch objectWatch = new Stopwatch();
Stopwatch genericWatch = new Stopwatch();
commonWatch.Start();
for(int i = 0; i < 20_000_000; i++)
{
ComInt(iParam);
}
commonWatch.Stop();
commonTime = commonWatch.ElapsedMilliseconds;
objectWatch.Start();
for(int i = 0; i < 20_000_000; i++)
{
ComObject(iParam);
}
objectWatch.Stop();
objectTime = objectWatch.ElapsedMilliseconds;
genericWatch.Start();
for (int i = 0; i < 20_000_000; i++)
{
ComGeneric<int>(iParam);
}
genericWatch.Stop();
genericTime = genericWatch.ElapsedMilliseconds;
Console.WriteLine($"普通方法耗时:{commonTime},里氏替换方法耗时:{objectTime},泛型方法耗时:{genericTime}");
}
#region
private static void ComInt(int iParam)
{
}
private static void ComObject(object oParam)
{
}
private static void ComGeneric<T>(T tParam)
{
}
#endregion
执行结果:
经运行结果可知,里氏替换方法耗时最多,约为普通方法和泛型方法的三倍左右,泛型方法比普通方法耗时还要少一点
8.泛型缓存
缓存实质上是一个暂存区
① 静态字典缓存
② 泛型缓存
//静态字典缓存
public class DictionaryCache
{
private static Dictionary<Type, string> dic_Cache = null;
static DictionaryCache()
{
dic_Cache = new Dictionary<Type, string>();
Console.WriteLine("This is DictionaryCache 静态代码块");
}
public static string GetCache<T>()
{
Type type = typeof(T);
if (!dic_Cache.ContainsKey(type))
{
dic_Cache.Add(type, $"{type.FullName}_{DateTime.Now:yyyy-MM-dd hh:mm:ss.fff}");
Console.WriteLine($"添加到静态字典缓存中的类型是:{dic_Cache[type]}");
}
return dic_Cache[type];
}
}
/// <summary>
/// 泛型缓存
/// 泛型缓存实质上是一个泛型类,根据调用不同的类型生成不同的副本
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
private static string generic_Cache = "";
/// <summary>
/// 静态代码块执行且只执行一次
/// 但是这个静态代码块却执行了多次
/// 因为只要添加一个新的类型,就相当于生成了一个新的类的副本
/// 就会开辟出一个新的内存来存放这个“新的类”
/// </summary>
static GenericCache()
{
generic_Cache = $"{typeof(T).FullName}_{DateTime.Now:yyyy-MM-dd hh:mm:ss.fff}";
Console.WriteLine("This is GenericCache 静态代码块");
}
public static string GetCache()
{
Console.WriteLine($"添加到泛型缓存中的类型是:{generic_Cache}");
return generic_Cache;
}
}
//缓存实质上就是一个暂存区
//测试
public class CacheTest
{
public static void Show()
{
DictionaryCache.GetCache<int>();
DictionaryCache.GetCache<string>();
DictionaryCache.GetCache<int>();
DictionaryCache.GetCache<DateTime>();
DictionaryCache.GetCache<long>();
Console.WriteLine();
GenericCache<int>.GetCache();
GenericCache<string>.GetCache();
GenericCache<int>.GetCache();
GenericCache<int>.GetCache();
GenericCache<int>.GetCache();
GenericCache<int>.GetCache();
GenericCache<DateTime>.GetCache();
GenericCache<long>.GetCache();
GenericCache<double>.GetCache();
}
}
两种缓存执行结果如图所示:
9.泛型约束
public static void ShowObject(object oPar)
{
People people = (People)oPar;
int Id = people.Id;
string Name = people.Name;
Console.WriteLine($"Id={Id},Name={Name}");
}
上述代码中,参数类型是object,但实际上处理的是People类型的数据,其它类型的数据不可处理。
泛型约束的意义就在于解决这类的类型安全问题:传入的参数进行处理的时候,可能会出现类型不匹配的情况。
① 基类约束:where T:People
约束只能传入People及其子类
② 接口约束:where T:ISport
约束只能传入接口的实现类
③ 无参构造函数约束:where T:new()
约束只能传入具有无参构造函数的类
④ 值类型约束:where T:struct
约束只能传入值类型参数,引用类型参数无法传入
⑤ 引用类型约束:where T:class
约束只能传入引用类型参数,值类型参数无法传入
注意:
泛型支持同时进行多个约束,如where T:People,ISport,但是
① where T:class,People不对,因为class包含了People,约束内容不可以重复
② where T:class,struct不对,因为class和struct不允许同时出现,二者没有交集,同时约束就没有有符合条件的了,而且,class和struct在写约束时都规定必须放在第一个位置
以下代码定义了各个类、接口及其关系
public interface ISports
{
void PingPang();
}
public interface IWork
{
void Work();
}
public class People
{
public int Id { get; set; }
public string Name { get; set; }
public virtual void Hi()
{
Console.WriteLine("你好!");
}
}
public class Chinese : People, ISports, IWork
{
public void PingPang()
{
Console.WriteLine("Chinese 打乒乓球……");
}
public void Work()
{
Console.WriteLine("Chinese 工作……");
}
public override void Hi()
{
//base.Hi();
Console.WriteLine("Chinese 你好……");
}
public void Trandition()
{
Console.WriteLine("仁义礼智信,温良恭俭让");
}
}
public class ShanDong : Chinese
{
public string YellowRiver { get; set; }
public void EatJianBing()
{
Console.WriteLine("吃煎饼……");
}
public override void Hi()
{
//base.Hi();
Console.WriteLine("Shandong 你好!");
}
}
public class Japanese : ISports
{
public int Id { get; set; }
public string Name { get; set; }
public void PingPang()
{
Console.WriteLine("Japanese 打乒乓球……");
}
}
泛型约束的应用
/// <summary>
/// 基类约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowBasic<T>(T t) where T : People
{
Console.WriteLine($"基类约束,Id={t.Id},Name={t.Name}");
t.Hi();
}
/// <summary>
/// 接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowInterface<T>(T t) where T : ISports
{
Console.WriteLine($"接口约束,传入的参数类型是:{typeof(T)},参数是:{t}");
}
/// <summary>
/// 无参构造函数约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowNoParam<T>(T t) where T : new()
{
Console.WriteLine($"无参构造函数约束,传入的参数类型是:{typeof(T)},参数是:{t}");
}
/// <summary>
/// 值类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowStruct<T>(T t) where T : struct
{
Console.WriteLine($"值类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
}
/// <summary>
/// 引用类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowClass<T>(T t) where T : class
{
Console.WriteLine($"引用类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
}
/// <summary>
/// 复合类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public static void ShowComplex<T>(T t) where T : People, IWork
{
Console.WriteLine($"复合类型约束,传入的参数类型是:{typeof(T)},参数是:{t}");
}
应用测试
public static void Show()
{
People people = new People
{
Id = 12,
Name = "people"
};
Chinese chinese = new Chinese
{
Id = 23,
Name = "chinese"
};
ShanDong shandong = new ShanDong
{
Id = 34,
Name = "shandong"
};
Japanese japanese = new Japanese
{
Id = 45,
Name = "japanese"
};
GenericConstraint.ShowBasic<People>(people);
GenericConstraint.ShowBasic<Chinese>(chinese);
GenericConstraint.ShowBasic<ShanDong>(shandong);
Console.WriteLine();
GenericConstraint.ShowInterface<Chinese>(chinese);
GenericConstraint.ShowInterface<Japanese>(japanese);
Console.WriteLine();
GenericConstraint.ShowNoParam<People>(people);
Console.WriteLine();
GenericConstraint.ShowStruct<int>(123);
Console.WriteLine();
GenericConstraint.ShowClass<string>("234");
GenericConstraint.ShowClass<ShanDong>(shandong);
Console.WriteLine();
GenericConstraint.ShowComplex<Chinese>(chinese);
}
测试结果
10.泛型的协变、逆变
协变、逆变存在的意义是什么?看如下代码中的场景问题
public static void Show2()
{
List<Animal> animals = null;
animals = new List<Animal>();
//List<Animal> animals2 = null;
//animals2 = new List<Cat>();
//一群猫一定是一群动物,但对于编译器而言,List<Animal>()和List<Cat>()是两个互不相干的类
//泛型类在我们指定了类型之后,其实是一个新的类
//为什么不能这么写?
//因为两个互不相干的类,没有继承关系,在C#语法中,只能用父类声明子类的实例
//一群猫明明就是一群动物,但编译器不允许我们这么写
//这是泛型存在的不和谐的地方
List<Animal> animals3 = new List<Cat>().Select(c => (Animal)c).ToList();
}
- 协变
定义泛型参数时,out修饰符是协变的标志
out修饰的泛型参数只允许作为返回值参数,不允许作为方法参数使用
比如接口中的方法Show(T t)就将泛型参数作为方法参数了,协变约束的泛型参数不允许出现在方法参数中
协变的出现是为了解决“一群猫是一群动物”的问题
协变—>“和谐地变化”—>“很自然地变化”—>string===>object
协变其实是一种为了规避“风险”的约束
我理解的协变规避风险是在解决“一群猫就是一群动物”这个场景问题的前提下,通过协变来规避其中的风险,我们要带着这个场景去理解协变
协变是一种“规避风险”的约束,那么规避的是什么风险呢?
ICustomerListOut<Animal> customerListOut1 = new CustomerListOut<Animal>();
ICustomerListOut<Animal> customerListOut2 = new CustomerListOut<Cat>();//协变的体现
- 如上代码,接口ICustomerListOut定义了泛型参数T,CustomerListOut作为接口实现类,必然实现了泛型参数T,“风险”就出自这两个泛型参数T,接口ICustomerListOut指定泛型参数T是Animal类,为了解决“一群猫就是一群动物”的问题,实现类指定泛型参数是Aniaml的子类Cat类,如果没有协变的约束,接口ICustomerListOut完全可以定义一个以泛型参数T作为方法参数的方法,如Show(T t),实现类也必然会实现这个方法,那么“风险来了”,由于接口ICustomerListOut指定的泛型参数类型是Animal,在没有协变的约束下,customerListOut2.Show(new Animal())也是可以的,但实现类指定的泛型参数类型却是Animal的子类Cat类,也就说Show(T t)方法中是由Cat类执行操作的,这就会出现强制将Animal类“向下转型”为Cat类的“类型安全风险”。协变的意义就在于规避这个“风险”,给泛型参数一个out修饰符,约束泛型参数T不可做为接口中的方法参数,如Show(T t)。协变实际上就是约束着不输入父类作为方法参数。
- 以上是对协变逆向思维的分析理解,下面我们从正向思考理解协变。接口ICustomerListOut指定了T是Animal,但实现类CustomerListOut指定的T是Cat类型,在协变的约束下,泛型参数T只允许作为方法的返回参数使用,如 T GetOutType(),因此,实现类实际上返回的是Cat类,而Cat类是Animal的子类,根据里氏替换,任何父类出现的地方都可以用子类代替,从而规避了“类型安全风险”。
- 逆变
定义泛型参数时,in修饰符是逆变的标志
in修饰的泛型参数T只允许作为方法参数,不可作为方法的返回值使用
比如 T GetInType()方法就使得泛型参数作为返回值使用了,逆变约束的泛型参数不允许作为返回值
逆变的出现是为了解决“这群动物就是一群猫”的场景问题
逆变—>“逆常地变化”—>“不正常地变化”—>object===>string
逆变其实是一种为了规避“风险”的约束
对于逆变的理解要带着解决“这群动物就是一群猫”这个场景问题去理解。
逆变是一种“规避风险”的约束,那么规避的“风险”是什么呢?
ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
ICustomerListIn<Cat> customerListIn2 = new CustomerListIn<Animal>();//逆变的体现
- 接口ICustomerListIn定义了泛型参数T,CustomerListIn作为实现类实现了泛型参数T,“风险”就出自这两个泛型参数T,如上代码,接口ICustomerListIn指定了泛型参数为Cat类型,而实现类CustomerListIn指定了泛型参数是Cat的父类Animal类型,如果没有逆变的约束,接口ICustomerListIn完全可以定义一个以泛型参数T作为返回值的方法,如 T GetInType(),实现类也必然会实现这个方法。那么“风险”来了,接口这边指定泛型参数时需要返回Cat类执行操作,实现类实际上返回的是Animal类型,这就导致了需要强制将Animal类向下转型为Cat类型,出现了“类型安全风险”,逆变的意义就在于规避这个“向下转型”的类型安全风险问题。此时,给泛型参数一个in修饰符形成逆变约束,使得泛型参数T不允许作为接口中的方法返回值参数使用,如 T GetInType(),逆变实际上就是约束着不允许返回父类。
- 以上是我们逆向思维分析理解逆变,接下来,我们从正向分析理解逆变,接口ICustomerListIn指定了泛型参数T是Cat类,而实现类指定了泛型参数T是Animal类。在逆变的约束下,为了解决“这群动物是一群猫”的问题,ICustomerListIn只允许泛型参数T作为方法参数参与执行,如Show(T t),使得代码ICustomerListIn customerListIn2 = new CustomerListIn();成为合法,但在实际调用方法时,customerListIn2.Show(new Cat());只允许Cat类作为参数执行操作,这就避免了“向下转型”的类型安全“风险”。
测试代码如下:
public class Animal
{
public int Id { get; set; }
}
public class Cat:Animal
{
public string Name { get; set; }
}
interface ICustomerListOut<out T>
{
T GetOutType();
}
interface ICustomerListIn<in T>
{
void Show(T t);
}
public class CustomerListOut<T>:ICustomerListOut<T>
{
public T GetOutType()
{
Console.WriteLine($"协变测试类 CustomerListOut 返回的参数类型是:{typeof(T).FullName}");
return default(T);
}
}
public class CustomerListIn<T>:ICustomerListIn<T>
{
public void Show(T t)
{
Console.WriteLine($"逆变测试类 CustomerListIn 传入的参数类型是:{typeof(T).FullName},参数是:{t}");
}
}
interface IMyList<in T1,out T2>
{
void Show(T1 t);
T2 GetOutType();
T2 ShowComplex(T1 t);
}
public class MyList<T1,T2>:IMyList<T1,T2>
{
public void Show(T1 t)
{
Console.WriteLine($"逆变协变复合测试类 Show 方法参数类型:{typeof(T1).FullName},方法参数是:{t}");
}
public T2 GetOutType()
{
Console.WriteLine($"逆变协变复合测试类 GetOutType 返回参数类型:{typeof(T1).FullName}");
return default(T2);
}
public T2 ShowComplex(T1 t)
{
Console.WriteLine($"逆变协变复合测试类 ShowComplex 方法参数类型:{typeof(T1).FullName},方法参数是:{t},返回参数类型是:{typeof(T2)}");
return default(T2);
}
}
public static void Show3()
{
//协变就可以解决“一群猫就是一群动物”的问题了
IEnumerable<Animal> animalsList1 = new List<Animal>();
IEnumerable<Animal> animalsList2 = new List<Cat>();
ICustomerListOut<Animal> customerListOut1 = new CustomerListOut<Animal>();
ICustomerListOut<Animal> customerListOut2 = new CustomerListOut<Cat>();//协变的体现
customerListOut2.GetOutType();
Console.WriteLine();
ICustomerListIn<Cat> customerListIn1 = new CustomerListIn<Cat>();
ICustomerListIn<Cat> customerListIn2 = new CustomerListIn<Animal>();//逆变的体现
customerListIn2.Show(new Cat());
}
public static void Show4()
{
IMyList<Cat, Animal> mylist = new MyList<Animal, Cat>();
mylist.Show(new Cat());
mylist.GetOutType();
mylist.ShowComplex(new Cat());
}
测试结果如下: