目录
泛型引入
引入场景:当对于一些不同的类型,有相同的处理逻辑,只是传入参数类型或返回的类型不同 的情况,我们想用一种通用处理逻辑来解决,使其能适用于这些不同类型,而不必针对每种类型都去封装多个相似逻辑的方法或类。
public string GetClassType(Teacher teacher)
{
return teacher.GetType().Name;
}
public string GetClassType(Student student)
{
return student.GetType().Name;
}
public string GetClassType(Worker worker)
{
return worker.GetType().Name;
}
public int GetInt(string str)
{
return int.Parse(str);
}
public float GetFloat(string str) {
return float.Parse(str);
}
首先,我们会想到object类型,因为object是所有类的基类; 任何父类出现的地方(Object作为函数签名的形参),都可以用子类代替。
public string GetClassType(object obj)
{
return obj.GetType().Name;
}
但是其会存在这样的问题:
性能问题:如果类型是值类型,有些情况会出现装箱与拆箱 。此处为 int->object(装箱),object->int实际类型(拆箱);
就算不考虑性能问题,有些情况下,程序编译能通过,但是运行期间也可能会抛异常,对于这种以Object作为基类来进行装箱拆箱的操作。
而因此,引入了泛型,可以解决这些问题。
泛型定义
泛型(generic)是C# 2.0推出的新语法,它是专门为处理多段代码在不同的数据类型上执行相同的指令的情况而设计的。利用泛型的这一特性我们可以定义泛型类、泛型方法、泛型接口、泛型委托。
定义:用占位符T 代替实际数据类型,若定义方法,在方法名后加上<T>,如 GetUserName<T>;
若是泛型类,则类名后加上<T>,如 BaseModel<T>
同理,还有泛型接口、泛型委托,如IData<T>、CustHandler<int,string>。
public class CommonClass
{
//泛型方法
public string GetClassType<T>(T t)
{
return t.GetType().Name;
}
}
//泛型类
public class MyList<T>
{
List<T> list=new List<T>();
public void Add(T t)
{
return list.Add(t);
}
}
泛型思想:延迟一切可以延迟的——延迟类或方法中的数据类型的指定,直到调用时,才指定具体类型来代替占位符。
泛型的特性
泛型可以看作是一种增强程序功能的技术,泛型类和泛型方法具有可重用性、类型安全、可提高性能,这是非泛型类和非泛型方法无法实现的。泛型通常与集合以及作用于集合的方法一起使用。
在System.Collections.Generic 命名空间下就包含几个基于泛型的集合类,如List/Dictionary。
关于泛型的特性:
- 使用泛型类型可以最大限度地重用代码、保护类型的安全性以及提高性能;
- 泛型最常见的用途是创建集合类;
- .NET 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类,您可以使用这些类来代替 System.Collections 中的集合类;
- 可以自定义泛型接口、泛型类、泛型方法、泛型事件和泛型委托;
- 可以对泛型类进行约束以限定所应用的类型;
- 在泛型数据类型中所用类型的信息可在运行时通过使用反射来获取。
泛型方法
泛型方法:在声明中包括了类型参数的方法,用一个方法满足不同的类型需求。
泛型方法就是在一个方法名称后面加一个尖括号<>,在尖括号中有占位符,如 GetString<T>.
public string GetClassType<T>(T t)
{
return t.GetType().Name;
}
int intVal=5;
GetClassType<int>(intVal);
其中的T就是类型参数,声明的时候,不具体指明类型,而是用类型参数T代替;到调用时,才用实际类型代替类型参数占位符。
如果方法的参数类型是类型参数,调用时还可以省略类型参数,编译器将推断类型参数。
int intVal=5;
GetClassType(intVal);
类型推理也同样适用于静态方法和实例方法。 编译器可基于传入的方法参数推断类型参数;而无法仅根据约束或返回值推断类型参数。 因此,类型推理不适用于不具有参数或参数类型不是类型参数的方法。
public string GetClassType<T>()
{
return typeof(T).Name;
}
public T ToType<T>(string str)
{
Type type = typeof(T);
return (T)Convert.ChangeType(str, type);
}
这个 GetClassType()方法没有参数,所以不能自动推断,调用时必须显式指定实际类型。
这个 ToType()方法有参数,但传入参数类型并不是类型参数,所以也不能自动推断,调用时必须显式指定实际类型。
泛型类
泛型类用于封装不特定于某个具体数据类型的操作,通常是对于多个类型具有相同操作的统一封装。泛型类最常见用法是用于对象列表、哈希表、堆栈、队列和树等集合。 无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。对于大多数需要集合类的方案,推荐做法是使用 .NET 类库中提供的集合类。
如果我们自己要创建泛型类,通常是是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到所有的具体类型全部泛化。
自定义泛型类时,需要注意:
- 类型参数对应的实际类型。通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但不能过渡。
- 要将何种约束应用到类型参数(泛型约束),以限定类型的范围,有些类型对于当前的类中的方法不适用。
- 泛型行为分解为基类和子类。
public class IncomeList
{
List<IncomeInfo> list=new List<IncomeInfo>();
public void Add(IncomeInfo income)
{
list.Add(income);
}
public void Delete(IncomeInfo income)
{
list.Remove(income);
}
public int GetCount()
{
return list.Count;
}
}
public class DataList<T>
{
List<T> list=new List<T>();
public void Add(T t)
{
list.Add(t);
}
public void Delete(T t)
{
list.Remove(t);
}
public int GetCount()
{
return list.Count;
}
}
DataList<ItemInfo> itemList = new DataList<ItemInfo>();
ItemInfo item1 = new ItemInfo() { ItemId = 1, ItemName = "收入" };
ItemInfo item2 = new ItemInfo() { ItemId = 2, ItemName = "支出" };
itemList.Add(item1);
itemList.Add(item2);
itemList.Delete(item1);
int itemCount = itemList.GetCount();
常见的集合类型
ArrayList
使用大小会根据需要动态增加的数组来实现 IList 接口的集合。是动态数组,可以动态的增加和减少元素,但使用中会有性能损耗。
因为它的Add方法可以添加任意一个类型的项,可以是int/double,float,....,甚至可以是一个引用类型,所以在使用中对于值类型的存取会有装箱与拆箱的操作,会产生性能损耗,所以不建议使用ArrayList这个集合类型,建议使用List类型。
ArrayList arrList = new ArrayList();
arrList.Add(12);
arrList.Add(35.6);
arrList.Add(new IncomeInfo() { IncomeId = 1, IncomeName = "领工资"});
arrList.RemoveAt(1);
int iVal=Convert.ToInt32(arrList[0]);//拆箱
List
一个泛型的集合类型,可通过索引访问的对象的强类型列表,提供用于对列表进行搜索、排序和操作的方法。
它是一个强类型集合,是类型安全的。也是根据需要动态添加或移除其中的项,用于存储不固定数目的一组信息。
LIst列表集合操作:Add/Insert/AddRange/Remove/RemoveAt/Contains/Find/FindIndex/IndexOf/Sort/Reverse/Clear......
List<int> idList = new List<int>(); //存储一组int类型的集合
ids.Add(101);//添加到列表尾部
ids.Add(102);
ids.AddRange(new int[] { 103, 104, 105 });//添加多个到末尾
ids.Remove(103);//移除指定元素
ids.RemoveAt(2);//移除第3个元素
bool bl = ids.Contains(105);//判断某个元素是否存在
int newId = ids.Find(id => id == 103);//查找某个元素
int index = ids.IndexOf(104);//获取指定元素的索引
List<int> newList = ids.Where(id => id > 103).ToList();//按条件筛选
ids.Sort();//排序
ids.Reverse();//翻转
List什么情况用?
暂存编号集合、对象列表、选择的数据等等,是存储列表数据的首选。
Dictionary<TKey,TValue>
表示键和值的集合。它也是一个泛型集合类,强类型集合,类型安全的。
Dictionary<T,V>类型提供从一组键到一组值的映射。 每次对字典的添加都包含一个值和与其关联的键。 使用键检索值的速度非常快。
键必须是唯一的,而值不需要唯一的 。
键和值都是类型参数,其实际类型可以是任何类型(比如:string, int, 自定义类型,等等)。
键值对操作:Add/遍历/根据键取对应的值。
Dictionary<int, string> dicList = new Dictionary<int, string>();
dicList.Add(101, "收入");
dicList.Add(102, "收入");
dicList.Add(103, "支出");
dicList.Add(104, "收入");
dicList.Add(105, "支出");
List<int> itemIds = new List<int>();
//筛选出所有收入类型的编号
//1.遍历每个键值对
foreach (KeyValuePair<int, string> kv in dicList)
{
if (kv.Value == "收入")
{
itemIds.Add(kv.Key);
}
}
//2.遍历每个键
foreach (int key in dicList.Keys)
{
if (dicList[key]=="收入")
{
itemIds.Add(key);
}
}
//判断某个指定的Key是否存在
if(dicList.ContainsKey(104))
{
string val = dicList[104];//取指定键所对应的值
}
Dictionary<T,V> 一般用于存储一系列有对应关系的信息列表。
泛型约束
泛型定义中,有时有 where
子句,用于指定对用作泛型类型、方法、委托或本地函数中类型参数的约束。
泛型约束包含接口、基类、引用类型约束、值类型约束和无参构造函数约束。
泛型约束,简言之,就是限定类型的范围。
约束规则:
1.可以同时使用一个以上的约束
2.值类型约束与引用类型约束不能同时使用
3.引用类型约束不能与基类约束同时使用
4.如果有基类约束,还有其他约束,基类约束必须在最前面
5.如果同时使用多个约束,如果有new(),它必须是最后一个
值类型约束
//值类型约束 where T:struct 限定类型参数的实际类型不能是引用类型
public T ToType<T>(string str) where T:struct
{
return (T)Convert.ChangeType(str, typeof(T));
}
float f1=method.ToType<int>("3.9");//调用时的实际类型是值类型
string s = method.ToType<string>("aaaa"); // 错误 string 是引用类型
引用类型约束
//引用类型约束 where T:class
public class Record<T> where T:class
{
List<T> list = new List<T>();
//添加记录
public void AddRecord(T t)
{
list.Add(t);
}
......
}
Record<IncomeInfo> income = new Record<IncomeInfo>();//正确
Record<double> record1 = new Record<double>();//错误 必须是引用类型才能作为T的实际类型
基类约束
public class BaseModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
}
public class IncomeInfo:BaseModel
{
}
//基类约束 where T:BaseModel 调用时的实际类型必须是BaseModel类型或其子类
public void GetRecordInfo<T>(T t) where T:BaseModel
{
Console.WriteLine("编号:"+t.Id+", 名称:"+t.Name+", 金额:"+t.Amount);
}
IncomeInfo incomeInfo = new IncomeInfo();
incomeInfo.Id = 1;
incomeInfo.Name = "工资收入";
incomeInfo.Amount = 2000;
string str1=method.GetRecordInfo(incomeInfo);//正确
接口约束
//接口
public interface IRecord
{
decimal CalTotalAmount(decimal amount);
}
public class ExpendInfo:BaseModel,IRecord
{
public decimal Price { get; set; }
//实现了接口中的方法
public decimal CalTotalAmount(decimal amount)
{
return GenericMethod.totalAmount - amount;
}
}
public static decimal totalAmount=0.00m;
//接口约束 where T:IRecord 调用时的实际类型必须实现了IRecord接口
public decimal GetTotalAmount<T>(T t) where T:BaseModel,IRecord
{
return t.CalTotalAmount(t.Amount);
}
ExpendInfo exInfo = new ExpendInfo();
exInfo.Id = 2;
exInfo.Name = "借钱给朋友";
exInfo.Amount = 1000;
decimal totalAmount= method.GetTotalAmount(exInfo);
无参构造函数约束
//无参构造函数约束 where T:new()
public T CreateObj<T>() where T:new()
{
T t = new T();
return t;
}
IncomeInfo income = method.CreateObj<IncomeInfo>();