尽管 C# 泛型的根基是 C++ 模板

时间:2022-01-01 04:57:56

我们在编写措施时,经常遇到两个模块的成果非常相似,只是一个是措置惩罚惩罚int数据,另一个是措置惩罚惩罚string数据,或者其他自界说的数据类型,但我们没有步伐,只能分袂写多个要领措置惩罚惩罚每个数据类型,因为要领的参数类型差别。有没有一种步伐,在要领中传入通用的数据类型,这样不就可以合并代码了吗?泛型的呈现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

为什么要使用泛型
为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但成果是实现一个栈,这个栈只能措置惩罚惩罚int数据类型:

public class Stack

{

private int[] m_item;

public int Pop(){...}

public void Push(int item){...}

public Stack(int i)

{

this.m_item = new int[i];

}

}

上面代码运行的很好,但是,当我们需要一个栈来生存string类型时,该怎么办呢?很多人城市想到把上面的代码复制一份,把int改成string不就行了。固然,这样做自己是没有任何问题的,但一个优秀的措施是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的措施员会想到用一个通用的数据类型object来实现这个栈:

public class Stack

{

private object[] m_item;

public object Pop(){...}

public void Push(object item){...}

public Stack(int i)

{

this.m_item = new[i];

}

}

这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没出缺陷的,主要表示在:

当Stack措置惩罚惩罚值类型时,会呈现装箱、折箱操纵,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损掉非常严重。 
在措置惩罚惩罚引用类型时,虽然没有装箱和折箱操纵,但将用到数据类型的强制转换操纵,增加措置惩罚惩罚器的承担。 
在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):
Node1 x = new Node1();

stack.Push(x);

Node2 y = (Node2)stack.Pop();

上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将呈现措施运行时的类型转换异常,但却逃离了编译器的查抄。

针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来取代object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安适。

使用泛型
下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,期待在实例化时用一个实际的类型来取代。让我们来看看泛型的威力:

public class Stack<T>

{

private T[] m_item;

public T Pop(){...}

public void Push(T item){...}

public Stack(int i)

{

this.m_item = new T[i];

}

}

类的写法不乱,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安适的。这个类的挪用要领:

//实例化只能生存int类型的类

Stack<int> a = new Stack<int>(100);

a.Push(10);

a.Push("8888"); //这一行编译欠亨过,因为类a只接收int类型的数据

int x = a.Pop();

//实例化只能生存string类型的类

Stack<string> b = new Stack<string>(100);

b.Push(10);    //这一行编译欠亨过,因为类b只接收string类型的数据

b.Push("8888");

string y = b.Pop();

这个类和object实现的类有截然差此外区别:

1.       他是类型安适的。实例化了int类型的栈,就不能措置惩罚惩罚string类型的数据,其他数据类型也一样。

2.       无需装箱和折箱。这个类在实例化时,凭据所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

3.       无需类型转换。

泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,按照用户指定的数据类型取代T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以差此外关闭类的本地代码是不一样的。凭据这个道理,我们可以这样认为:

泛型类的差此外关闭类是分袂差此外数据类型。

例:Stack<int>和Stack<string>是两个完全没有任何关系的类,你可以把他当作类A和类B,这个解释对泛型类的静态成员的理解有很大辅佐。

泛型类中数据类型的约束
措施员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制挪用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方法是指定T的祖先,即担任的接口或类。因为C#的单根担任性,所以约束可以有多个接口,但最多只能有一个类,并且类必需在接口之前。这时就用到了C#2.0的新增关键字:

public class Node<T, V> where T : Stack, IComparable

where V: Stack

{...}

以上的泛型类的约束表白,T必需是从Stack和IComparable担任,V必需是Stack或从Stack担任,否则将无法通过编译器的类型查抄,编译掉败。