泛型的基本特点:
-
可重用性
基于面向对象的编程思路,我们提高代码复用的基本方法有继承以及多态。那么泛型模板对于我们代码重用又有多高的提升呢?
假如我们要对订单信息和用户信息进行增删改查的基本管理,那我们可以编写两个接口并实现他们,但写完我们可能会发现,他们的基本核心功能是几乎相同的,我们花了双倍的时间写了两份几乎一样的代码,那如果还有其他模块的信息需要管理呢?我们也就一直这样写下去明显不是正确的做法,这个时候我们应该引入泛型。
如下创建一个基础的泛型基类:
public class Repository<T> : IRepository<T> where T : class { protected DbContext _context; public Repository(DbContext dbContext) { _context = dbContext; } public IEnumerable<T> GetAll() { return _context.Set<T>(); } public virtual async Task<IEnumerable<T>> GetAllAsyn() { return await _context.Set<T>().ToListAsync(); } public virtual T Get(int id) { return _context.Set<T>().Find(id); } public virtual async Task<T> GetAsync(int id) { return await _context.Set<T>().FindAsync(id); } public virtual T Add(T t) { _context.Set<T>().Add(t); _context.SaveChanges(); return t; } public virtual async Task<T> AddAsyn(T t) { _context.Set<T>().Add(t); await _context.SaveChangesAsync(); return t; } public virtual T Find(Expression<Func<T, bool>> match) { return _context.Set<T>().SingleOrDefault(match); } public virtual async Task<T> FindAsync(Expression<Func<T, bool>> match) { return await _context.Set<T>().SingleOrDefaultAsync(match); } public IEnumerable<T> FindAll(Expression<Func<T, bool>> match) { return _context.Set<T>().Where(match).ToList(); } public async Task<IEnumerable<T>> FindAllAsync(Expression<Func<T, bool>> match) { return await _context.Set<T>().Where(match).ToListAsync(); } public virtual void Delete(T entity) { _context.Set<T>().Remove(entity); _context.SaveChanges(); } public virtual async Task<int> DeleteAsyn(T entity) { _context.Set<T>().Remove(entity); return await _context.SaveChangesAsync(); } public virtual T Update(T t, object key) { if (t == null) return null; T exist = _context.Set<T>().Find(key); if (exist != null) { _context.Entry(exist).CurrentValues.SetValues(t); _context.SaveChanges(); } return exist; } public virtual async Task<T> UpdateAsyn(T t, object key) { if (t == null) return null; T exist = await _context.Set<T>().FindAsync(key); if (exist != null) { _context.Entry(exist).CurrentValues.SetValues(t); await _context.SaveChangesAsync(); } return exist; } public int Count() { return _context.Set<T>().Count(); } public async Task<int> CountAsync() { return await _context.Set<T>().CountAsync(); } public virtual void Save() { _context.SaveChanges(); } public async virtual Task<int> SaveAsync() { return await _context.SaveChangesAsync(); } public virtual IEnumerable<T> FindBy(Expression<Func<T, bool>> predicate) { IEnumerable<T> query = _context.Set<T>().Where(predicate); return query; } public virtual async Task<IEnumerable<T>> FindByAsyn(Expression<Func<T, bool>> predicate) { return await _context.Set<T>().Where(predicate).ToListAsync(); } }
这样我们就完成了一个基础的模板,当我们要构造某个模块的功能时,我们只需将实体通过泛型参数传入即可获取所有的基本功能。同时,由于基类中的方法都是虚方法,这里也可以对其进行重写。
2. 类型安全性
泛型将类型安全的负担从你那里转移到编译器。 没有必要编写代码来测试正确的数据类型,因为它会在编译时强制执行。 降低了强制类型转换的必要性和运行时错误的可能性。
如我构造了一个泛型参数为string的列表,当我想插入其他类型的数据时,这个时候是不被允许的。
3. 性能与效率
以下一个例子比较了普通方法,Object参数类型的方法,泛型方法的性能。让三种方法执行相同的操作,比较用时长短。
public class Monitor { public static void Show() { 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 < 100000000; i++) { ShowInt(iValue); } watch.Stop(); commonSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { ShowObject(iValue); } watch.Stop(); objectSecond = watch.ElapsedMilliseconds; } { Stopwatch watch = new Stopwatch(); watch.Start(); for (int i = 0; i < 100000000; i++) { Show<int>(iValue); } watch.Stop(); genericSecond = watch.ElapsedMilliseconds; } Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}" , commonSecond, objectSecond, genericSecond); } } private static void ShowInt(int iParameter) { } private static void ShowObject(object oParameter) { } private static void Show<T>(T tParameter) { } }
由结果可以看出,泛型方法的性能最高,其次是普通方法,object方法由于装箱和拆箱导致性能损耗严重,性能最低。
泛型的形式:
1. 泛型类
public class MyGenericClass<T> { public T t; public void Show(T t) { Console.WriteLine(t + ":"+ typeof(T)); } } class Program { static void Main(string[] args) { MyGenericClass<string> strGenericClass = new MyGenericClass<string>(); MyGenericClass<int> intGenericClass = new MyGenericClass<int>(); strGenericClass.Show("Hello"); intGenericClass.Show(1); Console.ReadKey(); } }
2. 泛型接口
using System.Collections.Generic; namespace MyGeneric { public interface IGenericInterface<T> { //泛型类型的返回值 T GetT(T t); } }
在使用泛型接口或者类时,需要指定具体类型。
namespace MyGeneric { /// <summary> /// 使用泛型的时候必须指定具体类型, /// 这里的具体类型是int /// </summary> public class CommonClass :GenericClass<int> { } }
如果子类也是泛型,那么继承的时候可以不指定具体类型:
namespace MyGeneric { /// <summary> /// 使用泛型的时候必须指定具体类型, /// 这里的具体类型是int /// </summary> public class CommonClass :GenericClass<int> { } /// <summary> /// 子类也是泛型的,继承的时候可以不指定具体类型 /// </summary> /// <typeparam name="T"></typeparam> public class CommonClassChild<T>:GenericClass<T> { } }
泛型的约束:
以下六种为泛型类型的约束:
where T: struct 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。 where T : class 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。 where T:new() 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 where T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。 where T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 where T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。
使用约束的原因:
例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。提高功能内聚度,提升隔离度。
多个参数的约束:
可以对多个参数应用约束,并对一个参数应用多个约束,如下面的示例所示:
class Base { } class Test<T, U> where U : struct where T : Base, new() { }