泛型概述:
目的:
通过泛型可以定义类型安全的数据结构,而无须使用实际的数据类型。使相同算法可以应用于不同的数据类型,而无须复制类型特定的代码。与C++中的模板在语法上比较类似。
语法:
public class Stack<T>
{
private T[] m_Item;
public void Push(T data)
{
}
public void Pop(T data)
{
}
}
优点:
1) 提供了一个强类型的编程模型。在参数化的类中,只有成员明确希望的数据类型才能使用。减小了InvalidCastException异常发生的几率。2) 为泛型类型成员使用值类型,不再造成到object的强制转换,不需要装箱操作。提高了效率。
3) 代码可读性好。减少了代码的膨胀。
一个简单的实例:
using System;
using System.Collections.Generic;
using System.Text;
namespace chapter_11
{
class Program
{
class Stack<T>
{
private T[] store;
private int size;
public Stack()
{
store = new T[10];
size = 0;
}
public void Push(T x)
{
store[size++] = x;
}
public T Pop()
{
return store[--size];
}
}
static void Main(string[] args)
{
Stack<int> IntStack = new Stack<int>();
Stack<String> StringStack = new Stack<String>();
IntStack.Push(22);
StringStack.Push("Hello");
Console.WriteLine("Int Stack Pop value:{0}", IntStack.Pop());
Console.WriteLine("String Stack Pop value:{0}", StringStack.Pop());
Console.ReadKey();
}
}
}
泛型接口:
在泛型类中同样可以使用接口方法,这样可以使代码更加清晰。从代码实例中可以发现下面的例子与上面的例子的区别,使用了一个IStack的接口定义了Pop 和 Push 方法。
代码实例:
using System;
using System.Text;
namespace GeneralTypeInterface
{
class Program
{
interface IStack<T>
{
void Push(T x);
T Pop();
}
class Stack<T> : IStack<T>
{
private T[] store;
private int size;
public Stack()
{
store = new T[10];
size = 0;
}
public void Push(T x)
{
store[size++] = x;
}
public T Pop()
{
return store[--size];
}
}
static void Main(string[] args)
{
Stack<int> IntStack = new Stack<int>();
Stack<String> StringStack = new Stack<String>();
IntStack.Push(22);
StringStack.Push("Hello");
Console.WriteLine("Int Stack Pop value:{0}", IntStack.Pop());
Console.WriteLine("String Stack Pop value:{0}", StringStack.Pop());
Console.ReadKey();
}
}
}
约束:
一种规则,强迫泛型类型参数必须遵守的规则。定义的比较虚,使用一个实例来解释一下。
public class Compare<T>
{
public Compare(T item1, T item2)
{
_item1 = item1;
_item2 = item2;
}
public bool execute()
{
if (_itme1.CompareTo(_item2))
{
return true;
}
else
{
return false;
}
}
private T _itme1;
private T _itme2;
}
问题来了,没有人敢保证,泛型类型的参数 T 运行时的类型中一定会有一个 CompareTo的接口。T1和T2在使用时可以是任何类型的。没有人希望这种错误出现在运行时刻。
如果不希望它出现在运行时刻,那就让它在编译时被消灭掉。
语法:声明一个约束 要使用 where 关键字 后面加 参数:要求
例:
where T:System.IComparable<T>
对上面的代码加上约束就成为:保证T 一定实现了一个CompareTo 接口
public class Compare<T>
where T:System.IComparable<T>
{
public Compare(T item1, T item2)
{
_item1 = item1;
_item2 = item2;
}
public bool execute()
{
if (_itme1.CompareTo(_item2))
{
return true;
}
else
{
return false;
}
}
private T _itme1;
private T _itme2;
}
1) 接口约束
保证泛型类型的参数一定会实现指定的接口。上面的代码就是实例,不用在列举了。
当我们需要将构建的类型限制为特定的基类时我们可以采用基类约束。 注意: 基类约束只能出现一次。
使用基类约束时,只能必须是放在最前面,保证第一个出现。
public class Tester<T> : TesterBase<T>
where T: TBase
{
......
}
3) 多个约束
对于任何给定类型的参数,都可以指定任意数量的接口作为约束,但基类约束只能有一个。原因,一个类可以有多个接口,但只能从一个类派生。当使用多个约束时,基类约束必须是第一个出现的。
示例:
public class Tester<T> : TesterBase<T>
where T: TBase
where T: ICompareable<T>, IFormattable
{
......
}
4) 构造器约束
某些情况下,程序需要在泛型类的内部创建一个泛型类型参数的对象,这就要求泛型类型 一定要有个构造函数,来保证创建对象时不会出错。但只能 对默认构造器进行约束,不能为带参数的构造器指定约束。
示例代码:
public class Tester<T>
where T: new()
{
public T CreateInst()
{
T newObj = new T();
}
}
5) 约束继承
约束可以由一个派生类继承,但在派生类中必须显示的指定这些约束。避免人们在使用派生类时发现有约束,但又不知道从那里来的。当然子类除了继承约束外,还可以自由的添加自己的约束。
示例代码:
public class TestBase<T> where T : ISomeInterface { }
public class Tester<T> : TestBase<T> where T : ISomeInterface { }
6) 约束限制: 主要是防止产生无意义的代码或有歧义的代码。
- 1) 不支持运算符约束
错误实例:
public class Test<T>
where T: System.IComparable<T> || System.IFormattable
- 3) 委托和枚举类型的约束是无效的
泛型方法: 即使包容类不是泛型类,或者方法包含的类型参数不在泛型类的类型参数列表中,也依然使用泛型的方法。
实例:
public static class MathEx
{
public static T Max<T>(T first, params T[]values)
where T: IVompstsnlr<T>
{
....
}
}
当然在使用方法的时候,编译器可以主动的推断参数的类型。
示例:
Console.WriteLine(MathEx.Max<int>(7, 12));
与
Console.WriteLine(MathEx.Max(7, 12));
效果是一样的。
协变性和逆变性
同一个泛型类,但用不同的两个类型参数声明两个变量,这两个参数一个是父类一个是子类。非协变量: C#不允许把 子类的泛型变量赋给 父类的泛型变量。
非逆变量: C#不允许把 父类的泛型变量赋给 子类的泛型变量。
示例:
public class A {}
public class SubA : A {}
public class Tester<T>{};
Tester<A> a = new Tester<SubA>(); // 错误, 非协变量
error CS0029: Cannot implicitly convert type 'Tester<SubA>' to 'TesterA>'
Tester<SubA> a = new Tester<A>(); // 错误, 非逆变量
看个例子就好理解了,其实就是泛型类型变量的类型转换原则是不随着泛型参数具体类型进行变化的。