撸·泛型

因为下面这张图片,逼自己再看一遍泛型,并做下记录,加深印象。翻看.net core源码,发现泛型已经使用的到处都是,所以掌握泛型是成为最基础的知识了。
![](https://img2018.cnblogs.com/blog/710776/201902/710776-20190218181940499-616182179.png)

前奏

> 泛型 * 引入时间:.net framework 2.0 * 引入目的:为了解决装箱、拆箱带来的性能损失。 * 安全效果: 1. 装箱拆箱有一个安全转换问题,所以又解决了类型安全问题。 2. 在父子类或者接口继承的时候,泛型很好的起到了安全约束的作用,如第一张图片。 * 阳光普照效果: 节约代码量,提高重复利用(object也可以实现,但是有性能损失)。

泛型的协变和逆变

  • 引入时间:.net framework4.0
  • 引入目的:为了解决泛型父子类型的转换问题。
  • 使用规则:
    1. 只能放在接口或者委托的泛型参数前面。
    2. out 协变covariant,用来修饰返回值,儿子可以赋值给老父亲。
    3. in:逆变contravariant,用来修饰传入参数,老父亲可以赋值给儿子。

正文

### 泛型方法 定义:泛型方法是通过类型参数声明的方法。 在下面的代码中,Method方法名称后面必须紧跟类型占位符T,如果没有,编译器会将方法参数类型T默认识别为正常类型,而不是占位符,所以会报错的。正确样例如下: ``` public class Generic { /// /// 泛型方法 /// /// /// public static void Method(T tParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(Generic), tParameter.GetType().Name, tParameter.ToString()); } } ```

同理,如果返回类型是泛型的话,Method方法名称后面必须紧跟类型占位符T,如果没有,编译器会将方法参数类型T默认识别为正常类型,而不是占位符,所以会报错的。正确样例如下:

public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters)
{
     throw null;
}

泛型类

  定义:泛型类封装不特定于特定数据类型的操作。 泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和树等集合。 无论存储数据的类型如何,添加项和从集合删除项等操作的执行方式基本相同。
  通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。样例如下:

 public class Generic<T>
     {
        public T t;
    }

泛型接口

跟泛型类定义一样,只要将class改为interface。样例如下:

 public interface Generic<T>
     {
       T GetT(T t);
    }

泛型委托

跟方法一样,同样是约束方法参数类型。样例如下:

  public delegate void SayHi<T>(T t);

default 关键字

在泛型类和泛型方法中会出现的一个问题是,如何把缺省值赋给参数化类型,此时无法预先知道以下两点: 1. T将是值类型还是引用类型 2. 如果T是值类型,那么T将是数值还是结构

对于一个参数化类型T的变量t,仅当T是引用类型时,t = null语句才是合法的; t = 0只对数值的有效,而对结构则不行。这个问题的解决办法是用default关键字,它对引用类型返回空,对值类型的数值型返回零。而对于结构,它将返回结构每个成员,并根据成员是值类型还是引用类型,返回零或空。如下所示:

public class MyList<T>
{
    //...
        public T GetNext()
        {
            T temp = default(T);
            if (current != null)
            {
                temp = current.Data;
                current = current.Next;
            }
            return temp;
        }
}

使用注意点(重点)

### 注意点1 > 泛型在声明的时候可以不指定具体的类型,但是在使用的时候必须指定具体类型; 如果子类也是泛型的,那么继承的时候可以不指定具体类型 ``` namespace MyGeneric { /// /// 使用泛型的时候必须指定具体类型, /// 这里的具体类型是int /// public class CommonClass :GenericClass { }
/// <summary>
/// 子类也是泛型的,继承的时候可以不指定具体类型
/// </summary>
/// <typeparam name="T"></typeparam>
public class CommonClassChild<T>:GenericClass<T>
{

}

}


### 注意点2
> 类实现泛型接口也是这种情况

namespace MyGeneric
{
///


/// 必须指定具体类型
///

public class Common : IGenericInterface
{
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();
    }
}

}


<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">泛型约束</h2>
&#8195;&#8195;为什么要泛型约束,其主要问题还是解决安全问题,规范开发人员写代码的规范性,避免一些在运行时期才能检查到的错误。比如下面的代码,在编译器是不会报错的,但是在运行期会出现转换异常。

public class 动物
{

}
public class 狗 :动物
{
}

public class 猫
{

}

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


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

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


<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">协变和逆变</h2>
在OO的世界里,可以安全地把子类的引用赋给父类引用。但是在T的世界里,就不一定了。有的能变,有的不能变,先了解以下几点:
* 以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
* 当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
* 值类型不参与逆变与协变。

如果不能理解以上几句话,就先看下面的知识点
### 协变
&#8195;&#8195;所谓协变,就是为了解决子类泛型接口或委托能回到父类泛型接口或委托上来。(Foo<父类> = Foo<子类> )

//泛型委托:
public delegate T MyFuncA();//不支持逆变与协变
public delegate T MyFuncB();//支持协变

MyFuncA funcAObject = null;
MyFuncA funcAString = null;
MyFuncB funcBObject = null;
MyFuncB funcBString = null;
MyFuncB funcBInt = null;

funcAObject = funcAString;//编译失败,MyFuncA不支持逆变与协变
funcBObject = funcBString;//变了,协变
funcBObject = funcBInt;//编译失败,值类型不参与协变或逆变

//泛型接口
public interface IFlyA { }//不支持逆变与协变
public interface IFlyB { }//支持协变

IFlyA flyAObject = null;
IFlyA flyAString = null;
IFlyB flyBObject = null;
IFlyB flyBString = null;
IFlyB flyBInt = null;

flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
flyBObject = flyBString;//变了,协变
flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变

//数组:
string[] strings = new string[] { "string" };
object[] objects = strings;


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

public delegate void MyActionA(T param);//不支持逆变与协变
public delegate void MyActionB(T param);//支持逆变

public interface IPlayA { }//不支持逆变与协变
public interface IPlayB { }//支持逆变

MyActionA actionAObject = null;
MyActionA actionAString = null;
MyActionB actionBObject = null;
MyActionB actionBString = null;
actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败
actionBString = actionBObject;//变了,逆变

IPlayA playAObject = null;
IPlayA playAString = null;
IPlayB playBObject = null;
IPlayB playBString = null;
playAString = playAObject;//IPlayA不支持逆变与协变,编译失败
playBString = playBObject;//变了,逆变


>注意

 in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?
原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。

<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">泛型的本质</h2>
 > 为什么泛型可以解决的各种类型问题呢?

泛型是延迟声明的:即定义的时候没有指定具体的参数类型,把参数类型的声明推迟到了调用的时候才指定参数类型。 延迟思想在程序架构设计的时候很受欢迎。例如:分布式缓存队列、EF的延迟加载等等。
> 泛型究竟是如何工作的呢?

控制台程序最终会编译成一个exe程序,exe被点击的时候,会经过JIT(即时编译器)的编译,最终生成二进制代码,才能被计算机执行。泛型加入到语法以后,VS自带的编译器又做了升级,升级之后编译时遇到泛型,会做特殊的处理:生成占位符。再次经过JIT编译的时候,会把上面编译生成的占位符替换成具体的数据类型。请看下面一个例子:

1 Console.WriteLine(typeof(List<>));
2 Console.WriteLine(typeof(Dictionary<,>));

![](https://img2018.cnblogs.com/blog/710776/201905/710776-20190523185755534-734515836.png)

从上面的截图中可以看出:泛型在编译之后会生成占位符。
其次,通过测试发现泛型方法的性能与普通方法相等,object方法的性能最低。

<h2 style="background-color:#226DDD;width:100%;color:#FFF;text-indent:1em">总结</h2>
&#8195;&#8195;泛型已经发展了很多年,现在流行DDD开发方式,使用泛型解决各种问题也越来越多,以上总结只是个人翻看别人博客,然后学习留下记录。如果要查看更多信息,可以浏览更多链接。如有帮助,点个赞。
&#8195;&#8195;撸完泛型,接下来还要重新回顾撸两个东西
1. [撸·委托](https://www.cnblogs.com/zhan520g/p/10917053.html)
2. [撸·反射](https://www.cnblogs.com/zhan520g/p/10917061.html)

> 引用

1. [逆变与协变详解](http://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html)
2. [c#泛型详解](https://www.cnblogs.com/dotnet261010/p/9034594.html)

posted @ 2019-05-24 09:19  天空的湛蓝  阅读(335)  评论(0编辑  收藏  举报