zoukankan      html  css  js  c++  java
  • C# 泛型(Generic)

    泛型(Generic)

    允许延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候

    在泛型类型的定义中,出现的每个T(一个展位变量而已叫别的名字也行)在运行时都会被替换成实际的类型参数。

    泛型方法

    现在有一个需求,需要写一个方法,这个方法传入的参数可能是int型的,也可能是string型的。首先我们可以用方法的重载方案解决这个问题,比如下面两个重载方法:

    public void test(int param)
    { }
    public void test(string param)
    { }

    但是这样的话如果支持的类型变多了,那么你需要写很多重载方法。如果只写一个重载方法,则可以写成

     public void test(object param) { }

    但是这样写又出现object转成其他类型的问题,会带来效率损失。同时不检查类型,一旦传入了不支持的类型,可能会出问题。

    现在把test方法改造一下,这样写:

     public void test<T>(T param){ }

    这样写之后,使用的时候要求提前通知这个方法,你传入的是什么类型,即:

    test<int>(10);

    如果写成 test<int>("10");编译器就会报错。

    这就是泛型方法。这里面我们省略了方法内部的实现,其实仔细想一下,如果要在这样的方法里面添加业务代码,似乎除了用于存放数据的集合之外,并没有多少场景需要这么写方法。没错,泛型这个东西最常用的应用场景就是数据集合。而List<T>就是一个存放各种数据的泛型类。

    泛型类

    上面的方法:public void test<T>(T param){ },我们可以尝试一下把<T>去掉,只写成public void test(T param){ }看看会发生什么。你会发现编译器会报错,那么我们再尝试一下在这个方法的类名上加上<T>,即写成:

    class TClass<T>
    {
        public void test(T param)
        { }
    }

    你会发现,如果把<T>放到类名上,里面的方法就不需要加 <T>了,同时编译器也不会报错。这是一种比较简洁的写法。这个时候,TClass这个类就是泛型类,而它的构造方法,则和普通的类的构造方法的写法是一样的。当你要实例化这个类型的时候,必须告诉这个类型T代表哪个类型,之后,所有这个类里面被标识了T的地方,都是指你开始实例化指明的类型。比如test这个方法里面传入的param,一定要和你开始实例化这个类的时候指明的类型一致。再比如你写一个返回T的方法: public T returnTest() { },这个方法的返回值也必须是你实例化类时指明的类型。如果我们TClass改成List,把test改成Add,则方法变成了下面这样

     class List<T>
     {
         public void Add(T param)
         { }
     }

    这不就是我们经常用的List<T>这个泛型集合吗。当然它的内部实现还有很多东西,这里我们不去关注。

    参考链接:https://www.cnblogs.com/ypa-yap-yap/p/11523736.html

    泛型约束

    为什么要泛型约束,其主要问题还是解决安全问题,规范开发人员写代码的规范性,避免一些在运行时期才能检查到的错误。比如下面的代码,在编译器是不会报错的,但是在运行期会出现转换异常。

    public class 动物
    { 
    }
     public class 狗 :动物
    {
    } 
    public class 猫
    { 
    }
      
     public class Generic
        {
            public void method(object cat)
            {
                动物 animal= (动物)cat;
            }
        }

    所谓的泛型约束,实际上就是约束的类型T。使T必须遵循一定的规则。比如T必须继承自某个类,或者T必须实现某个接口等等。那么怎么给泛型指定约束?其实也很简单,只需要where关键字,加上约束的条件。

    序号约束说明
    1 T:struct 类型参数必须是值类型
    2 T:class 类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。
    3 T:new() 类型参数必须具有无参数的公共构造函数。 当与其他约束一起使用时,new() 约束必须最后指定。
    4 T:基类名 类型参数必须是指定的基类或派生自指定的基类。
    5 T:接口名称 类型参数必须是指定的接口或实现指定的接口。 可以指定多个接口约束。 约束接口也可以是泛型的。
    6 T:基类名,接口名称,new() 泛型约束也可以同时约束多个,但是new()必须放在最后

    泛型的协变和逆变

    • 引入时间:.net framework4.0

    • 引入目的:为了解决泛型父子类型的转换问题。

    • 使用规则:

      1. 只能放在接口或者委托的泛型参数前面。

      2. out 协变covariant,用来修饰返回值,儿子可以赋值给老父亲。

      3. in:逆变contravariant,用来修饰传入参数,老父亲可以赋值给儿子。

    在OO的世界里,可以安全地把子类的引用赋给父类引用。但是在T的世界里,就不一定了。有的能变,有的不能变,先了解以下几点:

    • 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。

    • 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。

    • 值类型不参与逆变与协变。

    如果不能理解以上几句话,就先看下面的知识点

    协变

    所谓协变,就是为了解决子类泛型接口或委托能回到父类泛型接口或委托上来。(Foo<父类> = Foo<子类> )

    //泛型委托:
    public delegate T MyFuncA<T>();//不支持逆变与协变
    public delegate T MyFuncB<out T>();//支持协变
     
    MyFuncA<object> funcAObject = null;
    MyFuncA<string> funcAString = null;
    MyFuncB<object> funcBObject = null;
    MyFuncB<string> funcBString = null;
    MyFuncB<int> funcBInt = null;
     
    funcAObject = funcAString;//编译失败,MyFuncA不支持逆变与协变
    funcBObject = funcBString;//变了,协变
    funcBObject = funcBInt;//编译失败,值类型不参与协变或逆变
     
    //泛型接口
    public interface IFlyA<T> { }//不支持逆变与协变
    public interface IFlyB<out T> { }//支持协变
     
    IFlyA<object> flyAObject = null;
    IFlyA<string> flyAString = null;
    IFlyB<object> flyBObject = null;
    IFlyB<string> flyBString = null;
    IFlyB<int> flyBInt = null;
     
    flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
    flyBObject = flyBString;//变了,协变
    flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变
     
    //数组:
    string[] strings = new string[] { "string" };
    object[] objects = strings;

    逆变

    所谓协变,就是为了解决父类泛型接口或委托能回到子类泛型接口或委托上来。(Foo<子类> = Foo<父类>)

    public delegate void MyActionA<T>(T param);//不支持逆变与协变
    public delegate void MyActionB<in T>(T param);//支持逆变
     
    public interface IPlayA<T> { }//不支持逆变与协变
    public interface IPlayB<in T> { }//支持逆变
     
    MyActionA<object> actionAObject = null;
    MyActionA<string> actionAString = null;
    MyActionB<object> actionBObject = null;
    MyActionB<string> actionBString = null;
    actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败
    actionBString = actionBObject;//变了,逆变
     
    IPlayA<object> playAObject = null;
    IPlayA<string> playAString = null;
    IPlayB<object> playBObject = null;
    IPlayB<string> playBString = null;
    playAString = playAObject;//IPlayA不支持逆变与协变,编译失败
    playBString = playBObject;//变了,逆变

    注意

    in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?

    原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。

    使用注意点(重点)

    注意点1

    泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型;

    如果子类也是泛型的,那么继承的时候可以不指定具体类型

    namespace MyGeneric
    {
        /// <summary>
        /// 使用泛型的时候必须指定具体类型,
        /// 这里的具体类型是int
        /// </summary>
        public class CommonClass :GenericClass<int>
        {
        }
    
        /// <summary>
        /// 子类也是泛型的,继承的时候可以不指定具体类型
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CommonClassChild<T>:GenericClass<T>
        {
    
        }
    }

    注意点2

    类实现泛型接口也是这种情况

    namespace MyGeneric
    {
        /// <summary>
        /// 必须指定具体类型
        /// </summary>
        public class Common : IGenericInterface<string>
        {
            public string GetT(string t)
            {
                throw new NotImplementedException();
            }
        }
    
        /// <summary>
        /// 可以不知道具体类型,但是子类也必须是泛型的
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public class CommonChild<T> : IGenericInterface<T>
        {
            public T GetT(T t)
            {
                throw new NotImplementedException();
            }
        }
    }

    参考链接:https://www.cnblogs.com/zhan520g/p/10397117.html#idx_10

    参考链接:https://www.cnblogs.com/dotnet261010/p/9034594.html

  • 相关阅读:
    Linux下利用rsync实现多服务器文件同步
    SVN使用import导入新数据到版本库
    SVN协同开发时服务端与线上APACHE测试环境网站同步记录 转
    [转]rsync的配置与应用
    Matrix 旋转mc 注册点在mc的左上角
    多边形面积计算
    【神奇的代码】
    【碰撞回弹】
    三角函数
    判断点是否在线段或直线上
  • 原文地址:https://www.cnblogs.com/zhaoyl9/p/12174500.html
Copyright © 2011-2022 走看看