使用可配置的枚举器

Using a configurable enumerator

我有一个 class,它封装了我们称之为 ItemCollection 的项目集合。我需要公开两个不同的枚举器来处理这个集合:一个简单地枚举所有项目 (Item),第二个只枚举那些属于特定派生类型的项目 (T where T:Item)。一个问题是紧凑框架上的一些实时库代码 运行 的这一部分确实需要避免在堆上分配对象,这可能会在 foreach 循环中间触发收集。我一直在使用 struct Enumerators 来满足这个要求。

public class ItemCollection
{
    public ItemEnumerator GetEnumerator() {}
    public ItemGenericEnumerator<T> GetEnumerator<T>() {} // Probably not correct
}

public struct ItemEnumerator : IEnumerator<Item>
{
    Item Current;
    bool MoveNext() {}
    void Reset() {}
}

public struct ItemGenericEnumerator<T> : IEnumerator<T> where T : Item
{
    T Current;
    bool MoveNext() {}
    void Reset() {}
}

从表面上看,我不知道如何对 GetEnumerator<T> 进行特定调用,实际上我是从这个开始的:

public class ItemCollection
{
    public ItemEnumerator GetEnumerator() {}
    public ItemGenericEnumerator<T> ItemsOfType<T>() {}
}

foreach(var item in collection.ItemsOfType<X>)

可是我立马运行变成了does not contain a public definition for 'GetEnumerator'

一个解决方案是让 ItemsOfType return 一个通用的 throw away class 带有一个接收枚举器的构造函数和一个 GetEnumerator 方法,但该实现打破了无堆分配要求(将 throw away struct that returns another struct with GetEnumerator work?).

另一个被放弃的选择是简单地使用第一个枚举器并要求手动检查类型,这只是额外的一行代码。然而,当前 ItemCollection 的实现细节意味着 return 仅提取一种类型的项目与提取所有项目然后在框外排序它们之间存在很大差异。

好吧,这是一个很好的例子,说明了讨论和休息的地方。解决方案确实是一个指向结构枚举器的丢弃结构。我正在关注一个非典型的实现,它让我纠结于存储值类型枚举器的值类型的味道以及发生坏事的可能性。退后一步,开始新的传统 class 模式可以很容易地适应没有气味的结构。

public class ItemCollection
{
    private ItemEnumerator<T> GetItemsOfTypeEnumerator<T>() {}

    public ItemEnumeratorWrapper<T> ItemsOfType<T>()
    {
        return new ItemEnumeratorWrapper<T>(this);
    }

    public ItemEnumerator<Item> GetEmumerator() {}

    public struct ItemEnumeratorWrapper<T> where T : Item
    {
        private ItemCollection _collection;

        public ItemEnumeratorWrapper<T>(ItemCollection collection)
        {
            _collection = collection;
        }

        public ItemEnumerator<T> GetEnumerator()
        {
            return _collection.GetItemsOfTypeEnumerator<T>();
        }
    }
}

在 .NET 中使用不安全的代码和垃圾收集器管理会让您开始想得太多。

你的回答让我有点困惑。我想我一直在想你的问题。

我现在明白你唯一需要的是一个实现 GetEnumerator() 方法的结构,这样你就可以做 foreach,等等。我没有意识到你已经有了 IEnumerator<> 项目本身的实施已完成。

一个可能的改进是让你的 ItemEnumeratorWrapper 实现完整的 IEnumerable 接口,这样你就可以把它变成一个私有的内部结构,如:

private struct ItemEnumeratorWrapper<T> : IEnumerable<T> where T : Item
{
    private ItemCollection _collection;

    public ItemEnumeratorWrapper<T>(ItemCollection collection)
    {
        _collection = collection;
    }

    public ItemEnumerator<T> GetEnumerator()
    {
        return _collection.GetItemsOfTypeEnumerator<T>();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>)this).GetEnumerator();
    }
}

这允许您从 ItemsOfType<T>() 方法中 return 一个 IEnumerable<T>,并隐藏您的实现。

包装器的替代方法是让您的 ItemEnumerator<T> 结构实现 IEnumerator<T>IEnumerable<T> 接口,并将它们设为私有。这只会在它们的实现中添加 2 个非常小的附加方法,并且不需要中间包装器。

下面的例子有点做作,因为我对你容器的内部结构一无所知,所以我编造了一些东西来说明这个想法。

private struct ItemEnumerator<T> : IEnumerator<T>, IEnumerable<T> where T : Item
{
    // Specific types for your unsafe internals
    // completely made up.
    private readonly SomeTypeOfCollectionHandle _handle;
    private SomeTypeOfCursor _cursor = SomeTypeOfCursor.BeforeAll;

    public ItemEnumerator(SomeTypeOfCollectionHandle handle)
    { 
        _handle = handle;
    }

    // IEnumerable<T> implementation.
    public IEnumerator<T> GetEnumerator()
    {
        // simply return a new instance with the same collection handle.
        return new ItemEnumerator(_handle);
    }

    public bool MoveNext()
    {
        return (cursor = _handle.CursorNext(_cursor)).IsValid();
    }

    public T Current
    { 
        get
        {
            if (!_cursor.IsValid())
                throw new InvalidOperationException(); 
            return _cursor.Read<T>();
        }
    }

    object System.Collections.IEnumerator.Current
    {
        get { return (object)((IEnumerator<T>)this).Current; }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<T>)this).GetEnumerator();
    }

    public void Dispose()
    {
        _cursor = _handle.CloseCursor(_cursor);
    }
}

这样做可以确保 ItemCollection 的 public 接口仅公开标准 BCL 接口。