博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第一课《.net之--泛型》
阅读量:6900 次
发布时间:2019-06-27

本文共 13380 字,大约阅读时间需要 44 分钟。

 

  今天我来学习泛型,泛型是编程入门学习的基础类型,从.net诞生2.0开始就出现了泛型,今天我们开始学习泛型的语法和使用。

  什么是泛型?

  泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性。泛型为.NET框架引入了类型参数(type parameters)的概念。类型参数使得设计类和方法时,不必确定一个或多个具体参数,其的具体参数可延迟到客户代码中声明、实现。这意味着使用泛型的类型参数T,写一个类MyList<T>,客户代码可以这样调用:MyList<int>, MyList<string>或 MyList<MyClass>。这避免了运行时类型转换或装箱操作的代价和风险。

  上面是官方腔调,我说人话:泛型就是为了满足不同类型,相同代码的重用!

 

  为什么要有泛型?

  下面我们举例子来讲解为什么会要泛型,以下我列举了三个例子来讲解:

   我们列举了ShowInt,ShowString,ShowDatatime三个方法,如果我们在程序中每封装一个方法有不同参数,就要像下面这样写一个函数的话,代码会变得很累赘,在调用的时候必须传递吻合的参数,所以不得不写出了3个方法,那有没有好的方法来解决这样的问题呢?答案是当然有,微软是很聪明的,倾听小满哥慢慢来讲。

///         /// 打印个int值///         ///         public static void ShowInt(int iParameter)        {            Console.WriteLine("This is {0},parameter={1},type={2}",                typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter);        }        ///         /// 打印个string值///         ///         public static void ShowString(string sParameter)        {            Console.WriteLine("This is {0},parameter={1},type={2}",                typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter);        }        ///         /// 打印个DateTime值///         ///         public static void ShowDateTime(DateTime dtParameter)        {            Console.WriteLine("This is {0},parameter={1},type={2}",                typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter);        }            定义一些测试变量

      int iValue = 123;

      string sValue = "456";
      DateTime dtValue = DateTime.Now;
      object oValue = "789";

      普通方式调用演示

      ShowInt(iValue);
      ShowInt(sValue);//这样不行类型必须吻合
      ShowString(sValue);
      ShowDateTime(dtValue);

  在.net 1.0的时候微软出现了object这个概念,下面有一个方法ShowObject,你们就会发现不管参数是int srtring datetime 我们都可以调用ShowObject来操作实现,那为什么会这样呢?

  1:Object类型是一切类型的父类。

  2:任何父类出现的地方,都可以用子类来代替。

出现这个基本满足了开发人员的一些需求,在.net1.0和1.1的时候,这个时候还没有泛型就用object代替。

///         /// 打印个object值        /// 1 object类型是一切类型的父类        /// 2 任何父类出现的地方,都可以用子类来代替        /// .Net Framework 1.0 1.1        ///         ///         public static void ShowObject(object oParameter)        {            Console.WriteLine("This is {0},parameter={1},type={2}",                typeof(CommonMethod), oParameter.GetType().Name, oParameter);        }
     定义一些测试变量

      int iValue = 123;

      string sValue = "456";
      DateTime dtValue = DateTime.Now;
      object oValue = "789";

     object方式调用演示

      ShowObject(oValue);

      ShowObject(iValue);
      ShowObject(sValue);
      ShowObject(dtValue);

 

 

  接着吹牛比,劳动人民的智慧还是很屌的,经过之前的经历在.net2.0的时候,微软让主角登场了"泛型",当然同时出现的还有“线程池”这个我们先不讲,回到正轨什么是泛型?你们在开发的时候有没有用过List<T>这个集合?这个就是泛型,深化下直接举个栗子吧,下面我写一个例子Show<T>(T tParameter)看下泛型的写法:

  有个毛用?下面这个方法也可以向上面ShowObject一样,你们会发现不管参数是int srtring datetime 我们也可以调用Show<T>(T tParameter)来操作实现,替换了ShowObject这个方法的实现,具备了满足了不同参数类型的方法实现,更适用性,泛型方法声明的时候,没有指定类型,而是调用的时候指定,具有延迟声明和延迟思想,这个思想对我们在开发框架的时候灰常有用,不得不说老外这点还是很几把厉害(还是我们被洗脑了?也许吧,我相信等中文编程语言出来估计会更屌,中华文化博大精深嘛),现在小伙伴们是不是大概了解泛型的基础作用了?

///         /// 泛型方法声明的时候,没有指定类型,而是调用的时候指定        /// 延迟声明:把参数类型的声明推迟到调用        /// 延迟思想:推迟一切可以推迟的        ///         /// 2.0泛型不是语法糖,而是由框架升级提供的功能        /// 泛型方法性能上和普通方法差不多的///         /// 
/// public static void Show
(T tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); }
     调用演示

      Show<object>(oValue);

      Show<int>(iValue);
      Show(iValue);//可以去掉,自动推算
      Show<string>(iValue);//必须匹配
      Show<string>(sValue);
      Show<DateTime>(dtValue);

    那问题来了,既然都可以实现为什么要用这个呢?我们做事凡事都要带着疑问去看待,有些事别人说好,但真的好不好我们要自己亲自试试才知道,我们最关注的的效率问题,下面是测试代码:

     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
(iValue); } watch.Stop(); genericSecond = watch.ElapsedMilliseconds; } Console.WriteLine("commonSecond={0},objectSecond={1},genericSecond={2}" , commonSecond, objectSecond, genericSecond); } }

  结果如下:commonSecond=508,objectSecond=916,genericSecond=452   你会发现普通方法508ms,object方法916ms,泛型是452ms,其中object最慢,为什么最慢呢?因为object会经过一个装箱拆箱的过程,所以性能上会损失一些,但是在我看来这样上亿次这点损耗,算不了什么,但是可以证明泛型和普通类型速度是差不多的,这一点可以认可泛型还是性能挺好的,这个可以推荐使用泛型的理由之一。

  但泛型仅仅表现在这个层面吗?远远不止,我们用泛型远远不是为了提升刚刚那点性能,为什么要用泛型?答案来了,我们要用泛型就是为了满足不同类型,相同代码的重用,下面我继续举栗子:

   泛型的一些用法,泛型只有四种用途,泛型类,泛型接口,泛型委托,泛型方法,如下:

public class GenericClass
{ public T Property { get; set; } }

   public interface IGenericInterface<T>

     {

     }

   public delegate void DoNothing<T>();

    调用演示

    List<int> intList = new List<int>();//原生态List类

    List<string> stringList = new List<string>();

    GenericClass<int> iGenericClass = new GenericClass<int>();

    iGenericClass.Property = 1;

    GenericClass<string> sGenericClass = new GenericClass<string>();

    sGenericClass.Property = "1233";

    DoNothing<int> method = new DoNothing<int>(() => { });

    还有一种,别被吓到:T,S,Xiaomange这些语法,只是占位符别怕,你可以自己定义的,在你调用的时候确定类型就OK了,好了差不多能理解泛型了吧?再说一次泛型就是为了满足不同类型,相同代码的重用。

public class ChildClassGeneric
: GenericClass
, IGenericInterface
{ }

  接下来我们来聊一聊拓展的一部分,好像泛型很吊的样子感觉什么都能用泛型类型代替,但是天下哪有那么好的事情,双刃剑的道理都懂,所以出现了泛型的约束这个紧箍咒。

  泛型的约束

  直接来代码:

  很简单的一个例子,接口ISports,IWork,People类,Japanese类等简单继承了一下,目前准备的一些代码。

  public interface ISports    {        void Pingpang();    }    public interface IWork    {        void Work();    }    public class People    {        public int Id { get; set; }        public string Name { get; set; }        public void Hi()        { }    } public class Chinese : People, ISports, IWork    {        public void Tradition()        {            Console.WriteLine("仁义礼智信,温良恭俭让");        }        public void SayHi()        {            Console.WriteLine("吃了么?");        }        public void Pingpang()        {            Console.WriteLine("打乒乓球...");        }        public void Work()        {            throw new NotImplementedException();        }    } public class Hubei : Chinese    {        public Hubei(int id)        {        }        public string Changjiang { get; set; }        public void Majiang()        {            Console.WriteLine("打麻将啦。。");        }    } public class Japanese : ISports    {        public int Id { get; set; }        public string Name { get; set; }        public void Pingpang()        {            Console.WriteLine("打乒乓球...");        }        public void Hi()        { }    }

  再来个调用类Constraint

public class Constraint    {        ///         /// 代码编译没问题,执行的时候才报错        /// 代码安全问题        ///         ///         public static void ShowObject(object oParameter)        {            Console.WriteLine("This is {0},parameter={1},type={2}",                typeof(CommonMethod), oParameter.GetType().Name, oParameter);            People people = (People)oParameter;            Console.WriteLine(people.Id);//这里就不行了 代码安全问题,调用不到,但编译不会报错。            Console.WriteLine(people.Name);        }        ///         /// 基类约束:        /// 1 带来权力,可以使用基类里面的属性和方法        /// 2 带来义务,类型参数必须是基类或者其子类        ///         /// 
/// public static void Show
(T tParameter) where T : People, ISports, new()//都是and 关系 { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id); Console.WriteLine(tParameter.Name); tParameter.Hi(); tParameter.Pingpang(); T t = new T(); } public static void ShowPeople(People tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericMethod), tParameter.GetType().Name, tParameter.ToString()); Console.WriteLine(tParameter.Id); Console.WriteLine(tParameter.Name); tParameter.Hi(); //tParameter.Pingpang(); } public static T DoNothing
(T tParameter) //where T : ISports//接口约束 //where T : class//引用类型约束 //where T : struct//值类型约束 where T : new()//无参数构造函数约束 { //tParameter.Pingpang(); //return null; T t = new T(); return default(T);//会根据T的类型,去产生一个默认值 } }

  有兴趣的可以测试下,用ShowObject的方法和泛型Show<T>(T tParameter)调用来看差异,如果不加入约束,想调用参数的属性和方法,代码安全问题是调用不了的,会报错,但是加入基类约束之后是可以调用到的,所以泛型约束带来了权利,可以使用基类的属性和方法,但也带来义务,参数只能是基类和子类,又想马儿跑,又想马儿不吃草的事情是没有的,权利和义务是相对的,在享受权利的同时也会带来义务。

  其次,约束可以多重约束,然后即可作为参数约束也可以作为返回值约束,例如default(T)会根据泛型类型返回一个默认值,如果是无参数构造约束就可以类似这样写返回值T t=new T()。

  总之,我觉得泛型约束为了更灵活的满足不同条件的需求而产生的,就是我们在写一些固定的需求,约束叠加来完成我们的功能,同时不让泛型肆无忌惮。

        泛型约束范围如下:

约束

描述

where T: struct

类型参数必须为值类型。

where T : class

类型参数必须为引用类型。

where T : new()

类型参数必须有一个公有、无参的构造函数。当于其它约束联合使用时,new()约束必须放在最后。

where T : <base class name>

类型参数必须是指定的基类型或是派生自指定的基类型。

where T : <interface name>

类型参数必须是指定的接口或是指定接口的实现。可以指定多个接口约束。接口约束也可以是泛型的。

  好了泛型的约束的先说到这,继续套底子,了解到的都倒出来。

 

  逆变和协变

  逆变和协变不知道有没有小伙伴熟悉的,我开始是不知道这个的,第一次看到也是一脸懵逼,到现在也有点迷糊,能不能讲清楚看造化了,哈哈

   继续举栗子:

//协变 public interface IEnumerable
: IEnumerable //逆变 public delegate void Action
(T obj);

  这段代码是不是很熟悉?里面有个Out T 还有 in T,这里的Out 不是我们熟悉的参数返回Out,ref的作用,是协变专用的,逆变和协变指出现在接口或者委托泛型前面,

  In只能作为参数传入,Out只能作为参数传出。

  下面来个代码  

  

public class Bird    {        public int Id { get; set; }    }    public class Sparrow : Bird    {        public string Name { get; set; }    }       调用实例      IEnumerable
intList = new List
(); Action
iAction = null; Bird bird1 = new Bird(); Bird bird2 = new Sparrow();//左边是父类 右边是子类 Sparrow sparrow1 = new Sparrow(); //Sparrow sparrow2 = new Bird();//不是所有的鸟,都是麻雀 List
birdList1 = new List
();//一群鸟 是一群鸟 //List
birdList2 = new List
();//一群麻雀难道不是一群鸟 ? 不是的,没有父子关系 List
birdList3 = new List
().Select(c => (Bird)c).ToList();//这里使用别扭了,明明知道但就是不能这样写

  以上代码发现问题了吗?很明显出现了一些不和谐的地方,我们换个方式如下: 

      
      
  ///     /// 逆变:只能修饰传入参数    ///     /// 
public interface ICustomerListIn
{ //T Get(); void Show(T t); } public class CustomerListIn
: ICustomerListIn
{ //public T Get() //{ // return default(T); //} public void Show(T t) { } } ///
/// out 协变 只能是返回结果 /// ///
public interface ICustomerListOut
{ T Get(); //void Show(T t);//不能做参数 } public class CustomerListOut
: ICustomerListOut
{ public T Get() { return default(T); } //public void Show(T t) //{ //} }

 

        {
//协变:接口泛型参数加了个out,就是为了解决刚才的不和谐 IEnumerable
birdList1 = new List
(); IEnumerable
birdList2 = new List
(); //Func
func = new Func
(() => null); ICustomerListOut
customerList1 = new CustomerListOut
(); ICustomerListOut
customerList2 = new CustomerListOut
(); } { //逆变 ICustomerListIn
customerList2 = new CustomerListIn
(); ICustomerListIn
customerList1 = new CustomerListIn
(); //customerList1.Show() ICustomerListIn
birdList1 = new CustomerListIn
(); birdList1.Show(new Sparrow()); birdList1.Show(new Bird()); Action
act = new Action
((Bird i) => { }); }

  这样可以了,协变IEnumerable加入协变Out 左边是个父类,右边可以是子类,逆变In 左边是个字类,右边也可以是父类,下面这段就更晕了,稍微看下吧。

      {                IMyList
myList1 = new MyList
(); IMyList
myList2 = new MyList
();//协变 IMyList
myList3 = new MyList
();//逆变 IMyList
myList4 = new MyList
();//协变+逆变 }

  总结下原理:泛型在JIT编译时指定具体类型,同一个泛型类,不同的类型参数,其实会变成不用的类型。

  我走的很慢,但从不后退!

转载于:https://www.cnblogs.com/MachineVision/p/10431199.html

你可能感兴趣的文章
Angular CLI 使用教程指南参考
查看>>
[设计模式之禅读书笔记]001_设计模式六大原则(一):单一职责原则(Single Responsibility Principle)...
查看>>
CSAPP:cachelab(1)
查看>>
时间序列识别代码调试版本1
查看>>
Tomcat内存溢出解决办法
查看>>
Vue:v-model指令
查看>>
Software Engineering | Strategy pattern
查看>>
ios开发系列-准备工作
查看>>
Android Studio调试手机或者安装APK的时候出现install failed test only
查看>>
js闭包
查看>>
Xcode +SVN
查看>>
设置界面分析
查看>>
SQL中DateTime转换成Varchar样式
查看>>
java.util.AbstractList
查看>>
几个常见用于解决nginx负载均衡的session共享问题的办法
查看>>
setTimeOut、setInterval与clearInterval函数
查看>>
Appium原理及版本变化细节
查看>>
iphone ios 用xcode4.2开发 访问web service的功能
查看>>
Visual Studio 代码折叠快捷键(摘要)
查看>>
《2016ThoughtWorks技术雷达峰会----雷达新趋势》
查看>>