1  /  1  页   1 跳转 查看:1884

C#泛型秘诀

C#泛型秘诀

原文出处: http://www.cnblogs.com/abatei/archive/2008/02/03/1063282.html

C#泛型秘诀(1)

本系列文章翻译O'Reilly 出版的《C# Cookbook》一书中的片段,仅供学习交流使用
 
4.0 介绍
泛型,一个期待已久的功能,随着C# 2.0版本编译器的到来最终出现。泛型是一个非常有用的功能,它使得您的代码变得精简而富有效率。这些将在秘诀4.1进行详细讲述。泛型的到来使得您可以编写更为强大的应用程序,但这需要正确地使用它。如果您考虑把ArrayListQueueStackHashtable对象转变为使用相应的泛型版本,可以阅读秘诀4.44.54.10。当您阅读过后,会发现这种转变不一定简单,甚至有可能会不再打算进行转变。

本章的另外一些秘诀涉及到.NET Framework 2.0所包含的其他泛型类,如秘诀4.6。其他秘诀讲述一些泛型类的操作,如秘诀4.24.84.13

 
4.1决定在何时何地使用泛型
问题
您希望在一个新工程内使用泛型,或者想把已有项目中的非泛型类转换为等价的泛型版本。但您并非了解为何要这样做,也不知道哪个非泛型类应该被转换为泛型类。
解决方案
决定在何时何地使用泛型,您需要考虑以下几件事件:
l
您所使用的类型是否包含或操作未指定的数据类型(如集合类型)?如果是这样,如果是这样,创建泛型类型将能提供更多的好处。如果您的类型只操作单一的指定类型,那么就没有必要去创建一个泛型类。
l
如果您的类型将操作值类型,那么就会产生装箱和拆箱操作,就应该考虑使用泛型来防止装箱和拆箱操作。
l
泛型的强类型检查有助于快速查找错误(也就是编译期而非运行期),从而缩短bug修复周期。
l
在编写多个类操作多个数据类型时是否遭遇到“代码膨胀”问题(如一个ArrayList只存储StreamReaders而另一个存储StreamWriters)?其实编写一次代码并让它工作于多个数据类型非常简单。
l
泛型使得代码更为清晰。通过消除代码膨胀并进行强制检查,您的代码将变得更易于阅读和理解。
讨论
很多时候,使用泛型类型将使您受益。泛型将使得代码重用更有效率,具有更快的执行速度,进行强制类型检查,获得更易读的代码。
阅读参考
MSDN文档中的“Generics Overview”和“Benefits of Generics”主题。


4.2 理解泛型类型
问题
您需要理解泛型类型在.NET中是如何工作的,它跟一般的.NET类型有什么不同。
解决方案
几个小实验就可以演示一般类型和泛型类型之间的区别。例4-1中的StandardClass类就是一个般类型。
4-1 StandardClass:一般的.NET类型


public
class StandardClass

   
static
int _count =
0; //StandardClass类的对象的表态计数器

int _maxItemCount; //项数目的最大值

object[] _items; //保存项的数组

int _currentItem =
0; //当前项数目

public StandardClass(int items) //构造函数

{
        _count
++; //对象数目加
        _maxItemCount = items;
        _items
=
new
object[_maxItemCount]; //数组初始化
    }
   
//用于添加项,为了适用于任何类型,使用了object类型

public
int AddItem(object item)
   
{
       
if (_currentItem < _maxItemCount)
       
{
            _items[_currentItem]
= item;
           
return _currentItem++; //返回添加的项的索引
        }
       
else
           
throw
new Exception("Item queue is full");
    }

   
//用于从类中取得指定项

public
object GetItem(int index)
   
{
        Debug.Assert(index
< _maxItemCount); //设置断言

if (index >= _maxItemCount)
           
throw
new ArgumentOutOfRangeException("index");
       
return _items[index]; //返回指定项
    }
   
public
int ItemCount //属性,指示当前项数目

{
       
get
{ return _currentItem; }
    }

   
public
override
string ToString( )
   
//重载ToString方法,用于介绍类的情况

return
"There are "
+ _count.ToString( ) +
           
" instances of "
+
this.GetType( ).ToString( ) +
           
" which contains "
+ _currentItem +
" items of type "
+
            _items.GetType( ).ToString( )
+
"";
    }

}



 
StandardClass类有一个整型静态成员变量_count,用于在实例构造器中计数。重载的ToString()方法打印在这个应用程序域中StandardClass类实例的数目。StandardClass类还包括一个object数组(_item),它的长度由构造方法中的传递的参数来决定。它实现了添加和获得项的方法(AddItemGetItem),还有一个只读属性来获取数组中的项的数目(ItemCount)。
GenericClass<T>类是一个泛型类型,同样有静态成员变量_count,实例构造器中对实例数目进行计算,重载的ToString()方法告诉您有多少GenericClass<T>类的实例存在。GenericClass<T>也有一个_itmes数组和StandardClass类中的相应方法,请参考例4-2
Example4-2 GenericClass<T>:泛型类


public
class GenericClass<T>
{
   
static
int _count =
0;
   
int _maxItemCount;
    T[] _items;
   
int _currentItem =
0;
   
public GenericClass(int items)
   
{
        _count
++;
        _ _maxItemCount
= items;
        _items
=
new T[_maxItemCount];
    }

   
public
int AddItem(T item)
   
{
       
if (_currentItem < _maxItemCount)
       
{
            _items[_currentItem]
= item;
           
return _currentItem++;
        }

       
else
           
throw
new Exception("Item queue is full");
    }

   
public T GetItem(int index)
   
{
        Debug.Assert(index
< _maxItemCount);
       
if (index >= _maxItemCount)
           
throw
new ArgumentOutOfRangeException("index");
       
return _items[index];
    }

   
public
int ItemCount
   
{
       
get
{ return _currentItem; }
    }

   
public
override
string ToString()
   
{
       
return
"There are "
+ _count.ToString() +
         
" instances of "
+
this.GetType().ToString() +
         
" which contains "
+ _currentItem +
" items of type "
+
          _items.GetType().ToString()
+
"";
    }

}



 
从GenericClass<T>中的少许不同点开始,看看_items数组的声明。它声明为:
T[] _items;
而不是
object[] _items;
_items数组使用泛型类(<T>)做为类型参数以决定在_itmes数组中接收哪种类型的项。StandarClass_itmes数组中使用Objcec以使得所有类型都可以做为项存储在数组中(因为所有类型都继承自object)。而GenericClass<T>通过使用类型参数指示允许使用的对象类型来提供类型安全。
下一个不同在于AddItemGetItem方法的声明。AddItem现在使用一个类型T做为参数,而在StandardClass中使用object类型做为参数。GetItem现在的返回值类型TStandardClass返回值为object类型。这个改变允许GenericClass<T>中的方法在数组中存储和获得具体的类型而非StandardClass中的允许存储所有的object类型。

public
int AddItem(T item)
   
{
       
if (_currentItem < _maxItemCount)
       
{
            _items[_currentItem]
= item;
           
return _currentItem++;
        }

       
else
           
throw
new Exception("Item queue is full");
    }

   
public T GetItem(int index)
   
{
        Debug.Assert(index
< _maxItemCount);
       
if (index >= _maxItemCount)
           
throw
new ArgumentOutOfRangeException("index");
       
return _items[index];
    }



 
这样做的优势在于,首先通过GenericClass<T>为数组中的项提供了类型安全。在StandardClass中可能会这样写代码:



// 一般类
    StandardClass C =
new StandardClass(5);
    Console.WriteLine(C);
   
string s1 =
"s1";
   
string s2 =
"s2";
   
string s3 =
"s3";
   
int i1 =
1;
   
// 在一般类中以object的形式添加项
    C.AddItem(s1);
    C.AddItem(s2);
    C.AddItem(s3);
   
// 在字符串数组中添加一个整数,也被允许
C.AddItem(i1);
但在GenericClass
<T>中做同样的事情将导致编译错误:
   
// 泛型类
    GenericClass<string> gC =
new GenericClass<string>(5);
    Console.WriteLine(gC);
   
string s1 =
"s1";
   
string s2 =
"s2";
   
string s3 =
"s3";
   
int i1 =
1;
   
// 把字符串添加进泛型类.
    gC.AddItem(s1);
    gC.AddItem(s2);
    gC.AddItem(s3);
   
// 尝试在字符串实例中添加整数,将被编译器拒绝
   
// error CS1503: Argument '1': cannot convert from 'int' to 'string'
//GC.AddItem(i1);


 
编译器防止它成为运行时源码的bug,这是一件非常美妙的事情。
虽然并非显而易见,但在StandardClass中把整数添加进object数组会导致装箱操作,这一点可以StandardClass调用GetItem方法时的IL代码:

IL_0170: ldloc.2



IL_0171: ldloc.s i1



IL_0173: box [mscorlib]System.Int32



IL_0178: callvirt instance int32

CSharpRecipes.Generics/StandardClass::AddItem(object)


 
这个装箱操作把做为值类型的整数转换为引用类型(object),从而可以在数组中存储。这导致了在object数组中存储值类型时需要增加额外的工作。
当您在运行StandardClass并从类中返回一个项时,还会产生一个问题,来看看StandardClass.GetItem如何返回一个项:



// 存储返回的字符串.



string sHolder;



// 发生错误CS0266:



// Cannot implicitly convert type 'object' to 'string'…



sHolder = (string)C.GetItem(1);



因为StandardClass.GetItem返回的是object类型,而您希望通过索引1获得一个字符串类型,所以需要把它转换为字符串类型。然而它有可能并非字符串-----只能确定它是一个object-----但为了赋值正确,您不得不把它转换为更明确的类型。字符串比较特殊,所有对象都可以自行提供一个字符串描述,但当数组接收一个double类型并把它赋给一个布尔类型就会出问题。
这两个问题在GenericClass<T>中被全部解决。无需再进行拆箱,因为GetItem所返回的是一个具体类型,编译器会检查返回值以强近它执行。


string sHolder;



int iHolder;



// 不需要再进行转换



sHolder = gC.GetItem(1);



// 尝试把字符串变为整数.将出现



// 错误CS0029: Cannot implicitly convert type 'string' to 'int'


//iHolder = gC.GetItem(1);
为了了解两种类型的其他不同点,分别给出它们的示例代码:


// 一般类
    StandardClass A =
new StandardClass();
    Console.WriteLine(A);
    StandardClass B
=
new StandardClass();
    Console.WriteLine(B);
    StandardClass C
=
new StandardClass();
    Console.WriteLine(C);
   
// 泛型类
    GenericClass<bool> gA =
new GenericClass<bool>();
    Console.WriteLine(gA);
    GenericClass
<int> gB =
new GenericClass<int>();
    Console.WriteLine(gB);
    GenericClass
<string> gC =
new GenericClass<string>();
    Console.WriteLine(gC);
    GenericClass
<string> gD =
new GenericClass<string>();
    Console.WriteLine(gD);


 
上述代码输出结果如下:

There are 1 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...


There are 2 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...


There are 3 instances of CSharpRecipes.Generics+StandardClass which contains 0 items of type System.Object[]...


There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Boolean] which contains 0 items of type System.Boolean[]...


There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.Int32] which contains 0 items of type System.Int32[]...


There are 1 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...


There are 2 instances of CSharpRecipes.Generics+GenericClass`1[System.String] which contains 0 items of type System.String[]...



讨论
泛型中的类型参数允许您在不知道使用何种类型的情况下提供类型安全的代码。在很多场合下,您希望类型具有某些指定的特征,这可以通过使用类型约束(秘诀4.12)来实现。方法在类本身不是泛型的情况下也可以拥有泛型类型的参数。秘诀4.9为此演示了一个例子。
注意当StandardClass拥有三个实例,GenericClass有一个声明为<bool>类型的实例,一个声明为<int>类型的实例,两个声明为<string>类型的实例。这意味着所有非泛型类都创建同一.NET类型对象,而所有泛型类都为指定类型实例创建自己的.NET类型对象。
示例代码中的StandardClass类有三个实例,因为CLR中只维护一个StandardClass类型。而在泛型中,每种类型都被相应的类型模板所维护,当创建一个类型实例时,类型实参被传入。说得更清楚一些就是为GenericClass<bool>产生一个类型,为GenericClass<int>产生一个类型,为GenericClass<string>产生第三个类型。
内部静态成员_count可以帮助说明这一点,一个类的静态成员实际上是跟CLR中的类型相连的。CLR对于给定的类型只会创建一次,并维护它一直到应用程序域卸载。这也是为什么在调用ToString方法时,输出显示有StandardClass的三个实例,而GenericClass<T>类型有12个实例。


阅读参考
MSDN文档中的“Generic Type Parameters”和“Generic Classes”主题。
 

C#泛型秘诀(2)

4.3 获取泛型的类型
问题
您需要在运行时获得一个泛型类型实例的Type对象。
解决方案
在使用typeof操作符时提供类型参数;使用类型参数实例化的泛型类型,用GetType()方法。
声明一个一般类型和一个泛型类型如下:

public
class Simple
{
   
public Simple()
   
{
    }

}


public
class SimpleGeneric<T>
{
   
public SimpleGeneric()
   
{
    }

}



  使用typeof操作符和简单类型的名称就可以在运行时获得简单类型的类型。对于泛型类型来说,在调用typeof时类型参数必须要提供,但是简单类型实例和泛型类型实例都可以使用相同的方式来调用GetType()

Simple s =
new Simple();
    Type t
=
typeof(Simple);
    Type alsoT
= s.GetType();
   
//提供类型参数就才可以获得类型实例
    Type gtInt =
typeof(SimpleGeneric<int>);
    Type gtBool
=
typeof(SimpleGeneric<bool>);
    Type gtString
=
typeof(SimpleGeneric<string>);
   
// 当有一个泛型类实例时,您也可以使用GetType的旧的方式去调用一个实例。.
    SimpleGeneric<int> sgI =
new SimpleGeneric<int>();
Type alsoGT
= sgI.GetType();


  讨论
不能直接获取泛型类的类型,因为如果不提供一个类型参数,泛型类将没有类型(参考秘诀4.2获得更多信息)。只有通过类型参数实例化的泛型类才有Type
如果您在使用typeof操作符时,只提供泛型类型定义而不提供类型参数,将得到下面的错误:
// 这产生一个错误:
   
// Error 26 Using the generic type 'CSharpRecipes.Generics.SimpleGeneric<T>'
   
// requires '1' type arguments
    Type gt =
typeof(SimpleGeneric);


  阅读参考
查看秘诀4.2;参考MSDN文档中的“typeof”主题。

4.4 使用相应的泛型版本替换ArrayList
问题
您希望通过将所有ArrayList对象替换为相应的泛型版本以提高应用程序的效率,并使得代码更易于使用。当结构体或其他值类型存储在这些数据结构中时,会导致装箱/拆箱操作,这时就需要这么做。
解决方案
使用更有效率的泛型类System.Collections.Generic.List来替换已存在的System.Collection.ArrayList类。
下面是使用System.Collection.ArrayList对象的简单例子:

public
static
void UseNonGenericArrayList()
   
{
       
// 创建一个ArrayList.
        ArrayList numbers =
new ArrayList();
        numbers.Add(
1); // 导致装箱操作
        numbers.Add(2); // 导致装箱操作
       
// 显示ArrayList内的所有整数
       
// 每次迭代都导致拆箱操作

foreach (int i in numbers)
       
{
            Console.WriteLine(i);
        }

        numbers.Clear();
}


  相同的代码使用了System.Collections.Generic.List对象
public
static
void UseGenericList()
   
{
       
// 创建一个List.
        List<int> numbers =
new List<int>();
        numbers.Add(
1);
        numbers.Add(
2);
       
// 显示List中的所有整数.

foreach (int i in numbers)
       
{
            Console.WriteLine(i);
        }

        numbers.Clear();
}


  讨论
因为所有的应用程序几乎都会使用ArrayList,从提升您的应用程序的执行效率开始是一个不错的选择。对于应用程序中简单使用ArrayList的地方来说,这种替代是非常容易的。但有些地方需要注意,例如,泛型List类未实现Icloneable接口而ArrayList实现了它。
4-1显示了两个类中的等价成员。

ArrayList类成员


等价的泛型List类成员


Capacity 属性


Capacity属性


Count属性


Count属性


IsFixedSize属性


((IList)myList).IsFixedSize


IsReadOnly属性


((IList)myList).IsReadOnly


IsSynchronized属性


((IList)myList).IsSynchronized


Item属性


Item属性


SyncRoot属性


((IList)myList).SyncRoot


Adapter 静态方法


N/A


Add 方法


Add方法


AddRange方法


AddRange方法


N/A


AsReadOnly方法


BinarySearch方法


BinarySearch方法


Clear方法


Clear方法


Clone方法


Getrange(0, numbers.Count)


Contains方法


Contains方法


N/A


ConvertAll方法


CopyTo方法


CopyTo方法


N/A


Exists方法


N/A


Find方法


N/A


FindAll方法


N/A


FindIndex方法


N/A


FindLast方法


N/A


FindLastIndex方法


N/A


ForEach方法


FixedSize 静态方法