泛型(一)
泛型通常用在集合和集合上运行的方法中;
泛型NET Framework2.0提供一个新的命名空间System。Collections。Generic。
早期集合缺点:
System.Collections.ArrayList list = new System.Collections.ArrayList(); // Add an integer to the list. list.Add(3); // Add a string to the list. This will compile, but may cause an error later. list.Add("It is raining in Redmond.");
添加到 ArrayList 中的任何引用或值类型都将隐式地向上强制转换为 Object。如果项是值类型,则必须在将其添加到列表中时进行装箱操作,在检索时进行取消装箱操作。强制转换以及装箱和取消装箱操作都会降低性能;在必须对大型集合进行循环访问的情况下,装箱和取消装箱的影响非常明显。(泛型的优点)
// The .NET Framework 2.0 way to create a list List<int> list1 = new List<int>(); // No boxing, no casting: list1.Add(3); // Compile-time error: // list1.Add("It is raining in Redmond.");
优点:“对于客户端代码,与 ArrayList 相比,使用 List<T> 时添加的唯一语法是声明和实例化中的类型参数。虽然这稍微增加了些编码的复杂性,但好处是您可以创建一个比 ArrayList 更安全并且速度更快的列表,特别适用于列表项是值类型的情况.
在泛型方法中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。
类型参数命名准则:
- 尽量使用描述性名称命名,除非单个字母完全可以让人理解它表示的含义。
- 考虑使用T作为单个字母类型参数的类型的类型参数。
- 务必将T 作为泛型参数描述性的前缀。如TSession
- 考虑在参数名中指示对此类型参数的约束。例如,可以将带有 ISession 约束的参数命名为 TSession。
类型参数的约束
T:结构 类型参数必须是值类型,可以指定除Nullable以外的任何值类型。
public class MyClass2<T> where T : struct
//这个泛型类只接受值类型的泛型参数
T:类 类型 参数必须是引用类型,包括任何类,接口。委托。数组类型。
public class MyClass<T> where T:class//这个泛型类只接受引用类型的泛型参数
T:NEW() 类型参数必须具有无参数的公共构造函数,当与其他约束一起使用时,new()约
束必须最后指定。
public class MyClass3<T> where T : new()
T:<基类名>:类型参数必须是指定的基类或派生自指定的基类。
public class MyClass5<T>where T : Customer
T:<接口名称,类型参数必须是指定的接口或者实现指定的接口。可以指定多个接口约束,约束接口也可以是泛型的>
public class MyClass4<T> where T : System.IComparable
T:U。。。。为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。
在指定一个类型参数时,可以指定类型参数必须满足的约束条件。
这是通过在指定类型参数时使用where子句来实现的。
class
class-name<type-param> where type-param:constraints{}
在泛型中,要为某个使用泛型的变量初始化值,
可是我们需要考虑的是这个泛型可能是引用类型,也可能是值类型,这时我们可以借助default来完成初始化复制。
T value = default(T);如果T是引用类型,value = null,如果是T是值类型,value = 0.
泛型类
class Test<T> { public T obj; public Test(T obj) { this.obj = obj; } }
子类继承泛型类时必须明确指定泛型参数的类型
当子类也是泛型参数是,要注意约束必须和父类的匹配
class C<U,V> class D:C<string,int> class E<U,V>:C<U,V> class F<U,V>:C<string,int>
泛型接口
//定义泛型接口 interface Icomuter<T> { //定义泛型方法 T Add(T item1, T item2); T Substract(T item1, T item2); } //继承泛型接口 class Student : Icomuter<int> { //继承实现泛型方法 public int Add(int item1, int item2) { return item1 + item2; } public int Substract(int item1, int item2) { return item1 - item2; } }
public interface IList< T> { T[] GetElements(); } public interface IDictionary< K,V> { void Add(K key, V value); } // 泛型接口的类型参数要么已实例化 // 要么来源于实现类声明的类型参数 class List< T> : IList< T>, IDictionary< int, T> { public T[] GetElements() { return null; } public void Add(int index, T value) {} }
泛型方法
泛型方法是使用类型参数声明的方法
static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } //方法的调用 int a = 1, b = 2; Swap<int>(ref a, ref b); System.Console.WriteLine(a + " " + b); //省略类型参数,编译器将推断出该参数 Swap(ref a, ref b); //相同的类型推断规则也适用于静态方法以及实例方法。编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数
在泛型类中,非泛型方法可以访问类级别类型参数,如下所示:
class SampleClass<T> { void Swap(ref T lhs, ref T rhs) { } }
泛型方法是使用类型参数声明的方法,如下所示:
static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } public static void TestSwap() { int a = 1; int b = 2; Swap<int>(ref a, ref b); System.Console.WriteLine(a + " " + b); }
也可以省略类型参数,编译器将推断出该参数。下面对 Swap 的调用等效于前面的调用:
Swap(ref a, ref b);
相同的类型推断规则也适用于静态方法以及实例方法。编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数。因此,类型推断不适用于没有参数的方法。类型推断在编译时、编译器尝试解析任何重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推断逻辑。在重载解析步骤中,编译器仅包括类型推断取得成功的那些泛型方法。
在泛型类中,非泛型方法可以访问类级别类型参数,如下所示:
class SampleClass<T> { void Swap(ref T lhs, ref T rhs) { } }
如果定义的泛型方法接受与包含类相同的类型参数,编译器将生成警告 CS0693,因为在方法范围内,为内部 T 提供的参数将隐藏为外部 T 提供的参数。除了类初始化时提供的类型参数之外,如果需要灵活调用具有类型参数的泛型类方法,请考虑为方法的类型参数提供其他标识符,如下面示例中的GenericList2<T> 所示。
class GenericList<T> { // CS0693 void SampleMethod<T>() { } } class GenericList2<T> { //No warning void SampleMethod<U>() { } }
使用约束对方法中的类型参数启用更专门的操作。此版本的 Swap<T> 现在称为 SwapIfGreater<T>,它只能与实现 IComparable<T> 的类型参数一起使用
void SwapIfGreater<T>(ref T lhs, ref T rhs) where T : System.IComparable<T> { T temp; if (lhs.CompareTo(rhs) > 0) { temp = lhs; lhs = rhs; rhs = temp; } }
在 C# 2.0 中,下限为零的一维数组自动实现 IList<T>。这使您可以创建能够使用相同代码循环访问数组和其他集合类型的泛型方法。此技术主要对读取集合中的数据很有用。IList<T> 接口不能用于在数组中添加或移除元素;如果试图在此上下文中调用 IList<T> 方法(如数组的 RemoveAt),将引发异常。
//下面的代码示例演示带有 IList<T> 输入参数的单个泛型方法如何同时循环访问列表和数组,本例中为整数数组。 class Program { static void Main() { int[] arr = { 0, 1, 2, 3, 4 }; List<int> list = new List<int>(); for (int x = 5; x < 10; x++) { list.Add(x); } ProcessItems<int>(arr); ProcessItems<int>(list); } static void ProcessItems<T>(IList<T> coll) { foreach (T item in coll) { System.Console.Write(item.ToString() + " "); } System.Console.WriteLine(); } } //尽管 ProcessItems 方法无法添加或移除项,但对于 ProcessItems 内部的 T[],IsReadOnly 属性返回 False,因为该数组本身未声明 ReadOnly 特性。
委托 可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,就像实例化泛型类或调用泛型方法一样,如下例所示:
public delegate void Del<T>(T item); public static void Notify(int i) { } Del<int> m1 = new Del<int>(Notify);
C# 2.0 版具有称为方法组转换的新功能,此功能适用于具体委托类型和泛型委托类型,并使您可以使用如下简化的语法写入上一行:
Del<int> m2 = Notify;
在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。
class Stack<T> { T[] items; int index; public delegate void StackDelegate(T[] items); }
引用委托的代码必须指定包含类的类型变量,如下所示:
private static void DoWork(float[] items) { } public static void TestStack() { Stack<float> s = new Stack<float>(); Stack<float>.StackDelegate d = DoWork; }
根据典型设计模式定义事件时,泛型委托尤其有用,因为发送方参数可以为强类型,不再需要强制转换成 Object,或反向强制转换。
delegate void StackEventHandler<T, U>(T sender, U eventArgs); class Stack<T> { public class StackEventArgs : System.EventArgs { } public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent; protected virtual void OnStackChanged(StackEventArgs a) { stackEvent(this, a); } } class SampleClass { public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { } } public static void Test() { Stack<double> s = new Stack<double>(); SampleClass o = new SampleClass(); s.stackEvent += o.HandleStackChange; }
default:
之所以会用到default关键字,是因为需要在不知道类型参数为值类型还是引用类型的情况下,为对象实例赋初值。
在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:
-
T 是引用类型还是值类型。
如果 T 为值类型,则它是数值还是结构