技术太菜,做不了架构,只能写业务,但是做架构的心永远都在,所以我要开始玩转业务代码。
泛型这个东西,只要讨厌重复代码,追求高质量代码的程序员都肯定有用过。C#自带的泛型使用案例很多,Linq,感觉无处不在的都是泛型阿。
(一)泛型的基本概念
泛型的出现是在.net framework2.0之后,基于泛型,我们可以将类型参数化,以便更大范围类地进行代码复用。同时,它减少了泛型及泛型方法中的转型,确保了类型安全。
1、将类型参数化,代码复用更高;
2、类型安全;
3、高效率;
下面分别对这三点,进行阐述;
对于可重用性,比如要设计一个集合类;
public class MyList { public static int Size = 100; public int[] Items = new int[Size]; public int this[int i] { get { return Items[i]; } set { Items[i] = value; } } }
该类型只支持整形,如果要支持字符串,有一种方法是重新添加一个类。但是这两个类型属性和方法都非常接近,如果有一种方法可以类型作为一个通用的数据类型,那么就可以进行代码复用了,同时类型也只要一个就够了。泛型完成的就是这样的功能。
public class MyList<T> { public static int Size = 100; public T[] Items = new T[Size]; public T this[int i] { get { return Items[i]; } set { Items[i] = value; } } }
继续从刚刚那个问题出发,除了新增加一个类,另外一种方法是样MyList的编码从object的角度去设计(因为C#世界里面,所以类型(值类型和引用类型都是继承自object)。
public class MyList { public static int Size = 100; public object[] Items = new object[Size]; public object this[int i] { get { return Items[i]; } set { Items[i] = value; } } }
使用的时候如下写法:
MyList list = new MyList(); list[0] = 123; list[1] = "123";
这个就会带来类型不安全了,虽然编译能过,但是这里会导致一些隐藏的Bug,很难发现。同时也可以避免装箱拆箱的操作带来的性能损耗。
(二)泛型的深入
2.1泛型本质
泛型的本质就是一个占位符;在C#泛型编译生成的IL代码中,T就是一个占位符的角色。在运行时,即时编译器(JIT)会用实际代码中输入的T类型来代替T,也就是说,在由JIT生成的本地代码中,已经使用了实际的数据类型。我们可以把MyList<int>和MyList<String>看成是两个完全不同的类型,但是这个仅仅是对本地代码而言的。对于实际的C#代码,它拥有的一个类型,那就是泛型类型MyList<T>。
2.2 泛型的约束
如果泛型不给约束,就是太过自由了,任何类型都可以进来。在现在这个社会,没有约束就没有自由,自由和约束是一起存在的,下面我们来看看泛型的约束。
有了约束之后,泛型给我们的不仅仅是个对象,还可以是对象里面的方法和属性。
1、基类约束
这个可能是用得最多的了,用基类约束的好处是1、可以使用基类的一切属性方法;2、可以保证这个一定是基类或者基类的子类;
public class SalaryComputer { public int Compare<T>(T t1, T t2) where T : Salary { if (t1.BaseSalary > t2.BaseSalary) { return 1; } else if (t1.BaseSalary < t2.BaseSalary) { return -1; } return 0; } } public class Salary { public int BaseSalary { get; set; } public int Bonus { get; set; } }
2、引用类型约束
public void Method1<T>(T t) where T : class { }
3、值类型约束
public void Method2 <T>(T t) where T : struct { }
4、无参数构造函数约束
public void Method3<T>(T t) where T : new() { }
5、接口约束
2.3 default关键字
使用default为泛型类型的变量指定初始值;引用类型的变量默认初始值就是NULL,而值类型的变量就不好说了,可能为零,也可能为其他。
(三)协变,逆变
协变和逆变都是用在泛型接口或者委托上面的,协变是out 关键字(只能作为返回值),逆变是in 关键字(只能作为参数);
先记住上面,然后来看例子;
public class Bird { } public class Sparrow:Bird { }
先上面定义了两个类,一个是鸟,一个是继承自鸟的麻雀类。
为了解决我们上面这个问题,一群麻雀应该是要属于一群鸟的情况,C#在.net framework4.0引入了out关键字。
//这样子做,可以的话呢,是因为IEnumerable泛型里面是有out T 协变的,只能用在返回值上面。 IEnumerable<Bird> birds4 = new List<Sparrow>();
逆变呢就是刚刚好反过来。把一群父类复制给一群子类。逆变是用in;
public interface IMyList<in T>{ //逆变,只能用在返回值这边。 void Get(T t); } public class MyList<T> : IMyList<T> { public void Get(T t) { throw new NotImplementedException(); } }
IMyList<Sparrow> sparrows=new MyList<Bird>();
上面这个就是逆变和协变,但是说实话的,平常很少用到。
(四)泛型在业务中的一些应用
4.1 泛型方法,泛型类,泛型接口
知道这些怎么用呢,刚刚开始是去掌握这些概念,去看别人写的代码,去学习观摩,然后去重构自己的烂代码...
4.2 典型的应用场景
4.2.1泛型缓存类
public static class ListStaticCache<T> where T : class{ /// <summary> /// 版本:可以从数据库的表,或者配置文件中读取,每次取值查询是否更新缓存 /// </summary> private static string _version = string.Empty; /// <summary> /// 缓存内容 /// </summary> private static List<T> _genericCache = null; //缓存的时候,还要加上版本号,错误信息等等东西 /// <summary> /// 数据库:该参数表所在的数据库名 /// </summary> private static string _database = string.Empty; /// <summary> /// 错误信息:保存缓存数据失败时的错误信息 /// </summary> public static string ErrMsg { get; set; } = string.Empty; private static void ReadData() { //去数据库读取数据 } /// <summary> /// /// </summary> /// <returns></returns> public static List<T> Getcache(){ //加了个版本号 if (_genericCache == null || ConfigurationManager.AppSettings.Get(typeof(T).Name + ".version") != _version){ } return _genericCache; } }
4.2.2 数据库通用操作类
数据库里面通用的方法,根据不同实体类,通过反射来进行生成对应的SQL语句;
public interface IDbHelper { void Insert<T>(T t); void Update<T>(T t); void Delete<T>(T t); void Query<T>(T t); }
上面的具体代码,我迟一点会公布出来;请大家多多指教。