C#2.0学习笔记(一) 使用泛型

泛型

1)概述

基于Object的解决方案,会有性能问题(如装箱和拆箱),而且不是类型安全的。如果要解决这种问题,可能要写特定类型的数据结构,这样一来造成代码冗余,重用率不高,一个数据结构的变更要将所有类型的数据结构做相应的修改。于是引入了泛型。


2)什么是泛型?

通过泛型可以定义类型安全类,而不会损害类型安全、性能或工作效率。

public class Stack <T>
{
    T[] m_items;
    public void Push(T item){...}
    public T Pop(){...}
}

从字面上理解,泛型就是“广泛的类型”。定义时不知道类型,运行时确定。可以使用任何类型来声明和实例化,声明和实例化时都必须使用一个特定的类型来代替一般类型。如:

Static<int> statck = new Stack<int>();
stack.Push(1);
stack.Push(2);
int number = stock.Pop();

这样的优点在于,内部算法和数据操作保持不变,而实际数据类型可以在使用时指定。


泛型是如何实现的?

1)在.NET 2.0中,泛型在IL和CLR本身中具有本机支持。
2)编译泛型类时就像编译其他类一样,泛型仅保持一个占位符。
3)使用特定类型实例化泛型代码,编译时会将泛型替换为实例化的特定类型。

 

泛型的好处:
1)一次性开发、测试和部署代码,通过任何类型(包括将来的类型)来重用它。
2)编译器支持和类型安全。
3)不会强行对值类型进行装箱和拆箱,或者对引用类型进行向下强制类型转换,所以性能显著提高。值类型,性能通常会提高200%。引用类型,在访问该类型时可以预期性能最多提高100%(当然,整个应用程序的性能可能会提高,也可能不会提高)。

 

3)如何使用泛型?

符合CLR的语言都可以使用泛型。因为IL和CLR都为泛型提供本机支持。

轻量级的结构中也可以使用泛型

public struct Point<T>
{
    public T X;
    public T Y;
}

Point<int> point;
point.X = 1;
point.Y = 2;

Default关键字
如果不希望在堆栈为空时引发异常,而是希望返回堆栈中存储的类型的默认值(值类型返回0,引用类型返回null)。如果是基于object,则可以简单地返回null。

如:

public T Pop()
{
    m_StackPointer -;
    if(m_StackPointer >=0)
    {
        return m_item[m_StackPointer];
    }
    else
    {
       m_StackPointer =0;
       return default(T);
    }
}

 

单个类型可以定义多个泛型,如

class Node<K,T>
{
   public K Key;
   public T Item;
   public Node<K,T> NextNode;
   public Node()
   {
      Key = default(K);
      Item = default(T);
      NextNode = null;
   }
   public Node(K key,T item,Node<K,T> nextNode)
   {
      Key = key;
      Item = item;
      NextNode = nextNode;
   }
}

public class LinkedList<K,T>
{
   Node<K,T> m_head;
   public LinkedList()
   {
      m_head = new Node<K,T>();
   }
  
   public void AddHead(K key,T item)
   {
      Node<K,T> newNode = new Node<K,T>(key,item,m_Head.NextNode);
      m_head.NextNode = newNode;
   }

}

 

泛型别名

在文件头部使用using 为特定类型取别名。作用范围是整个文件。
如:

using List = LinkedList<int,string>;

class ListClient
{
    static void Main(string[] args)
   {
      List list = new List();
      list.AddHead(123,"AAAA");
   }

}

 


4)泛型约束

派生约束(Derivation Constraints)

where 关键字,如:

public class LinkedList<K,T> where K:IComparable

{...}


这样还是无法避免传入值类型的K所带来的装箱问题。System.Collections.Generic命名空间定义了泛型接口:Icomparable<T>:

public interface IComparable<T>
{
   int CompareTo(T other);
   bool Equals(T other);
}

在C#2.0中所有的派生约束必须放在类的实际派生列表之后,如:
public class LinkedList<K,T>:IEnumerable<T> where K:IComparable<K>
{...}

通常只需要在需要的级别定义约束。如在Node节点定义IComparable<K>约束没有意义。如果一定要在Node上定义IComparable<K>约束,则LinkedList上也要定义此约束。


一个泛型参数上约束多个接口(彼此之间用逗号分隔)。
public class LinkedList<K,T> where K:IComparable<K>,IConvertible
{...}

一个约束中最多只能使用一个基类,这是因为C#不支持多继承。

约束的基类下不能是密封类或静态类,并由编译器实施这一限制。

不能将System.Delegate或System.Array约束为基类。

可以同时约束一个基类以及一个或多个接口,但是该基类必须首先出现在派生约束列表中如:
public class LinkedList<K,T> where K:MyBaseClass,IComparable<K>
{...}

C#允许将另一个范型参数指定为约束:
public class MyClass<T,U> where T:U
{...}

定义自己的基类或接口进行泛型约束也是可以的。但自定义的接口或基类必须与泛型参数具有一致的可见性。即public/private。

 

构造函数约束

如果要在泛型类的内部实例化一个新的泛型对象,C#编译器不知道客户将使用的类型实参是否具有匹配的构造函数,因此它将拒绝编译实例化行。为解决该问题,C#允许约束泛型参数,使其必须支持公共默认构造函数。这是使用new()约束完成的。

class Node<K,T> where T:new()
{
   ...
}
可以将构造函数约束与派生约束组合,但是构造函数约束必须出现在约束列表中的最后。如:
public class LinkedList<K,T> where K:IComparable<K>,new()
{....}

 

引用/值类型约束
可以使用struct约束将泛型参数约束为值类型,如int\bool\enum或任何自定义结构。同样可使用class约束将泛型参数约束为引用类型(类)。如:

public class MyClass<T> where T:struct|class
{...}
注意
1)不能将引用/值类型约束与基类约束一起使用,因为基类约束涉及到类。
2)不能使用结构和默认构造函数约束,因为默认构造函数约束也涉及到类。
3)虽然可以使用类和默认构造函数约束,但这样做没有任何价值。
4)可以将引用/值类型约束与接口约束组合起来,前提是引用/值类型约束出现在约束列表的开头。

 

 

 


泛型和强制类型转换

C#编译器只允许将泛型参数隐式强制转换到Object或约束指定的类型。
C#编译器允许将泛型参数显示强制转换到其他任何接口,但不能将其转换到类,但是可以使用临时的Object变量,将泛型参数强制转换到其他任何类型。
强制类型转换是危险的,因为如果取代泛型参数而使用的类型实参不是派生自要显式强制转换到的类型,则会引发运行时异常。解决办法是使用is和as运算符。

 

继承和泛型

在从泛型基类派生时,可以提供类型实参而不是基类泛型参数。
public class BaseClass<T>
{...}
public class SubClass:BaseClass<int>
{...}
如果子类是泛型而不是具体的类型实参则可以使用子娄泛型参数作为泛型基类的指定类型

如:
public class SubClass<T>:BaseClass<T>

使用子类泛型参数时必须在子类级别重复基类级别规定的任何约束。

基类可以定义其签名使用泛型参数的虚拟方法。在重写它们时子类必须在方法签名中提供相应的类型。如果该子类是泛型类,则它还可以在重写时使用它自己的泛型参数。

我们可以定义泛型接口、泛型抽象类甚至泛型抽象方法。其行为和其它任何泛型基类型一样。

泛型抽象方法和泛型接口中有一种限制,C#2.0中不能对泛型参数使用+或者—+=之类的参数。但我们通过定义泛型操作,使用抽象方法(最好是接口)进行补偿。由于抽象方法的内部不能具有任何代码,可以在基类级别指定泛型操作,并且在子类级别提供具体的类型和实现。

泛型方法

C#2.0中方法可以定义特定于其执行范围的泛型参数。即使包含类不使用泛型,也可以定义方法特定的泛型参数。该功能仅适用于方法,对属性和索引器不能这样,只能使用类级别的泛型参数。
在调用定义了泛型参数的方法时可以根据要在调用场所使用的类型。
MyClass obj = new MyClass();
obj.MyMethod<int>(3);
因此在调用该方法时,C#编译器将足够聪明,基于传入参数的类型推断出正确的类型,并且它允许完全省略类型规范。即直接使用obj.MyThod(3);这称为泛型推理。

泛型推理不能只根据返回值的类型推断出类型。当方法定义它自己的泛型参数时,它还可以定义这些类型的约束。无法为类级别泛型参数提供方法级别约束。类级别泛型参数的所有约束都必须在类作用范围中定义。

在重写定义了泛型参数的虚拟方法时,子类方法必须重新定义该方法特定的泛型参数。子类不能重复在基础方法级别出现的约束。注意方法重写不能定义没有在基础方法中出现的新约束。

此外,如果子类方法调用虚拟方法的基类实现,则它必须指定要代替泛型基础方法类型参数使用的类型实参。您可以自己显式指定它,或者依靠类型推理。

C#允许定义使用泛型类型的静态方法。但在调用这样的静态方法时,需要在调用场所为所包含类提供具体的类型。
静态方法可以定义方法特定的泛型参数和约束,就像实例方法一样。在调用这样的方法时需要在调用场所提供方法特定的类型(或使用可能的类型推理)。
象实例方法一样可以为静态方法定义的泛型参数提供约束。C#中的运算符只是静态方法,并且C#允许您为自己的泛型重载运算符。

 


泛型委托

在某个类中定义的委托可以利用该类的泛型参数。
在为包含类指定类型时也会影响到委托。
C#2.0可以将方法引用的直接分配转变为委托变量。这称为委托推理。编译器能够推断出分配到其中的委托的类型,查明目标对象是否具有采用指定的名称的方法并且验证该方法的签名匹配。然后编译器创建所推断出的参数类型(包括正确的类型而不是泛型参数)的新委托,并将新委托分配到推断出的委托中。
象类、结构和方法一样,委托也可以定义泛型参数:
public class MyClass<T>
{
    public delegate void GenericDelegate<X>(T t,X x);
}
在类的作用范围外定义的委托可以使用泛型参数。在该情况下,声明和实例化委托时必须为其提供类型实参。
public delegate void GenericDelegate<T>(T t);

public class MyClass
{
    public void SomeMethod(int number){...}
}

MyClass obj = new MyClass();
GenericDelegate<int> del;
del = new GenericDelegate<int>(obj.SomeMethod);
del(3);

可以在分配委托时使用委托推理。
委托可以定义约束以伴随它的泛型参数。

委托级别的约束只在使用端实施(在声明委托变量和实例化委托对象时),类似于在类型或方法的作用范围中实施的其他任何约束。

泛型委托对于事件非常有用,可以精确地定义一组有限的泛型委托(只按照它们需要的泛型参数的数量进行区分),并且使用这些委托来满足所有事件处理需要)

 


泛型和反射

在.NET2.0中扩展了反射以支持泛型参数。类型Type现在可以表示带有特定类型实参(称为绑定类型)或未指定(未绑定)类型的泛型。象C#1.1中一样,可以通过typeof运算符或者通过调用每个类型支持的GetType()方法来获得任何类型的Type。

LinkedList<int,string> list = new LinkedList<int,string>();

Type type1 = typeof(LinkedList<int,string>);
Type type2 = list.GetType();
Debug.Assert(type1 == type2);

typeof和GetType()都可以对泛型参数进行操作。typeof运算符还可以对未绑定的泛型进行操作。如:
public class MyClass<T>
{}
Type unboundedType = typeof(MyClass<>);
Trace.WriteLine(unboundedType.ToString());
这里是“<>”的用法。要对带有多个类型参数的未绑定类型进行操作,需要在<>中使用“,”。


Type类中增加了新的方法和属性用于提供有关该类型的泛型方面的反射信息。

与Type类似,MethodInfo和它的基类MethodBase具有反射泛型方法信息的新成员。


属性和泛型:
C#2.0不允许定义泛型属性。属性类可以使用泛型类也可以使用定义泛型方法。

 

泛型集合

System.Collections中的数据结构都是基于Object的,基于Object的方案性能较差、缺少类型安全。.NET 2.0在System.Collections.Generic命名空间中引入了一组泛型集合。例如有泛型的Stack<T>类和泛型的Qeue<T>类。Dictionary<K,T>数据结构等效于非泛型的HashTable,并且还有一个有点象SortedList的SortedDictionary<K,T>类。类List<T>类似于非泛型的ArrayList。

 

泛型无法完成的工作

在.NET2.0下,不能定义泛型Web服务,即使用泛型类型参数的Web方法。因为没有哪个Web服务标准支持泛型服务。
不能在服务组件上使用泛型类型。原因是泛型不能满足COM可见性要求,而该要求对于服务组件而言是必需的。就像是无法在COM或COM+中使用C++模板一样。

 

posted @ 2008-01-17 16:17  一碗豆芽汤(OneV)  阅读(600)  评论(0编辑  收藏  举报