使用 ICollection<T> 实现 C# 泛型集合






4.53/5 (21投票s)
一篇解释如何使用 ICollection 实现 C# 泛型集合的文章,附带一个业务逻辑层示例。
引言
在构建三层 Web 应用程序的过程中,我发现需要一个泛型集合类,它能够以类型安全的方式存储我的业务逻辑层中的任何业务对象的集合。我已经编写了一个非泛型集合类(很可惜,每个业务对象都有一个),它继承自 CollectionBase
。随着越来越多的业务对象的需求出现,我立即停止了手中的工作,开始寻找更动态的解决方案。
我记得 C++ 有一种叫做模板类(template classes)的东西,它可以接受动态变量类型。在我看来,实现一个可枚举(即可以使用 `foreach` 语句进行遍历)的泛型集合类在 C# 中应该不难。我认为最好的方法是编写一个实现了泛型接口 `ICollection
经过一番搜索,我找到了一些不完整或/和无法正常工作的实现该接口的代码示例。我从所有找到的信息中学习到了我能学到的东西,并提出了一个我认为是合理、可行的 `ICollection
什么是泛型?
泛型是在 .NET 2.0 中首次引入 C# 语言的。它们提供了一种类型安全的方法来访问集合和/或函数调用中使用的数据。使用泛型可以显著减少由于类型转换或装箱/拆箱引起的运行时错误,因为泛型操作中使用的类型是在编译时进行评估的。如果您是 C++ 的爱好者,您会发现泛型与 C++ 中的模板类的工作方式非常相似,但也有一些区别。
C++ 模板与 C# 泛型有什么区别?
C++ 模板类和 C# 泛型之间的主要区别之一是,在 C# 中,您不能在泛型类中使用算术运算符(自定义运算符除外)。
C# 不允许类型参数具有默认类型,也不允许您使用类型参数(`T`)作为泛型类型的基类。
这些只是初步观察到的一些区别,但如果您熟悉 C++ 的话,注意到它们很重要。尽管 C++ 在这方面更灵活,但 C# 模型通过限制基于泛型约束的操作,可以减少运行时错误。
深入了解 ICollection<T>
在我们开始之前,让我们先看一下 .NET Framework 中 `ICollection
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
void Add(T item);
bool Remove(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
int Count { get; }
bool IsReadOnly { get; }
}
您可以看到,为了在我们的泛型集合中扩展 `ICollection
示例业务逻辑层
在我们实际编写集合类之前,我们将编写一些基础代码来配合使用它。本文附带的示例源代码包含两个类,它们构成了一个简单的业务逻辑层,我们将与我们的泛型集合类一起使用。
BusinessObjectBase 类
这是我们所有其他业务对象将从中派生的基类。该类只有一个名为 `UniqueId`(`Guid`)的属性,以及一个初始化该属性的默认构造函数。尽管这里使用的基类很简单,但可以为业务对象基类添加很多功能。就目前而言,我们只关心它如何被其他类派生,因为我们将用它来限制可用于我们泛型集合的类型。
//Abstract base class for all business object in the Business Logic Layer
public abstract class BusinessObjectBase
{
protected Guid? _UniqueId;
//local member variable which stores the object's UniqueId
//Default constructor
public BusinessObjectBase()
{
//create a new unique id for this business object
_UniqueId = Guid.NewGuid();
}
//UniqueId property for every business object
public Guid? UniqueId
{
get
{
return _UniqueId;
}
set
{
_UniqueId = value;
}
}
}
这并不复杂。基类是抽象的,以确保它必须被继承才能实例化,并且它只包含一个属性来唯一标识它派生的每个业务对象。
Person 类
此类将代表我们将在集合中存储的个人。如下所示,`Person` 类继承自抽象类 `BusinessObjectBase`。这将在我们创建泛型集合类时很重要。
public class Person : BusinessObjectBase
{
private string _FirstName = "";
private string _LastName = "";
//Paramaterized constructor for immediate instantiation
public Person(string first, string last)
{
_FirstName = first;
_LastName = last;
}
//Default constructor
public Person()
{
//nothing
}
//Person' First Name
public string FirstName
{
get
{
return _FirstName;
}
set
{
_FirstName = value;
}
}
//Person's Last Name
public string LastName
{
get
{
return _LastName;
}
set
{
_LastName = value;
}
}
}
此类同样很简单,只包含 `FirstName` 和 `LastName` 属性,以及一个立即初始化这些属性的构造函数,还有一个默认的无参数构造函数。现在我们已经看到了我们将要收集的对象,让我们继续了解我们将如何收集它们。
BusinessObjectCollection<T> 类
这里将发生大部分魔法。我们在这里从 `ICollection
using System;
using System.Collections.Generic;
//needed for non-generic explicit interface
//implementation requirements from ICollection
using System.Collections;
using System.Text;
namespace GenericsExample.Business
{
public class BusinessObjectCollection<T> :
ICollection<T> where T : BusinessObjectBase
{
//inner ArrayList object
protected ArrayList _innerArray;
//flag for setting collection to read-only
//mode (not used in this example)
protected bool _IsReadOnly;
// Default constructor
public BusinessObjectCollection()
{
_innerArray = new ArrayList();
}
// Default accessor for the collection
public virtual T this[int index]
{
get
{
return (T)_innerArray[index];
}
set
{
_innerArray[index] = value;
}
}
// Number of elements in the collection
public virtual int Count
{
get
{
return _innerArray.Count;
}
}
// Flag sets whether or not this collection is read-only
public virtual bool IsReadOnly
{
get
{
return _IsReadOnly;
}
}
// Add a business object to the collection
public virtual void Add(T BusinessObject)
{
_innerArray.Add(BusinessObject);
}
// Remove first instance of a business object from the collection
public virtual bool Remove(T BusinessObject)
{
bool result = false;
//loop through the inner array's indices
for (int i = 0; i < _innerArray.Count; i++)
{
//store current index being checked
T obj = (T)_innerArray[i];
//compare the BusinessObjectBase UniqueId property
if (obj.UniqueId == BusinessObject.UniqueId)
{
//remove item from inner ArrayList at index i
_innerArray.RemoveAt(i);
result = true;
break;
}
}
return result;
}
// Returns true/false based on whether or not it finds
// the requested object in the collection.
public bool Contains(T BusinessObject)
{
//loop through the inner ArrayList
foreach (T obj in _innerArray)
{
//compare the BusinessObjectBase UniqueId property
if (obj.UniqueId == BusinessObject.UniqueId)
{
//if it matches return true
return true;
}
}
//no match
return false;
}
// Copy objects from this collection into another array
public virtual void CopyTo(T[] BusinessObjectArray, int index)
{
throw new Exception(
"This Method is not valid for this implementation.");
}
// Clear the collection of all it's elements
public virtual void Clear()
{
_innerArray.Clear();
}
// Returns custom generic enumerator for this BusinessObjectCollection
public virtual IEnumerator<T> GetEnumerator()
{
//return a custom enumerator object instantiated
//to use this BusinessObjectCollection
return new BusinessObjectEnumerator<T>(this);
}
// Explicit non-generic interface implementation for IEnumerable
// extended and required by ICollection (implemented by ICollection<T>)
IEnumerator IEnumerable.GetEnumerator()
{
return new BusinessObjectEnumerator<T>(this);
}
}
}
您会注意到类声明的第一行
public class BusinessObjectCollection<T> :
ICollection<T> where T : BusinessObjectBase
上面的语法只是告诉编译器,只允许从 `BusinessObjectBase` 派生的类型出现在集合中。这就是泛型集合真正派上用场的地方。当我们在上面使用泛型类型约束时,我们就省去了在运行时担心类型安全错误的步骤。如果我们尝试创建一个不派生自 `BusinessObjectBase` 的对象集合,我们会得到一个编译器错误。这可能听起来有限制,但这正是我们想要的结果,因为我们可能希望向我们的集合类添加特殊处理,而这些处理只在我们处理继承了 `BusinessObjectBase` 的类时才起作用。
您还可以添加其他泛型类型约束,例如 `new()
` 约束,它会告诉编译器,集合将存储的泛型类型必须有一个公共的无参数构造函数。这通常用于获得在泛型类内部实例化泛型类型实例的能力。有关泛型类型约束的更多信息,请访问 MSDN 网站 此处。
您会注意到我使用了一个 `ArrayList` 对象作为驱动自定义泛型集合的内部数据结构。由于这是一个非泛型类型,如果您尝试创建包含值类型(如整数)的泛型集合,可能会导致一些装箱/拆箱。但是,由于我们使用了约束来只允许派生自 `BusinessObjectBase` 的类型,所以我们不必担心这个问题。`ArrayList` 对象仍然会将一些类型向下转换为 `object` 基类,但性能损失很小,甚至没有。您也可以使用另一个泛型列表类型作为后备元素,例如 `List
该类的其余部分只是 `ICollection
但是,有一些代码值得注意,它与我们的下一个类有关
// Returns custom generic enumerator for this BusinessObjectCollection
public virtual IEnumerator<T> GetEnumerator()
{
//return a custom enumerator object
//instantiated to use this BusinessObjectCollection
return new BusinessObjectEnumerator<T>(this);
}
// Explicit non-generic interface implementation for IEnumerable
// extended and required by ICollection (implemented by ICollection<T>)
IEnumerator IEnumerable.GetEnumerator()
{
return new BusinessObjectEnumerator<T>(this);
}
这两个实现是 `foreach` 循环用来遍历我们的自定义集合的。我们必须定义一个自定义泛型 `Enumerator` 对象来执行实际的遍历。如果您以前从未处理过泛型接口实现,您可能会想为什么这里有两个 `GetEnumerator()` 的实现。第二个方法是对 `IEnumerable` 的显式非泛型接口实现。为什么我们必须这样做?答案很简单,因为 `IEnumerator
现在我们知道将如何收集业务对象了,让我们继续了解我们将如何使用自定义泛型 `Enumerator` 来检索它们。
BusinessObjectEnumerator<T> 类
下面是我们用于枚举 `BusinessObjectCollection
public class BusinessObjectEnumerator<T> : IEnumerator<T> where T : BusinessObjectBase
{
protected BusinessObjectCollection<T> _collection; //enumerated collection
protected int index; //current index
protected T _current; //current enumerated object in the collection
// Default constructor
public BusinessObjectEnumerator()
{
//nothing
}
// Paramaterized constructor which takes
// the collection which this enumerator will enumerate
public BusinessObjectEnumerator(BusinessObjectCollection<T> collection)
{
_collection = collection;
index = -1;
_current = default(T);
}
// Current Enumerated object in the inner collection
public virtual T Current
{
get
{
return _current;
}
}
// Explicit non-generic interface implementation for IEnumerator
// (extended and required by IEnumerator<T>)
object IEnumerator.Current
{
get
{
return _current;
}
}
// Dispose method
public virtual void Dispose()
{
_collection = null;
_current = default(T);
index = -1;
}
// Move to next element in the inner collection
public virtual bool MoveNext()
{
//make sure we are within the bounds of the collection
if (++index >= _collection.Count)
{
//if not return false
return false;
}
else
{
//if we are, then set the current element
//to the next object in the collection
_current = _collection[index];
}
//return true
return true;
}
// Reset the enumerator
public virtual void Reset()
{
_current = default(T); //reset current object
index = -1;
}
}
首先,您应该注意此类声明的第一行
public class BusinessObjectEnumerator<T> : IEnumerator<T> where T : BusinessObjectBase
如您在此处所见,我们实现了相同的泛型约束,以确保我们将要枚举的对象类型派生自 `BusinessObjectBase`。
我们还定义了三个成员变量,我们将用它们来控制枚举的流程。它们是
protected BusinessObjectCollection<T> _collection; //enumerated collection
protected int index; //current index
protected T _current; //current enumerated object in the collection
第一个是正在遍历的 `BusinessObjectCollection` 的内部实例。此变量通过 `BusinessObjectEnumerator
另外两个成员变量只是对正在读取的类型为 `T` 的当前对象的引用,以及一个索引,用来告诉我们在集合中的位置。其余的 `IEnumerator
现在您已经定义了所有类并使它们协同工作,剩下的就是编写一些“驱动”程序或示例程序来演示自定义泛型集合的功能。我包含了一个示例控制台应用程序,演示了此处解释的泛型类的用法,以及类本身的源代码。我还添加了另一个我编写的控制台应用程序,用于测试使用不同泛型和非泛型类型、进行装箱/拆箱和类型转换时,不同集合操作所需的时间。
结论
自定义泛型集合可以为您的应用程序带来巨大优势,并为您节省大量时间和麻烦。通过创建一个类型安全的环境来收集您的对象,您可以从一开始就无需担心运行时实际存储在集合中的内容。您还可以通过不硬编码数据类型来自由地重用和扩展您的代码。您可以轻松地删除上述类中的约束,以创建适合您确切需求的解决方案,并且将来也可以扩展以满足您的需求。
您可能会想,“嗯,这一切都很好,但为什么不直接使用 `List