• C#泛型


        在C#开发中,必不可少的要用到泛型。泛型是.NET2.0版本就有的,它广泛应用于C#框架中容器的使用中。下面我们来详细介绍一下。

      一、泛型的主要优势

        1.性能更高。

        2.类型更安全。

        3.代码更多的重用和扩展性。

      二、泛型的基本使用

        泛型的一个主要优点是性能,我们来看下面的例子:

       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);
    元数据中ArrayList类的Add方法

        相信大家都知道,装箱拆箱是比较损耗性能的,在执行add方法是, 把值类型转换成引用类型(装箱),取出来时在去拆箱,那怎么样才能避免这种情况发生呢?

    再来看下面代码:

               //泛型集合类
                List<int> list = new List<int>();
                list.Add(12);
                int num = list[0];
                Console.WriteLine(num);
                Console.WriteLine("执行结束");
                Console.ReadKey();    

        这个时候,代码并没有发生装箱拆箱操作。

     //
            // 摘要:
            //     将对象添加到 System.Collections.Generic.List`1 的结尾处。
            //
            // 参数:
            //   item:
            //     要添加到 System.Collections.Generic.List`1 末尾的对象。对于引用类型,该值可以为 null。
            public void Add(T item);
    元数据中List类的Add方法

        代码写到这里时,我们只是创建了一个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();
            }
    测试arraylist和list集合的性能

        执行结果:

         以上的例子中,可以看出在计时的过程中代码写的重复了, 怎么解决这个问题呢, 泛型要排上用场了。

        我们想一下,相同的操作(都是循环添加元素,计算用时)用同一个方法实现不就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类型,这里就设计到了泛型约束。

      三、泛型约束

        如果使用泛型时, 想要调用这个泛型类型中的方法, 那么就需要添加约束。泛型约束主要有以下几种:

        

    约束 说明
    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;
            }    

    调用结果:

         代码写到这里时,相信你已经对泛型有所了解了,但是真要应用到自己以后的逻辑编程中时,一定要善于总结:相同类型的相同方法,或者业务逻辑相同,只有某个判断不同时,可以用上泛型,不仅高效还代码量小。

      四、应用场景示范

        在我们的项目开发中,数据库的增删改查肯定是少不了的, 在这里我们用泛型来定义增删改查的泛型类。 之后建立一个用户表(实际应用中对应数据库中表结构),数据库中每一个表都可以用泛型类中定义的方法, 不需要每一个都写增删改查操作,也是面向对象编程的一种思想:

    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; }
        }
    user 类
               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();
    调用示范

      五、泛型的协变和抗变

        协变和抗变主要是对参数和返回值的类型进行转换,在.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("{0},height:{1}",Width,Height);
            }
        }
    
        //矩形
        public class Rectangle : Shape {
    
        }
    
    ///-----------------------------------方法与调用
    
    public static Rectangle GetRec() {
                return new Rectangle();
            }
     Shape s = GetRec();
                Console.ReadKey();
    普通方法抗变代码说明

        1、泛型接口的协变

          泛型接口在类型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();

    以上代码可以看出泛型也是支持抗变和协变的。

        六、总结

          泛型是C#语言发展带来的新方法,以上例子只是简单的运用,希望大家能通过以上例子有所启发,能在项目中更好的使用泛型。以上还有泛型缓存没有说到,大家有兴趣可以找下资料,今天就到这里吧, 没有说到的还有不好的地方, 欢迎大家指正!

     

  • 相关阅读:
    博客园 如何给上传的图片添加水印?
    今天把这三篇文章看完(DeepFM/NN/BN)
    今天看了这篇文章,还是要提前做好准备
    POJ1988Cube Stacking
    AcWing 1250. 格子游戏
    POJ 1962 Corporative Network [带权并查集]模板
    AcWing 477. 神经网络
    埃拉托色尼筛法和欧拉筛法
    AcWing 1252. 搭配购买
    AcWing 2128. 狡猾的商人
  • 原文地址:https://www.cnblogs.com/hkf100/p/13508126.html
Copyright © 2020-2023  润新知