C# 泛型介绍(三)

 

泛型和 .NET Framework

为了对本文做一下小结,下面介绍 .NET 中除 C# 本身以外的其他一些领域如何利用泛型或者与泛型交互。

System.Array 和泛型

System.Array 类型通过很多一般静态方法进行了扩展。这些一般静态方法专门用于自动执行和简化处理数组的常见任务,例如,遍历数组并且对每个元素执行操作、扫描数组,以查找匹配某个条件(谓词)的值、对数组进行变换和排序等等。代码块 11 是这些静态方法的部分清单。

代码块 11. System.Array 的一般方法

public abstract class Array
{
   //Partial listing of the static methods:  
   public static IList AsReadOnly(T[] array);
   public static int BinarySearch(T[] array, T value);
   public static int BinarySearch(T[] array, T value,
                                     IComparer comparer);
   public static U[] ConvertAll(T[] array, 
                                Converter converter);
   public static bool Exists(T[] array,Predicate match);
   public static T Find(T[] array,Predicate match);
   public static T[] FindAll(T[] array, Predicate match);
   public static int FindIndex(T[] array, Predicate match);
   public static void ForEach(T[] array, Action action);
   public static int  IndexOf(T[] array, T value);
   public static void Sort(K[] keys, V[] items,
                                IComparer comparer);
   public static void Sort(T[] array,Comparison comparison) 
}

System.Array 的静态一般方法都使用 System 命名空间中定义的下列四个一般委托:

public delegate void Action(T t);
public delegate int Comparison(T x, T y);
public delegate U Converter(T from);
public delegate bool Predicate(T t);

代码块 12 演示如何使用这些一般方法和委托。它用从 1 到 20 的所有整数初始化一个数组。然后,代码通过一个匿名方法和 Action 委托,使用 Array.ForEach() 方法来跟踪这些数字。使用第二个匿名方法和 Predicate 委托,代码通过调用 Array.FindAll() 方法(它返回另一个相同的一般类型的数组),来查找该数组中的所有质数。最后,使用相同的 Action 委托和匿名方法来跟踪这些质数。请注意代码块 12 中类型参数推理的用法。您在使用静态方法时无须指定类型参数。

代码块 12. 使用 System.Array 的一般方法

int[] numbers = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
Action trace =  delegate(int number)
                     {
                        Trace.WriteLine(number);
                     };
Predicate isPrime =   delegate(int number)
                           {
                              switch(number)
                              {
                                case 1:case 2:case 3:case 5:case 7:                                 
                                case 11:case 13:case 17:case 19:
                                    return true;
                                 default:
                                    return false;
                              }
                           };
Array.ForEach(numbers,trace);
int[] primes = Array.FindAll(numbers,isPrime);
Array.ForEach(primes,trace);

System.Collections.Generic 命名空间中定义的类 List 中,也可以得到类似的一般方法。这些方法使用四个相同的一般委托。实际上,您还可以在您的代码中利用这些委托,如以下部分所示。

静态集合类

尽管 System.ArrayList 都提供了能够大大简化自身使用方式的、方便的实用工具方法,但 .NET 没有为其他集合提供这样的支持。为了对此进行补偿,本文随附的源代码包含了静态 Helper 类 Collection,其定义如下所示:

public static class Collection
{
   public static IList AsReadOnly(IEnumerable collection);
   public static U[] ConvertAll(IEnumerable collection,
                                     Converter converter);
   public static bool Contains(IEnumerable collection,T item) 
                                               where T : IComparable;
   public static bool Exists(IEnumerable collection,Predicate); 
   public static T Find(IEnumerable collection,Predicate match);
   public static T[] FindAll(IEnumerable collection,
                                Predicate match);
   public static int FindIndex(IEnumerable collection,T value) 
                                                where T : IComparable;
   public static T FindLast(IEnumerable collection,
                               Predicate match);
   public static int FindLastIndex(IEnumerable collection,T value) 
                                                where T : IComparable;
   public static void ForEach(IEnumerable collection,Action action);
   public static T[] Reverse(IEnumerable collection);
   public static T[] Sort(IEnumerable collection);
   public static T[] ToArray(IEnumerable collection);
   public static bool TrueForAll(IEnumerable collection,
                                    Predicate match);
   //Overloaded versions for IEnumerator 
}

Collection 的实现简单易懂。例如,以下为 ForEach() 方法:

public static void ForEach(IEnumerator iterator,Action action)
{
   /* Some parameter checking here, then: */
   while(iterator.MoveNext())
   {
      action(iterator.Current);
   }
}

Collection 静态类的用法非常类似于 ArrayList,它们都利用相同的一般委托。您可以将 Collection 用于任何集合,只要该集合支持 IEnumerableIEnumerator

Queue queue = new Queue();
//Some code to initialize queue
Action trace =  delegate(int number)
                     {
                        Trace.WriteLine(number);
                     };
Collection.ForEach(queue,trace);

一般集合

System.Collections 中的数据结构全部都是基于 Object 的,因而继承了本文开头描述的两个问题,即性能较差和缺少类型安全。.NET 2.0 在 System.Collections.Generic 命名空间中引入了一组一般集合。例如,有一般的 Stack 类和一般的 Queue 类。Dictionary 数据结构等效于非一般的 HashTable,并且还有一个有点像 SortedListSortedDictionary 类。类 List 类似于非一般的 ArrayList。表 1 将 System.Collections.Generic 的主要类型映射到 System.Collections 中的那些主要类型。

表 1. 将 System.Collections.Generic 映射到 System.Collections
System.Collections.Generic System.Collections

Comparer

Comparer

Dictionary

HashTable

LinkedList

-

List

ArrayList

Queue

Queue

SortedDictionary

SortedList

Stack

Stack

ICollection

ICollection

IComparable

System.IComparable

IDictionary

IDictionary

IEnumerable

IEnumerable

IEnumerator

IEnumerator

IList

IList

System.Collections.Generic 中的所有一般集合还实现了一般的 IEnumerable 接口,该接口的定义如下所示:

public interface IEnumerable
{
   IEnumerator GetEnumerator();
} 
public interface IEnumerator : IDisposable
{
   T Current{get;}
   bool MoveNext();
}
      

简单说来,IEnumerable 提供了对 IEnumerator 迭代器接口的访问,该接口用于对集合进行抽象迭代。所有集合都在嵌套结构上实现了 IEnumerable,其中,一般类型参数 T 是集合存储的类型。

特别有趣的是,词典集合定义它们的迭代器的方式。词典实际上是两个类型(而非一个类型)的一般参数(键和值)集合。System.Collection.Generic 提供了一个名为 KeyValuePair 的一般结构,其定义如下所示:

struct KeyValuePair
{
   public KeyValuePair(K key,V value);
   public K Key(get;set;)
   public V Value(get;set;)
}

KeyValuePair 简单地存储一般键和一般值组成的对。该结构就是词典作为集合进行管理的类型,并且是它用于实现它的 IEnumerable 的类型。Dictionary 类将一般 KeyValuePair 结构指定为 IEnumerableICollection 的项参数:

public class Dictionary : IEnumerable<KEYVALUEPAIR>, 
                               ICollection<KEYVALUEPAIR>, 
                               //More interfaces
{...} 

KeyValuePair 中使用的键和值类型参数当然是词典自己的一般键和值类型参数。您无疑可以在您自己的使用键和值对的一般数据结构中完成同样的工作。例如:

public class LinkedList : IEnumerable<KEYVALUEPAIR> where K : IComparable
{...} 

序列化和泛型

.NET 允许您具有可序列化的一般类型:

[Serializable]
public class MyClass
{...}

在序列化类型时,除了持久保持对象成员的状态以外,.NET 还持久保持有关该对象及其类型的元数据。如果可序列化的类型是一般类型并且它包含绑定类型,则有关该一般类型的元数据还包含有关该绑定类型的类型信息。因此,一般类型的每个带有特定参数类型的变形都被视为唯一的类型。例如,您不能将对象类型 MyClass 序列化(而只能将其反序列化)为 MyClass 类型的对象。序列化一般类型的实例与序列化非一般类型没有什么不同。但是,在反序列化该类型时,您需要通过匹配的特定类型声明变量,并且在向下强制转换从 Deserialize 返回的 Object 时再次指定这些类型。代码块 13 显示了一般类型的序列化和反序列化。

代码块 13. 一般类型的客户端序列化

[Serializable]
public class MyClass
{...}
MyClass obj1 = new MyClass();

IFormatter formatter = new BinaryFormatter();?
Stream stream = new FileStream("obj.bin",FileMode.Create,FileAccess.ReadWrite); 
using(stream)
{
   formatter.Serialize(stream,obj1);
   stream.Seek(0,SeekOrigin.Begin);

   MyClass obj2; 
   obj2 = (MyClass)formatter.Deserialize(stream);
}

请注意,IFormatter 是基于对象的。您可以通过定义 IFormatter 的一般版本进行补偿:

public interface IGenericFormatter
{
   T Deserialize(Stream serializationStream);
   void Serialize(Stream serializationStream,T graph);
}

您可以通过包含一个基于对象的格式化程序来实现 IGenericFormatter

public class GenericFormatter : IGenericFormatter 
                                   where F : IFormatter,new()
{
   IFormatter m_Formatter = new F();
   public T Deserialize(Stream serializationStream)
   {
      return (T)m_Formatter.Deserialize(serializationStream);
   }
   public void Serialize(Stream serializationStream,T graph)
   {
      m_Formatter.Serialize(serializationStream,graph);
   }
}

请注意一般类型参数 F 上的两个约束的用法。尽管可以原样使用 GenericFormatter<F>

   
using GenericBinaryFormatter = GenericFormatter;
using GenericSoapFormatter2  = GenericFormatter;

但是,您还可以将该格式化程序强类型化以使用:

public sealed class GenericBinaryFormatter : GenericFormatter
{}
public sealed class GenericSoapFormatter : GenericFormatter
{}

强类型化定义的优点是可以跨文件和程序集共享它,这与 using 别名相反。

代码块 14代码块 13 相同,唯一的不同之处在于它使用一般格式化程序:

代码块 14. 使用 IGenericFormatter

[Serializable]
public class MyClass
{...}
MyClass obj1 = new MyClass();

IGenericFormatter formatter = new GenericBinaryFormatter();?
Stream stream = new FileStream("obj.bin",FileMode.Create,FileAccess.ReadWrite); 
using(stream)
{
   formatter.Serialize(stream,obj1);
   stream.Seek(0,SeekOrigin.Begin);
   MyClass obj2; 
   obj2 = formatter.Deserialize(stream);
}

泛型和远程处理

可以定义和部署利用泛型的远程类,并且可以使用编程或管理配置。请考虑使用泛型并且派生自 MarshalByRefObject 的类 MyServer

public class MyServer : MarshalByRefObject
{...}

只有当类型参数 T 是可封送的对象时,您才能通过远程处理访问该类。这意味着 T 是可序列化的类型或者派生自 MarshalByRefObject。您可以通过将 T 约束为派生自 MarshalByRefObject 来实施这一要求。

public class MyServer : MarshalByRefObject 
                           where T : MarshalByRefObject
{...}

在使用管理类型注册时,您需要指定要取代一般类型参数而使用的确切类型实参。您必须以与语言无关的方式命名这些类型,并且提供完全限定命名空间。例如,假设类 MyServer 在命名空间 RemoteServer 中的程序集 ServerAssembly 中定义,并且您希望在客户端激活模式下将其与整型而不是一般类型参数 T 一起使用。在该情况下,配置文件中必需的客户端类型注册条目应该是:

<client url="...some url goes here..."> 
   <activated type="RemoteServer.MyServer<b>[[System.Int32]]</b>,ServerAssembly"/>
</client> 

配置文件中的匹配主机端类型注册条目是:

<service> 
   <activated type="RemoteServer.MyServer<b>[[System.Int32]]</b>,ServerAssembly"/> 
</service> 

双方括号用来指定多个类型。例如:

LinkedList[[System.Int32],[System.String]]

在使用编程配置时,您可以用类似于 C# 1.1 的方式配置激活模式和类型注册,不同之处在于,当定义远程对象的类型时,您必须提供类型实参而不是一般类型参数。例如,对于主机端激活模式和类型注册,您可以编写如下代码:

Type serverType = typeof(MyServer);
RemotingConfiguration.RegisterActivatedServiceType(serverType);

对于客户端类型激活模式和位置注册,具有以下代码:

Type serverType = typeof(MyServer);      
string url = ...; //some url initialization 
RemotingConfiguration.RegisterWellKnownClientType(serverType,url);

当实例化远程服务器时,只须提供类型参数,就好像您在使用本地一般类型一样:

MyServer obj;
obj = new MyServer();
//Use obj

除了使用 new 以外,客户端还可以选择使用 Activator 类的方法来连接到远程对象。在使用 Activator.GetObject() 时,您需要提供要使用的类型实参,并且在显式强制转换返回的 Object 时提供实参类型:

string url = ...; //some url initialization 
Type serverType = typeof(MyServer);
MyServer obj;
obj = (MyServer)Activator.GetObject(serverType,url);
//Use obj

您还可以将 Activator.CreateInstance() 与一般类型一起使用:

Type serverType = typeof(MyServer);
MyServer obj;
obj = (MyServer)Activator.CreateInstance(serverType);
//Use obj

实际上,Activator 还提供 CreateInstance() 的一般版本,定义如下:

T CreateInstance<T>();

CreateInstance() 的使用方式类似于非一般方法,只是添加了类型安全的好处:

Type serverType = typeof(MyServer);
MyServer obj;
obj = Activator.CreateInstance(serverType);
//Use obj

泛型无法完成的工作

在 .NET 2.0 下,您不能定义一般 Web 服务,即使用一般类型参数的 Web 方法。原因是没有哪个 Web 服务标准支持一般服务。

您还不能在服务组件上使用一般类型。原因是泛型不能满足 COM 可见性要求,而该要求对于服务组件而言是必需的(就像您无法在 COM 或 COM+ 中使用 C++ 模板一样)。

小结

C# 泛型是开发工具库中的一个无价之宝。它们可以提高性能、类型安全和质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管 C# 泛型的根基是 C++ 模板,但 C# 通过提供编译时安全和支持将泛型提高到了一个新水平。C# 利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C# 的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他 .NET Framework 领域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值