在C#开发中,必不可少的要用到泛型。泛型是.NET2.0版本就有的,它普遍应用于C#框架中容器的使用中。下面咱们来详细介绍一下。数据库
1、泛型的主要优点编程
1.性能更高。缓存
2.类型更安全。
安全
3.代码更多的重用和扩展性。框架
2、泛型的基本使用ide
泛型的一个主要优势是性能,咱们来看下面的例子:函数
static void Main(string[] args) { //不是泛型的集合类 ArrayList list = new ArrayList(); //添加一个值类型 装箱操做 list.Add(12); //去除第一个元素12 拆箱操做 int num = (int)list[0]; Console.WriteLine(num); Console.WriteLine("执行结束"); Console.ReadKey(); }
// // 摘要: // 将对象添加到 System.Collections.ArrayList 的结尾处。 // // 参数: // value: // 要添加到 System.Collections.ArrayList 末尾的 System.Object。该值能够为 null。 // // 返回结果: // value 已添加的 System.Collections.ArrayList 索引。 // // 异常: // T:System.NotSupportedException: // The System.Collections.ArrayList is read-only.-or- The System.Collections.ArrayList // has a fixed size. public virtual int Add(object value);
相信你们都知道,装箱拆箱是比较损耗性能的,在执行add方法是, 把值类型转换成引用类型(装箱),取出来时在去拆箱,那怎么样才能避免这种状况发生呢?性能
再来看下面代码:测试
//泛型集合类 List<int> list = new List<int>(); list.Add(12); int num = list[0]; Console.WriteLine(num); Console.WriteLine("执行结束"); Console.ReadKey();
这个时候,代码并无发生装箱拆箱操做。spa
// // 摘要: // 将对象添加到 System.Collections.Generic.List`1 的结尾处。 // // 参数: // item: // 要添加到 System.Collections.Generic.List`1 末尾的对象。对于引用类型,该值能够为 null。 public void Add(T item);
代码写到这里时,咱们只是建立了一个List泛型集合,你可能还感受不到泛型优点到底在哪里,你也可能不知道泛型究竟是怎么用的。好,下面咱们写个测试还有本身实际应用的例子。
static void Main(string[] args) { //不是泛型的集合类 ArrayList arylist = new ArrayList(); //添加一个值类型 装箱操做 //泛型集合类 List<int> list = new List<int>(); //计时类 Stopwatch watch = new Stopwatch(); watch.Start();//开始计时 for (int i = 0; i < 10000000; i++) { arylist.Add(i); } watch.Stop(); Console.WriteLine("Arraylist用时:"+watch.ElapsedMilliseconds); Stopwatch watch1 = new Stopwatch(); watch1.Start();//开始计时 for (int i = 0; i < 10000000; i++) { list.Add(i); } watch1.Stop(); Console.WriteLine("list用时:" + watch1.ElapsedMilliseconds); Console.WriteLine("执行结束"); Console.ReadKey(); }
执行结果:
以上的例子中,能够看出在计时的过程当中代码写的重复了, 怎么解决这个问题呢, 泛型要排上用场了。
咱们想一下,相同的操做(都是循环添加元素,计算用时)用同一个方法实现不就ok了,只不过这个方法是泛型的(能够接受ArrayList,也能够接受List),下面咱们来写一下这个方法。
//咱们用T来表明泛型 public static long GetTime<T>(T t) { Stopwatch watch = new Stopwatch(); watch.Start();//开始计时 for (int i = 0; i < 10000000; i++) { t.Add(i); } watch.Stop(); return watch.ElapsedMilliseconds; }
可是问题来了, 这里并无Add方法让咱们使用啊。 咱们的思路是把这个T在调用时能够看成ArrayList类型, 也能够看成List类型,这里就设计到了泛型约束。
3、泛型约束
若是使用泛型时, 想要调用这个泛型类型中的方法, 那么就须要添加约束。泛型约束主要有如下几种:
约束 | 说明 |
where T:struct | 对于结构的约束, T必须是值类型 |
where T:class | T必须是引用类型 |
where T:ITest | T必须实现了ITest接口 |
where T:Test | T必须继承基类Test |
where T:new() | T必须有默认构造函数 |
where T:T2 | T派生自泛型类型T2,也称为裸类型约束 |
咱们接着上个泛型方法来修改,ArrayList和List都实现了接口IList , 这个时候咱们加上这个接口约束;
//咱们用T来表明泛型 public static long GetTime<T>(T t)where T:IList { Stopwatch watch = new Stopwatch(); watch.Start();//开始计时 for (int i = 0; i < 10000000; i++) { t.Add(i); } watch.Stop(); return watch.ElapsedMilliseconds; }
调用结果:
代码写到这里时,相信你已经对泛型有所了解了,可是真要应用到本身之后的逻辑编程中时,必定要善于总结:相同类型的相同方法,或者业务逻辑相同,只有某个判断不一样时,能够用上泛型,不只高效还代码量小。
4、应用场景示范
在咱们的项目开发中,数据库的增删改查确定是少不了的, 在这里咱们用泛型来定义增删改查的泛型类。 以后创建一个用户表(实际应用中对应数据库中表结构),数据库中每个表均可以用泛型类中定义的方法, 不须要每个都写增删改查操做,也是面向对象编程的一种思想:
public class BaseDal<T>where T:class ,new () { //如下是增删查改示范 public void Query(int id) { Console.WriteLine(typeof(T).Name+"查询方法,id="+id); } public void Update(T t) { Console.WriteLine(typeof(T).Name+"更新方法"); } public void Delete(T t) { Console.WriteLine(typeof(T).Name + "删除方法"); } public void Add(T t) { Console.WriteLine(typeof(T).Name + "添加方法"); } }
public class User { public int Id { get; set; } public string Name { get; set; } }
BaseDal<User> dal = new BaseDal<User>(); var user = new User() { Id = 0, Name = "用户1" }; dal.Add(user); dal.Query(0); user.Name = "用户11"; dal.Update(user); dal.Delete(user); Console.ReadKey();
5、泛型的协变和抗变
协变和抗变主要是对参数和返回值的类型进行转换,在.NET4以后能够经过协变和抗变为泛型接口或这泛型委托添加这个扩展。
如今咱们写俩个类Shape(形状)、Rectangle(矩形类),Rectangle派生自Shape,写一个方法public static Rectangle GetRec() ;这个时候你会发现, 方法的泛型类型是抗变的, 就是我返回一个类型为Rectangle可是我能够用Shape来接收, 但泛型在NET4.0以前不支持这个方式, 泛型在NET4.0以后提供了支持泛型接口和泛型委托的协变和抗变。
//形状 public class Shape { public double Width { get; set; } public double Height { get; set; } public override string ToString() { return string.Format("width:{0},height:{1}",Width,Height); } } //矩形 public class Rectangle : Shape { } ///-----------------------------------方法与调用 public static Rectangle GetRec() { return new Rectangle(); } Shape s = GetRec(); Console.ReadKey();
一、泛型接口的协变
泛型接口在类型T前加上out关键字,这个时候泛型接口就是协变的,也就意味着返回类型只能是T。 直接看代码:
//泛型接口的协变 public interface IIndex<out T> { T GetT(int index); int Count { get; } } public class RecCollection : IIndex<Rectangle> { private Rectangle[] data = new Rectangle[2] { new Rectangle() { Width=10,Height=20 }, new Rectangle() {Width=20,Height=30 } }; public int Count { get { return data.Count(); } } public Rectangle GetT(int index) { return data[index]; } }
//调用 Shape s1 = new RecCollection().GetT(1); Console.WriteLine(s1.ToString()); IIndex<Rectangle> rec = new RecCollection(); IIndex<Shape> shapes = rec; for (int i = 0; i < shapes.Count; i++) { Console.WriteLine(shapes.GetT(i)); } Console.ReadKey();
以上代码能够看出, 咱们把泛型接口的泛型定义为矩形(Rectangle), 可是在接受的时候能够用基类(Shape) ,在这里实现了协变。
2.泛型接口的抗变
若是泛型类型用in关键字标注,那么这个泛型接口就是抗变的,这样,接口只能把泛型类型T用做其方法的输入。
//泛型接口的抗变 public interface IDisplay<in T> { void Show(T item); } public class ShapeDisplay : IDisplay<Shape> { public void Show(Shape item) { Console.WriteLine(item); } } //-------------------------如下调用------ Rectangle recs = new Rectangle() { Width=100,Height=200}; IDisplay<Shape> shapeDisplay = new ShapeDisplay(); shapeDisplay.Show(recs); IDisplay<Rectangle> recDisplay = shapeDisplay; recDisplay.Show(recs); Console.ReadKey();
以上代码能够看出泛型也是支持抗变和协变的。
6、总结
泛型是C#语言发展带来的新方法,以上例子只是简单的运用,但愿你们能经过以上例子有所启发,能在项目中更好的使用泛型。以上还有泛型缓存没有说到,你们有兴趣能够找下资料,今天就到这里吧, 没有说到的还有很差的地方, 欢迎你们指正!