C# 进阶之–泛型
文章目录
前言
泛型是.NET Framework2.0新增的一个特性,在命名空间System.Collections.Generic,包含了几个新的基于泛型的集合类,官方建议.net 2.0 及更高版本的应用程序使用心得泛型集合类,而不使用非泛型集合类,例如ArrayList。
一、为什么要用泛型
如上所述,在没有使用泛型的情况下,假如我们需要定义一个存在小数的集合类型,那么我们只能使用ArrayList,如果我们忘记了,不小心放入了一个string类型,这个时候编译器不会提示报错。
static void Main(string[] args)
{
ArrayList list = new ArrayList();
list.Add(99.9);
list.Add(88.8);
list.Add("3.1415");
float total = 0;
foreach (var item in list)
{
total += (float)item;
};
Console.WriteLine(total);
}
只有在运行时才能发现错误,除此之外,我们还发现,ArrayList是以object的类型存储数据的,在存储和取出来使用的过程中,装箱和拆箱的操作是无法避免的,这必然会降低程序运行的性能。
而我们使用泛型重新构建下上面的程序如下:
static void Main(string[] args)
{
List<float> list = new List<float>();
list.Add(99.9f);
list.Add(88.8f);
/// list.Add("3.1415"); //编译会报错。
float total = 0;
foreach (var item in list)
{
total +=item;
};
Console.WriteLine(total);
}
这个时候,传入list的数据会在编译时就会进行检查,保证了类型的安全性,同时在使用的时候,不需要进行类型的转换。下面我们就一步步走进泛型。
二、泛型有哪些类型
1.泛型类
面向对象的语言,万物皆对象,我们先从泛型类来进一步了解泛型。
泛型类主要的定义方式如下:
[类型修饰符][static] class className <T>
我们先来定义一个泛型类。
public class GenericDemo1 <T>
{
public T Data { get; set; }
public void ShowInfo (T info)
{
Console.WriteLine(info);
}
public T GetInfo(T data)
{
return default(T);
}
}
其中 T是在实例化类的要传入的参数类型,这里可以是任何类型,
必须int、object、数组,甚至是自己定义的其他类类型,或者
static void Main(string[] args)
{
GenericDemo1<int> gInt = new GenericDemo1<int>(); //基本类型
GenericDemo1<int[]> gIntArray = new GenericDemo1<int[]>(); //数组类型
GenericDemo1<List<int>> gList = new GenericDemo1<List<int>>();//列表类型
GenericDemo1<Dictionary<string, string>> gdictonary = new GenericDemo1<Dictionary<string, string>>();//字典类型
GenericDemo1<Comparer> g = new GenericDemo1<Comparer>();//传入类类型
}
除此之外,如果一个类有几种不同的类型,可以定义多个占位符
public class GenericDemo1<T,U> //这个时候你可以传入相同的类型或者不同的类型
例如
GenericDemo1<int, int> genericDemo1 = new GenericDemo1<int, int>();
GenericDemo1<int, string> genericDemo2 = new GenericDemo1<int, string>();
泛型类继承主要分为两类:
1.如果子类是普通类,则继承泛型类的时候,必须明确泛型类的类型
2.如果子类也是泛型类,则继承泛型类的时候,必须和父类的泛型类型保持一致。
//子类是普通类,必须指明确定类型
public class ChildrenGeneric:GenericDemo1<String>
//子类是泛型类,必须和父类保持一致,也可以加新的泛型类类型
public class ChildrenGeneric<T,V>:GenericDemo1<T>
2.泛型方法
泛型方法和泛型类没有直接关系,
1、泛型方法不必在泛型类中,也可以在普通类中
2、泛型方法可以是静态方法,也可以是非静态方法
public class GenericDemo2
{
//带返回的泛型方法
public T GetInfo<T>(T data)
{
return default(T);
}
//不带返回值的泛型方法
public void Show<T>(T data)
{
Console.WriteLine(data.ToString());
}
//静态泛型方法
public static T ShowInfo<T>(T info)
{
return info;
}
}
泛型类中的T和泛型方法中的T没有直接联系,如下:
public class GenericDemo1 <T>
{
public T Data { get; set; }
public T ShowInfo<T> (T info)
{
return info;
}
public T GetInfo()
{
return Data;
}
}
测试结果如下:
static void Main(string[] args)
{
GenericDemo1<int> gInt = new GenericDemo1<int>(); //基本类型
gInt.Data = 1124610;
Console.WriteLine(gInt.GetInfo());//打印泛型类的T信息---结果为1124610
Console.WriteLine(gInt.ShowInfo<String>("中国最美丽!"));//泛型方法的T信息---结果为"中国最美丽!"
}
3.泛型接口
public interface GenericInterface<T>
{
T Show(T data);
}
泛型接口的继承主要分为两类:
1.如果实现类不是泛型类,必须指定泛型接口的具体类型
2.如果实现类是泛型类,必须和泛型接口保持一致,也可以增加新的占位符。
4.泛型委托
public delegate T Do<T>();
public delegate void Work<T>();
C#在定义了两个常用的泛型委托供我们使用,
1、不带返回值得委托,Action<T>
2、带返回值的委托 Func<T,V>(); //V才是返回值的类型
三、泛型的约束
有时候在使用泛型时,需要将泛型的类型约束在一定的范围内使用。可以对泛型类或泛型接口,方法能够接收的类型参数的种类加以限制。在编译阶段,如果使用不符合要求的类作为类型参数,则会产生编译错误。进一步来保证类型的安全。
泛型的约束主要分为:
- 基类约束
- 值类型、引用类型约束
- 接口约束
- new()构造函数约束
- 组合约束
定义如下类:
public interface Sing
{
void SingSong();
}
public class Animal
{
public string Name { get; set; }
}
public class Fish:Animal
{
public string Taste { get; set; }
public void Swim()
{
}
}
//金鱼
public class GoldFish : Fish
{
}
//鲤鱼
public class Carp : Fish
{
}
public class Dolphin : Fish, Sing
{
public void SingSong()
{
Console.WriteLine("I can sing song");
}
}
定义如下一个泛型方法:
//基类约束,只能是继承了Fish的类型
public static void Cook<T> ( T food ) where T:Fish
{
Console.WriteLine("this fish taste is:"+food.Taste);
}
//接口约束 只能是继承了Sing接口的类型
public static void ListenSong<T> ( T fish) where T:Sing
{
}
//组合约束 只能是继承了Sing接口的类型和Fish类型的,接口必须在后面。
public static void ListenSong<T> ( T fish) where T:Fish,Sing
{
}
//值类型约束
public static void ChangeNum<T> (T num) where T:struct
{
}
//引用类型约束
public static void GetType<T> (T type) where T:class
{
}
//new()无参构造函数约束
public static void SetInfo<T> (T type) where T:new()
{
}
注意: 多个约束类型的时候, new()必须在最后面。
四、泛型的逆变和协变
1.协变形势
先看下面的代码
static void Main(string[] args)
{
List<Fish> plist = new List<GoldFish>();//编译错误
plist = new List<Fish>();//编译错误
plist = new List<GoldFish>();//编译错误
}
在上面的代码中,plist = new List</Fish/>()、plist = new List</GoldFish/>()两句产生编译错误。虽然Fish是GoldFish的父类,但List</Fish/>类型却不是List</GoldFish/>类型的父类,所以上面的赋值语句报类型转换失败错误。
如果想用子类替换父类怎么办呢?如下,采用IEnumerable 。
static void Main(string[] args)
{
IEnumerable<Fish> fishes = new List<Fish>();
IEnumerable<Fish> goldfishes = new List<GoldFish>();
}
为什么采用IEnumerable就可以呢,查看源码得知如下:
public interface IEnumerable< out T> : IEnumerable
采用了 out 修饰 泛型T后就可以了,这是就是协变。
协变总结起来就是:
1. out 修饰,为逆变逆变是子类转为父类,
2. 逆变只能为返回值。
2.逆变类型
public interface IMyListIn<in T>
{
void Show(T t);
}
public class MyList<T> : IMyListIn<T>
{
public void Show(T t)
{
Console.WriteLine(t.ToString());
}
}
static void Main(string[] args)
{
IMyListIn<GoldFish> goldfishes = new MyList<Fish>();
}
用父类做参数的地方可以用子类替换,这就是逆变。
协变总结起来就是:
1. in修饰,父类向子类的转化
2. 只能做参数输入
五、性能与本质
1.性能对比
下面分别定义三个函数进行 原始类型、obj、和泛型进行对比
private static void ShowInt(int iParameter)
{
//do nothing
}
private static void ShowObject(object oParameter)
{
//do nothing
}
private static void Show<T>(T tParameter)
{
//do nothing
}
在主程序里进行相关的测试:
public static void Main(string[] args)
{
Console.WriteLine("****************Monitor******************");
{
int iValue = 12345;
long commonSecond = 0;
long objectSecond = 0;
long genericSecond = 0;
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000_000; i++)
{
ShowInt(iValue);
}
watch.Stop();
commonSecond = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000_000; i++)
{
ShowObject(iValue);
}
watch.Stop();
objectSecond = watch.ElapsedMilliseconds;
}
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 100_000_000; i++)
{
Show<int>(iValue);
}
watch.Stop();
genericSecond = watch.ElapsedMilliseconds;
}
Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}"
, commonSecond, objectSecond, genericSecond);
}
}
测试结果如下:
2.本质探索
使用 ILSpy查看 可以发现 泛型类在IL中的形势是
.class public auto ansi MyGeneric.Extend.GenericCache`1
extends [mscorlib]System.Object
的形式,说明在编译的时候 泛型的T并没有确定具体的类型,只是在调用的时候才能确定类型。说明泛型并不是简单的语法糖,而是框架进行了升级。