查看原文
其他

看完这篇,终于知道自己会不会 C# 泛型了!

羽生结弦 CSDN 2019-10-30

作者 | 羽生结弦

责编 | 胡巍巍

在开发过程中,同一段代码处出现多次调用,并且会有不同的类型在使用,这种就叫做跨类型代码复用。一般情况下跨类型代码复用我们会用到如下两种方法:

1. 继承;

2. 泛型。

继承是通过父类来代表代码复用,而泛型是通过带有占位符的模板来代码复用,其中占位符指的是类型。

比如:int、syting和实体等。本片主要讲解泛型的相关知识,下面就来详细讲解一下泛型。

零、泛型类型


泛型会声明类型参数,消费者需要提供类型参数来把占位符类型填充上。我们先来看一个例子:

public class GenericClass<T>
{

    T[] tArray=new T[10];
    int position=0;
    public void Push(T t) =>tArray[position++]=t;
    public T Pop()=>tArray[--position];
    public T[] GetTs() => tArray;
}
class Program
{

    static void Main(string[] args)
    
{
        var genericStr=new GenericClass<string>();
        generic.Push("张三");
        generic.Push("李四");
        generic.Push("王五");
        generic.Pop();

        var genericInt=new GenericClass<int>();
        generic.Push(1);
        generic.Push(2);
        generic.Push(3);
        generic.Pop();
    }
}

在上面的代码中当我们将string传入泛型类中,将会隐式动态的创建类型,这种操作被称为合成,合成将发生在运行时,而非编译时。当我们在代码中传入非string类型的值时,在编译时将报错。同样在上面的代码中我们也看到了int传入泛型类中的情况,这就说明泛型类可以跨类型复用。

小知识1:

我们将GenericClass<T>称为开放类型(OpenType),而将GenericClass<string>称为封闭类型(CloseType),开放类型在编译后就变成了封闭类型,在运行时所有的泛型类型都是封闭类型,因为占位符已经被具体类型填充完毕。

小知识2:

对于每一种封闭类型,静态数据都是唯一的,例如:

class Program
{

   static void Main(string[] args)
   
{
       // 输出1
       Console.WriteLine(++MyClass<int>.Count);
       // 输出2
       Console.WriteLine(++MyClass<int>.Count);
       // 输出1
       Console.WriteLine(++MyClass<string>.Count);
   }
}

class MyClass<T>
{

   public static int Count;
}

上面代码中,MyClass中存在一个静态字段Count,我们看到前两次的调用输出的分别是1和2,但是第三次输出的确实1,那么这是为什么呢,原因就是前两次的类型参数和泛型类型种的静态字段的类型一致,而第三次的类型参数不一致导致的。

在泛型类型饿子类中可以继续让父类参数保持开放,也剋及关闭父类的类型参数,同样子类也可以引入新的类型,我们来看一下例子:

// 父类继续保持开放
class Father<T>{}
class Children<T>:Father<T>{}

//关闭父类
class Father<T>{}
class Children:Father<string>{}

//引入新参数类型
class Father<T>{}
class Children<T,U>:Father<T>{}

小知识:

封闭参数类型的时候,该类型可以把自己作为具体的类型,例子如下:

class AClass<T>{}

class BClass : AClass<BClass> { }


泛型方法


泛型方法在方法签名内声明类型参数,例如下:

class Program
{

    static void Main(string[] args)
    
{
        GenericFun<int>(124);
    }
    static void GenericFun<T>(T x, T y)
    {
        T tmp = x;
        x = y;
        y = tmp;
        Console.WriteLine("x:" + x + "   y:" + y);
    }
}

在上面的代码中我们看到 ```generic.GenericFun<int>(12,4);``` ,我们将int传入泛型方法中,现在我们将这行代码改写成如下形式 ```generic.GenericFun(12,4);```。

我们发现现在这段代码和前面那段代码缺少了 <int> ,那么这么写代码是否有错呢?

答案是要看情况,当编译器可以推断出参数类型的话,我们可以省略掉参数类型,但是当编译器无法推断出参数类型的话我们就必须写上参数类型了。

当然上面这段代码是可以正确运行的,因为编译器可以正确的推断出参数类型。
我们还有如下几点需要注意的:

1. 泛型类中的方法,如果方法引入了参数类型,那它就是泛型方法,反之就不是泛型方法;

2. 除了class、struct、interface、delegate 和方法可以引入类型参数外,属性、字段、索引器、事件和构造函数等都不能声明类型参数,但是可以使用所在泛型类的类型参数。

小知识:

泛型类型和泛型方法可以有多个参数类型,例如 ```class a<T,U>``` 调用方法和单个参数类型一样。结合这一点我们就可以推断出泛型类型和泛型方法可以出现重载,只要参数类型的数量不同就没问题。

在一些情况下我们需要获取参数类型的默认值,这时我们就可以使用default(T)来获得。

约束


我们虽然可以使用所有类型作为泛型类型参数,但是我们在实际开发时很少会这么使用,一般会将类型参数约束到指定的范围内。泛型可用约束如下:

1. base-class:某一个父类的子类;

2. interface:必须是实现了指定的接口;

3. class:必须是引用类型;

4. struct:必须是非空值类型;

5. new():必须包含无参构造函数;

6. U:T:U必须继承T

我们使用的时候是这样的:

class Aclass {}

interface Binterface { }

class Generic1<T> where T : Aclass { }

class Generic2<T,U>
    where T:
Aclass,Binterface
    where U : new()
{ }

上面的代码表示 Generic1 类的参数类型T继承自Aclass,Generic2 类的T继承子Aclass,并且实现了 Binterface 接口,而且U包含了无参构造函数。

注意:约束可以用于泛型类型和泛型方法

类型参数与转换


C#种转换支持如下几种:

1. 数值转换;

2. 引用转换;

3. 装箱拆箱转换;

4. 自定义转换。

在发生编译的时候,会根据一直类型的操作数来决定采用那种转换,但是在泛型中我们不知道具体的类型是什么,编译器就会默认使用自定义转换,那么就有可能出现错误。

为了解决这个问题,我们引入了as ,例如我们将传进来的值转换成StringBuilder,这时我们可以这么做:

StringBuilder ToFloat<T>(T t)
{
    StringBuilder f = t as StringBuilder;
    return f;
}


variance 转换


讲解variance 转换前,先来简单了解一下协变、逆变和不变。

1. 协变(Covariance):当T作为返回值输出的时候;

2. 逆变(Contravariance):当T作为输出值的时候;

3. 不变(Invariance):当T即是输入又是输出时。

注意:以上这三种就是variance,只能用在接口和委托中。

所谓variance 转换就是上面三种之间的相互转换,variance 转换是引用转换的一个方法,从a转换到b如果是本体转换或者隐式引用转换,那么就是正确的。例如:

IEnumerable<stringto IEnumerable<object>
IEnumerable<IDisposable> to IEnumerable<object>

作者简介:朱钢,笔名羽生结弦,CSDN博客专家,.NET高级开发工程师,7年一线开发经验,参与过电子政务系统和AI客服系统的开发,以及互联网招聘网站的架构设计,目前就职于北京恒创融慧科技发展有限公司,从事企业级安全监控系统的开发。

【END】

 热 文 推 荐 

华人学者解开计算机领域 30 年难题:布尔函数敏感度猜想

百度移动:静悄悄的战争

三十年软件开发之路:老码农的自我修养!

☞Android 告急!

☞3个核心差异, 告诉你为什么Libra永远成不了比特币!

☞微博宕机复盘:什么样的技术架构,可支持80个明星并发出轨?

☞公开课 | 详解事件抽取与事件图谱构建

☞超酷炫!Facebook用深度学习和弱监督学习绘制全球精准道路图

☞中国第一程序员,微软得不到他就要毁了他!

点击阅读原文,输入关键词,即可搜索您想要的 CSDN 文章。

你点的每个“在看”,我都认真当成了喜欢

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存