C#泛型学习笔记
分类:
IT文章
•
2022-08-21 00:17:04
本笔记摘抄自:https://www.cnblogs.com/dotnet261010/p/9034594.html,记录一下学习过程以备后续查用。
一、什么是泛型
泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。
二、为什么使用泛型
下面代码演示输出几种类型的相关信息:
class Program
{
/// <summary>
/// 打印帮助类
/// </summary>
public class ShowHelper
{
/// <summary>
/// ShowInt
/// </summary>
/// <param name="intParam"></param>
public static void ShowInt(int intParam)
{
Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={intParam.GetType().Name},Parameter={intParam}");
}
/// <summary>
/// ShowString
/// </summary>
/// <param name="strParam"></param>
public static void ShowString(string strParam)
{
Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={strParam.GetType().Name},Parameter={strParam}");
}
/// <summary>
/// ShowDateTime
/// </summary>
/// <param name="dtParam"></param>
public static void ShowDateTime(DateTime dtParam)
{
Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={dtParam.GetType().Name},Parameter={dtParam}");
}
}
static void Main(string[] args)
{
#region 非泛型打印方式一
ShowHelper.ShowInt(123);
ShowHelper.ShowString("Hello World.");
ShowHelper.ShowDateTime(DateTime.Now);
Console.Read();
#endregion
}
}
View Code
运行结果如下:

上面3个方法很相似,除了参数类型不同外,实现的功能是一样的,可以稍作优化。
下面代码演示使用继承的方式输出几种类型的相关信息:
class Program
{
/// <summary>
/// 打印帮助类
/// </summary>
public class ShowHelper
{
/// <summary>
/// ShowType
/// </summary>
/// <param name="obj"></param>
public static void ShowType(object obj)
{
Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={obj.GetType().Name},Parameter={obj}");
}
}
static void Main(string[] args)
{
#region 非泛型打印方式二
ShowHelper.ShowType(123);
ShowHelper.ShowType("Hello World.");
ShowHelper.ShowType(DateTime.Now);
Console.Read();
#endregion
}
}
View Code

功能实现没有问题,只是object与其它类型的转换,涉及到装箱和拆箱的过程,这个是会损耗程序的性能的。
三、泛型类型参数
在泛型类型或方法的定义中,泛型类型参数可认为是特定类型的占位符。
下面代码演示使用泛型的方式输出几种类型的相关信息:
class Program
{
/// <summary>
/// 打印帮助类
/// </summary>
public class ShowHelper
{
/// <summary>
/// Show
/// </summary>
/// <param name="obj"></param>
public static void Show<T>(T tParam)
{
Console.WriteLine($"Class={typeof(ShowHelper).Name},Type={tParam.GetType().Name},Parameter={tParam}");
}
}
static void Main(string[] args)
{
#region 泛型打印方式
ShowHelper.Show(123);
ShowHelper.Show("Hello World.");
ShowHelper.Show(DateTime.Now);
Console.Read();
#endregion
}
}
View Code
运行结果如下:

1、为什么泛型可以解决上面的问题呢?
泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到调用的时候才给它指定。
2、泛型究竟是如何工作的呢?
程序执行原理:控制台程序最终会编译成一个exe程序。当exe被点击的时候,会经过JIT(即时编译器)的编译,最终生成二进制代码才能被计算机执行。
泛型工作原理:泛型加入到语法以后,VS自带的编译器做了升级,升级之后编译时若遇到泛型,会做特殊的处理:生成占位符。然后经过JIT编译的时候,
会把上面编译生成的占位符替换成具体的数据类型。
下面代码演示泛型占位符:
class Program
{
static void Main(string[] args)
{
#region 泛型占位符
Console.WriteLine(typeof(List<>));
Console.WriteLine(typeof(Dictionary<,>));
Console.Read();
#endregion
}
}
View Code
运行结果如下:

3、泛型性能问题
下面代码演示泛型性能测试:
class Program
{
static void Main(string[] args)
{
#region 泛型性能测试
long commonTime = 0;
long objectTime = 0;
long genericTime = 0;
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10000; i++)
{
ShowHelper.ShowInt(123);
}
watch.Stop();
commonTime = watch.ElapsedMilliseconds;
watch.Reset();
watch.Start();
for (int i = 0; i < 10000; i++)
{
ShowHelper.ShowType(123);
}
watch.Stop();
objectTime = watch.ElapsedMilliseconds;
watch.Reset();
watch.Start();
for (int i = 0; i < 10000; i++)
{
ShowHelper.Show(123);
}
watch.Stop();
genericTime = watch.ElapsedMilliseconds;
Console.Clear();
Console.WriteLine($"Common time={commonTime}ms");
Console.WriteLine($"Object time={objectTime}ms");
Console.WriteLine($"Generic time={genericTime}ms");
Console.Read();
#endregion
}
}
View Code
运行结果如下:

从结果可以看出,泛型的性能是最高的。
四、泛型类
下面代码演示泛型类:
class Program
{
/// <summary>
/// 泛型类
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericClass<T>
{
public T varT;
}
static void Main(string[] args)
{
#region 泛型类
//T是int类型
GenericClass<int> genericInt = new GenericClass<int>
{
varT = 123
};
Console.WriteLine($"The value of T={genericInt.varT}");
//T是string类型
GenericClass<string> genericString = new GenericClass<string>
{
varT = "123"
};
Console.WriteLine($"The value of T={genericString.varT}");
Console.Read();
#endregion
}
}
View Code
运行结果如下:

五、泛型接口
注:泛型在声明的时候可以不指定具体的类型,继承的时候也可以不指定具体类型,但是在使用的时候必须指定具体类型。
下面代码演示泛型接口:
class Program
{
/// <summary>
/// 泛型接口
/// </summary>
public interface IGenericInterface<T>
{
T GetT(T t);
}
/// <summary>
/// 泛型接口实现类
/// </summary>
/// <param name="args"></param>
public class GenericGet<T> : IGenericInterface<T>
{
T varT;
public T GetT(T t)
{
varT = t;
return varT;
}
}
static void Main(string[] args)
{
#region 泛型接口
IGenericInterface<int> genericInterface = new GenericGet<int>();
var result = genericInterface.GetT(123);
Console.WriteLine($"Result={result}");
Console.Read();
#endregion
}
}
View Code
运行结果如下:

六、泛型委托
下面代码演示泛型委托:
class Program
{
/// <summary>
/// 泛型委托
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
public delegate void SayHi<T>(T t);
static void Main(string[] args)
{
#region 泛型委托
SayHi<string> sayHi = SayHello;
sayHi("Hello World");
Console.Read();
#endregion
}
/// <summary>
/// SayHello
/// </summary>
/// <param name="greeting"></param>
public static void SayHello(string greeting)
{
Console.WriteLine($"{greeting}");
}
}
View Code
运行结果如下:

七、泛型约束
泛型约束,实际上就是约束的类型T,使T必须遵循一定的规则。比如T必须继承自某个类或者T必须实现某个接口等等。
怎样给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。
泛型约束总共有五种:
约束 |
s说明 |
T:结构 |
类型参数必须是值类型 |
T:类 |
类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 |
T:new() |
类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。 |
T:<基类名> |
类型参数必须是指定的基类或派生自指定的基类。 |
T:<接口名称> |
类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。 |
7.1基类约束
下面代码演示基类约束:
/// <summary>
/// 运动类接口
/// </summary>
public interface ISports
{
void Pingpong();
}
/// <summary>
/// 人类基类
/// </summary>
public class People
{
public string Name { get; set; }
public virtual void Greeting()
{
Console.WriteLine("Hello World.");
}
}
/// <summary>
/// 中国人
/// </summary>
public class Chinese : People, ISports
{
public void FineTradition()
{
Console.WriteLine("自古以来,中华民族就保持着勤劳的优良传统。");
}
public override void Greeting()
{
Console.WriteLine("吃饭了没?");
}
public void Pingpong()
{
Console.WriteLine("乒乓球是中国的国球。");
}
}
static void Main(string[] args)
{
#region 泛型约束:基类约束
Chinese chinese = new Chinese()
{
Name = "中国人"
};
ShowPeople(chinese);
Console.Read();
#endregion
}
/// <summary>
/// 基类约束
/// </summary>
/// <param name="obj"></param>
public static void ShowPeople<T>(T tParam) where T:People
{
Console.WriteLine($"{((People)tParam).Name}");
}
}
View Code
运行结果如下:

注:基类约束时,基类不能是密封类,即不能是sealed类。sealed类表示该类不能被继承,在这里用作约束就无任何意义了,因为sealed类没有子类。
7.2接口约束
下面代码演示接口约束:
class Program
{
/// <summary>
/// 运动类接口
/// </summary>
public interface ISports
{
void Pingpong();
}
/// <summary>
/// 人类基类
/// </summary>
public class People
{
public string Name { get; set; }
public virtual void Greeting()
{
Console.WriteLine("Hello World.");
}
}
/// <summary>
/// 中国人
/// </summary>
public class Chinese : People, ISports
{
public void FineTradition()
{
Console.WriteLine("自古以来,中华民族就保持着勤劳的优良传统。");
}
public override void Greeting()
{
Console.WriteLine("吃饭了没?");
}
public void Pingpong()
{
Console.WriteLine("乒乓球是中国的国球。");
}
}
static void Main(string[] args)
{
#region 泛型约束:接口约束
Chinese chinese = new Chinese()
{
Name = "中国人"
};
GetSportsByInterface(chinese);
Console.Read();
#endregion
}
/// <summary>
/// 接口约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T GetSportsByInterface<T>(T t) where T : ISports
{
t.Pingpong();
return t;
}
}
View Code
运行结果如下:

7.3引用类型约束 class
引用类型约束保证T一定是引用类型的。
下面代码演示引用类型约束:
class Program
{
/// <summary>
/// 运动类接口
/// </summary>
public interface ISports
{
void Pingpong();
}
/// <summary>
/// 人类基类
/// </summary>
public class People
{
public string Name { get; set; }
public virtual void Greeting()
{
Console.WriteLine("Hello World.");
}
}
/// <summary>
/// 中国人
/// </summary>
public class Chinese : People, ISports
{
public void FineTradition()
{
Console.WriteLine("自古以来,中华民族就保持着勤劳的优良传统。");
}
public override void Greeting()
{
Console.WriteLine("吃饭了没?");
}
public void Pingpong()
{
Console.WriteLine("乒乓球是中国的国球。");
}
}
static void Main(string[] args)
{
#region 泛型约束:引用类型约束
Chinese chinese = new Chinese()
{
Name = "中国人"
};
GetSportsByClass(chinese);
Console.Read();
#endregion
}
/// <summary>
/// 引用类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T GetSportsByClass<T>(T t) where T : class
{
if (t is ISports)
{
(t as ISports).Pingpong();
}
return t;
}
}
View Code
运行结果如下:

7.4值类型约束 struct
值类型约束保证T一定是值类型的。
下面代码演示值类型约束:
class Program
{
/// <summary>
/// 绩效工资
/// </summary>
public struct Achievement
{
public double MeritPay { get; set; }
public string Level { get; set; }
public double ReallyPay()
{
switch (Level)
{
case "A":
MeritPay = MeritPay * 1.0;
break;
case "B":
MeritPay = MeritPay * 0.8;
break;
case "C":
MeritPay = MeritPay * 0.6;
break;
case "D":
MeritPay = 0;
break;
default:
MeritPay = 0;
break;
};
return MeritPay;
}
}
static void Main(string[] args)
{
#region 泛型约束:值类型约束
Achievement achievement = new Achievement
{
MeritPay = 500,
Level = "B"
};
var result = GetReallyPay(achievement).ReallyPay();
Console.WriteLine($"ReallyPay={result}");
Console.Read();
#endregion
}
/// <summary>
/// 值类型约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T GetReallyPay<T>(T t) where T : struct
{
return t;
}
}
View Code
运行结果如下:

7.5无参数构造函数约束 new()
下面代码演示无参数构造函数约束:
class Program
{
/// <summary>
/// 运动类接口
/// </summary>
public interface ISports
{
void Pingpong();
}
/// <summary>
/// 人类基类
/// </summary>
public class People
{
public string Name { get; set; }
public virtual void Greeting()
{
Console.WriteLine("Hello World.");
}
}
/// <summary>
/// 中国人
/// </summary>
public class Chinese : People, ISports
{
public void FineTradition()
{
Console.WriteLine("自古以来,中华民族就保持着勤劳的优良传统。");
}
public override void Greeting()
{
Console.WriteLine("吃饭了没?");
}
public void Pingpong()
{
Console.WriteLine("乒乓球是中国的国球。");
}
}
/// <summary>
/// 广东人
/// </summary>
public class Guangdong : Chinese
{
public Guangdong() { }
public string Dialect { get; set; }
public void Mahjong()
{
Console.WriteLine("这麻将上瘾的时候,一个人也说是三缺一呀。");
}
}
static void Main(string[] args)
{
#region 泛型约束:无参数构造函数约束
Guangdong guangdong = new Guangdong()
{
Name = "广东人"
};
GetMahjong(guangdong);
Console.Read();
#endregion
}
/// <summary>
/// 无参数构造函数约束
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
public static T GetMahjong<T>(T t) where T : People, ISports, new()
{
if (t is Guangdong)
{
(t as Guangdong).Mahjong();
}
return t;
}
}
View Code
运行结果如下:

从上面可以看出,泛型约束可以有多个,但是有多个泛型约束时,new()约束要放到最后。
八:泛型的协变和逆变
协变和逆变是在.NET 4.0的时候出现的,只能放在接口或者委托的泛型参数前面,out协变covariant,用来修饰返回值;in:逆变contravariant,用来修饰
传入参数。
下面代码演示父类与子类的声明方式:
class Program
{
/// <summary>
/// 动物基类
/// </summary>
public class Animal
{
public int Breed { get; set; }
}
/// <summary>
/// 猫类
/// </summary>
public class Cat : Animal
{
public string Name { get; set; }
}
static void Main(string[] args)
{
#region 泛型的协变和逆变
//直接声明Animal类
Animal animal = new Animal();
//直接声明Cat类
Cat cat = new Cat();
//声明子类对象指向父类
Animal animal2 = new Cat();
//声明Animal类的集合
List<Animal> listAnimal = new List<Animal>();
//声明Cat类的集合
List<Cat> listCat = new List<Cat>();
#endregion
}
}
View Code
以上代码是可以正常运行的。假如使用下面的声明方式,是否正确呢?
List<Animal> list = new List<Cat>();
答案是错误的,因为List<Animal>和List<Cat>之间没有父子关系。
解决方法是使用协变的方式:
IEnumerable<Animal> List1 = new List<Animal>();
IEnumerable<Animal> List2 = new List<Cat>();
按F12查看IEnumerable定义:

可以看到,在泛型接口的T前面有一个out关键字修饰,而且T只能是返回值类型,不能作为参数类型,这就是协变。使用协变以后,左边声明的是基类,
右边的声明可以是基类或者基类的子类。
协变除了可以用在接口上面外,还可以用在委托上面:
Func<Animal> func = new Func<Cat>(() => null);
除了使用.NET框架定义好协变以外,我们也可以自定义协变:
//使用自定义协变
ICustomerListOut<Animal> customerList1 = new CustomerListOut<Animal>();
ICustomerListOut<Animal> customerList2 = new CustomerListOut<Cat>();
再来看看逆变:
在泛型接口的T前面有一个In关键字修饰,而且T只能方法参数,不能作为返回值类型,这就是逆变。
/// <summary>
/// 逆变 只能是方法参数
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListIn<in T>
{
void Show(T t);
}
public class CustomerListIn<T> : ICustomerListIn<T>
{
public void Show(T t)
{
}
}
View Code
使用自定义逆变:
//使用自定义逆变
ICustomerListIn<Cat> customerListCat1 = new CustomerListIn<Cat>();
ICustomerListIn<Cat> customerListCat2 = new CustomerListIn<Animal>();
协变和逆变也可以同时使用。
下面代码演示自定义协变与逆变:
class Program
{
/// <summary>
/// 动物基类
/// </summary>
public class Animal
{
public int Breed { get; set; }
}
/// <summary>
/// 猫类
/// </summary>
public class Cat : Animal
{
public string Name { get; set; }
}
#region 泛型的自定义协变和逆变
/// <summary>
/// inT-逆变 outT-协变
/// </summary>
/// <typeparam name="inT"></typeparam>
/// <typeparam name="outT"></typeparam>
public interface IMyList<in inT, out outT>
{
void Show(inT t);
outT Get();
outT Do(inT t);
}
public class MyList<T1, T2> : IMyList<T1, T2>
{
public void Show(T1 t)
{
Console.WriteLine(t.GetType().Name);
}
public T2 Get()
{
Console.WriteLine(typeof(T2).Name);
return default(T2);
}
public T2 Do(T1 t)
{
Console.WriteLine(t.GetType().Name);
Console.WriteLine(typeof(T2).Name);
return default(T2);
}
}
#endregion
static void Main(string[] args)
{
#region 泛型的自定义协变与逆变
IMyList<Cat, Animal> myList1 = new MyList<Cat, Animal>();
IMyList<Cat, Animal> myList2 = new MyList<Cat, Cat>(); //协变
IMyList<Cat, Animal> myList3 = new MyList<Animal, Animal>(); //逆变
IMyList<Cat, Animal> myList4 = new MyList<Animal, Cat>(); //逆变+协变
myList1.Get();
myList2.Get();
myList3.Get();
myList4.Get();
Console.Read();
#endregion
}
}
View Code
运行结果如下:

九、泛型缓存
类中的静态类型无论实例化多少次,在内存中只会有一个,静态构造函数只会执行一次。在泛型类中,T类型不同,每个不同的T类型,都会产生一个不同
的副本,所以会产生不同的静态属性、不同的静态构造函数。
下面代码演示泛型缓存:
class Program
{
/// <summary>
/// 泛型缓存
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericCache<T>
{
private static readonly string TypeTime = "";
static GenericCache()
{
Console.WriteLine("这个是泛型缓存的静态构造函数:");
TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff"));
}
public static string GetCache()
{
return TypeTime;
}
}
/// <summary>
/// 泛型缓存测试类
/// </summary>
public class GenericCacheTest
{
public static void Show()
{
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);
}
}
}
static void Main(string[] args)
{
#region 泛型缓存
GenericCacheTest.Show();
Console.Read();
#endregion
}
}
View Code
运行结果如下:

从上面的截图中可以看出,泛型会为不同的类型都创建一个副本,因此静态构造函数会执行5次,另外每次静态属性的值都是一样的。利用泛型的这一特性,可以实现缓存。
注:只能为不同的类型缓存一次;泛型缓存比字典缓存效率高;泛型缓存不能主动释放。