学习 C#:C# 中的自定义集合类






4.88/5 (11投票s)
本文将介绍如何创建自己的自定义集合类,该类可以被迭代。
目录
引言
作为 .NET 开发者,我们都熟悉集合和泛型。我们知道 ArrayList、Array、List 和 Dictionary 等集合类,可以通过它们进行迭代。本文将介绍如何创建自己的自定义集合类,该类可以被迭代。文章将通过逐步创建自定义集合类的过程,来了解创建集合类到底需要什么。本文的目的是通过循序渐进的实际操作来学习集合类的概念。
先决条件
学习如何创建自定义类的先决条件就是了解如何在 C# 中编写代码。本文使用一个名为 CollectionClassConcept
的控制台应用程序,其中包含一个名为 CollectionClass
的空类。我们将使用这个初始设置来学习集合类。
步骤 1
考虑到我们已经有一个集合类(即使它现在是空的),请尝试在 Program.cs 的 Main 方法中访问 CollectionClass
;创建该类的实例并对其进行迭代。
CollectionClass
namespace CollectionClassConcept
{
public class CollectionClass
{
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
编译解决方案后,最终会出现如下所示的编译时错误。
错误:foreach 语句无法在类型 'CollectionClassConcept.CollectionClass' 的变量上运行,因为 'CollectionClassConcept.CollectionClass' 不包含 'GetEnumerator' 的公共定义
步骤 2
正如错误所示,一个类需要有一个 GetEnumerator
方法才能被迭代。因此,请在 CollectionClass
类中添加一个新的 GetEnumerator()
方法。
CollectionClass
namespace CollectionClassConcept
{
public class CollectionClass
{
public int GetEnumerator()
{
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
编译解决方案后,最终会出现如下所示的编译时错误。
Error(错误):
- CS0161 - '
CollectionClass.GetEnumerator()
':并非所有代码路径都返回值 - CS0117 - 'int' 不包含 'Current' 的定义
- CS0202 - foreach 要求 '
CollectionClass.GetEnumerator()
' 的返回类型 'int' 必须有一个合适的公共MoveNext
方法和公共Current
属性
现在在上面的代码中,foreach 会尝试执行 GetEnumertaor
方法,但会失败,因为它期望一个 MoveNext
方法和一个名为 Current
的属性,并且它还期望该方法返回除整数以外的任何值。
步骤 3
此时,可以创建一个名为 CollectionEnumerator
的新类,实现名为 IEnumerator
的接口,并从 CollectionClass
的 GetEnumerator
方法返回该类的实例。
CollectionEnumerator
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
Error(错误):
- 'CollectionEnumerator' 未实现接口成员 'IEnumerator.Current'
- 'CollectionEnumerator' 未实现接口成员 'IEnumerator.MoveNext()'
- 'CollectionEnumerator' 未实现接口成员 'IEnumerator.Reset()'
因此,非常清楚的是,IEnumerator
接口(CollectionEnumerator
类实现的接口)有三个方法需要在子类中实现。
步骤 4
让我们尝试在 CollectionEnumerator
类中实现这些方法,看看结果。
CollectionEnumerator
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public bool MoveNext()
{
System.Console.WriteLine("Inside MoveNext Method");
return true;
}
public void Reset()
{
System.Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
System.Console.WriteLine("Inside Current Property");
return "Current Property";
}
}
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
运行应用程序时,我们看不到任何编译或运行时错误,但控制台窗口会显示无穷多的行在循环中重复,如下图所示。
是什么导致了这种无尽的输出?让我们找出原因。在 CollectionClass
类中,foreach 循环会调用 GetEnumerator
方法。这里期望 GetEnumerator
方法返回一个 IEnumerator
类型的值,以便进行迭代或枚举。之后,它会调用 CollectionEnumerator
类返回实例的 MoveNext
方法。如果 MoveNext
返回 true,则意味着有可读取的数据,然后它会调用 Current
属性来获取该数据。当调用 Current
属性时,它会输出“Inside Current Property”,然后获取属性的访问器,并始终返回“Current Property”文本,因为我们在代码中这样指定了。现在再次调用 MoveNext
,根据我们定义的逻辑,再次返回 true,因此就成了一个无限循环。如果 MoveNext
返回 false,表示没有更多数据了,循环就会在那里停止。让我们在下一步中尝试这样做。
步骤 5
CollectionEnumerator
using System;
using System.Collections;
using System.Collections.Generic;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public List<string> StringList=new List<string>(4) { "Value One", "Value Two", "Value Three", "Value Four", "Value Five" };
public int Counter = -1;
public bool MoveNext()
{
Counter++;
Console.WriteLine("Inside MoveNext Method : " + Counter);
return Counter != 5;
}
public void Reset()
{
Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
Console.WriteLine("Inside Current Property : " + StringList[Counter]);
return StringList[Counter];
}
}
}
}
CollectionClass
using System.Collections;
namespace CollectionClassConcept
{
public class CollectionClass
{
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator();
}
}
}
Program.cs
using System;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass();
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
}
}
}
输出
在 CollectionEnumerator
类中,创建了一个字符串的泛型 List。也可以创建一个数组或 ArrayList。在这种情况下,我们使用的是 List,它有五个成员,值为:“Value One”、“Value Two”、“Value Three”、“Value Four”和“Value Five”。有一个计数器变量初始化为 -1。每次调用 MoveNext 方法时,计数器值会加 1。现在我们明确指定了列表的长度为五,并在 MoveNext
方法中指定如果计数器超过五则返回 false。因此,计数器将跟踪 MoveNext
方法被调用的次数。每当 MoveNext
方法返回 true 时,就会调用 Current
属性,该属性会返回列表中定义的字符串成员,即 StringList
,索引为计数器的当前值。以类似的方式,可以根据列表或数组的长度进行迭代。
步骤 6
现在,让我们尝试使集合类独立于固定长度。例如,我们在前面的示例中使用了五。所以我们将实现动态获取输入,并通过计算其长度来迭代它,并相应地处理和显示输出。
CollectionEnumerator
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace CollectionClassConcept
{
public class CollectionEnumerator : IEnumerator
{
public List<string> StringList;
public int Counter = -1;
public CollectionEnumerator(string parameter)
{
StringList = parameter.Split(' ').ToList();
}
public bool MoveNext()
{
Counter++;
Console.WriteLine("Inside MoveNext Method : " + Counter);
return Counter != StringList.Count;
}
public void Reset()
{
Console.WriteLine("Inside Reset Method");
}
public object Current
{
get
{
Console.WriteLine("Inside Current Property : " + StringList[Counter]);
return StringList[Counter];
}
}
}
}
CollectionClass
using System.Collections;
using System.Collections.Generic;
namespace CollectionClassConcept
{
public class CollectionClass
{
private string _parameter;
public CollectionClass(string parameter)
{
_parameter = parameter;
}
public IEnumerator GetEnumerator()
{
return new CollectionEnumerator(_parameter);
}
}
}
Program.cs
using System;
using System.Security.AccessControl;
namespace CollectionClassConcept
{
class Program
{
static void Main(string[] args)
{
CollectionClass collectionClass=new CollectionClass("We know what is a collection class.");
foreach (string collection in collectionClass)
{
Console.WriteLine(collection);
}
Console.ReadLine();
}
}
}
上面代码的解释非常直接且不言自明。在 Program
类的 Main 方法中,当我们尝试创建 CollectionClass
的实例时,我们传入一个字符串作为参数,请记住该类有一个带参数的构造函数,它接受一个字符串参数。因此,首先调用 CollectionClass
构造函数,并将参数保存在变量 _parameter 中。现在,foreach 语句调用 GetEnumerator
,它又会创建一个 CollectionEnumerator
类的实例,并将 _parameter
作为参数传递给 CollectionEnumerator
的构造函数,请记住 CollectionEnumerator
类现在还有一个带参数的构造函数,它接受一个字符串参数。根据 CollectionEnumerator
类的实现,一旦调用其构造函数,参数就会通过字符串的 Split 方法按空格字符进行拆分。也可以通过向 split 方法提供分隔符数组来提供更多分隔符选项,现在我们使用空格作为分隔符。我们将拆分后得到的数组转换为字符串列表,并将其初始化为类中定义的 StringList 列表。现在,为了使枚举独立于固定长度工作,我们将不使用常量数字作为长度,而是使用构造函数中获得的列表的长度。因此,在 MoveNext
方法中,只有当计数器与列表的计数不匹配时才返回 false。因此,最终我们拥有了一个根据我们的实现而自定义的集合类,它迭代字符串中的单词。还可以根据需要进一步自定义。
结论
本文详细介绍了集合类/集合对象的主题。创建集合类听起来可能有点难,但实际上并非如此,它能让我们完全控制我们想要使用的枚举类型。编码愉快 :-)