65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (11投票s)

2017 年 4 月 4 日

CPOL

6分钟阅读

viewsIcon

37062

downloadIcon

554

本文将介绍如何创建自己的自定义集合类,该类可以被迭代。

目录

引言

作为 .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 的接口,并从 CollectionClassGetEnumerator 方法返回该类的实例。

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。因此,最终我们拥有了一个根据我们的实现而自定义的集合类,它迭代字符串中的单词。还可以根据需要进一步自定义。

结论

本文详细介绍了集合类/集合对象的主题。创建集合类听起来可能有点难,但实际上并非如此,它能让我们完全控制我们想要使用的枚举类型。编码愉快 :-)

GitHub 上的源代码

源代码

 

© . All rights reserved.