如何添加新的构造函数而不破坏面向对象编程原则?

4

我有一个包装类用于处理IAsyncCursor,它返回IEnumerable<TResult>

我的程序可以从两个地方获取项目:

IEnumerable<TResult> GetItems()
{
    List<TResult> fromA = fromA();
    IEnumerable<TResult> fromB = fromB();

    var result = fromA.Concat(fromB).ToList();
    return result;
}

但我不想在内存中存储result,因为太昂贵了。

我想把GetItems()的返回结果更改为返回ResultCollection,并且具有调用Dispose()的能力。

因此,我需要更改fromA()fromB()方法的返回结果为ResultCollection,如下所示:

ResultCollection<TResult> GetItems()
{
    ResultCollection<TResult> fromA = fromA(); // wrap List<TResult> to ResultCollection
    ResultCollection<TResult> fromB = fromB(); // return collection that I can dispose when I need

    return new ResultCollection<TResult>(fromA.Concat(fromB).ToList());
}

我需要如何更改我的ResultCollection以存储List构造函数? 我需要这个新类符合所有的OOPSOLID原则。

我不需要在ResultCollection中再添加另一个构造函数,例如:

public ResultCollection(IList<TResult> list)
{
    _list = list;
}

你能否将你的集合更改为仅接受多个游标,然后在内部链接这些游标吗?这样你就可以只需执行 return new DeferredResultCollection<TResult>(fromA(), fromB()) 了。 - poke
2个回答

2

GetItems 不应该返回 DeferredResultCollection,而应该返回 IEnumerable<...>。然后你可以直接执行以下操作:

return fromA.Concat(fromB);

这将是完全惰性的。

整个DeferredResultCollection类不应该向此API的使用者公开,因为它没有有用的公共成员。IEnumerable<...>已经足够了。

实际上,DeferredResultCollection类根本不需要。您可以用一个迭代器替换它。


下面是一个详细版本:

  1. 删除DeferredResultCollection
  2. 使用以下内容:

.

public static IEnumerable<TResult> CreateDeferredIEnumerable(IAsyncCursor<TResult> _asyncCursor)
{
    if (_asyncCursor != null)
    {
        using (_asyncCursor) { //This is key
         for (; _asyncCursor.MoveNextAsync().Result;)
         {
             foreach (var result in _asyncCursor.Current)
             {
                 yield return result;
             }
         }
        } //The Dispose is always triggered!
    }
}

这总是释放资源,即使在以下示例情况下:

1. ((IDisposable)CreateDeferredIEnumerable(...)).Dispose();
1. ((IDisposable)CreateDeferredIEnumerable(...).GetEnumerator()).Dispose();
2. CreateDeferredIEnumerable(...).ToList();
3. CreateDeferredIEnumerable(...).Take(1).ToList();
4. foreach (var x in CreateDeferredIEnumerable(...)) break;

为了支持连接,我们需要确保处理:
tatic IEnumerable<TSource> ConcatIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second)
{
    using (first as IDisposable) {
    using (secondas IDisposable) {
    foreach (TSource iteratorVariable0 in first)
    {
        yield return iteratorVariable0;
    }
    foreach (TSource iteratorVariable1 in second)
    {
        yield return iteratorVariable1;
    }
    }
    }
}

我希望这个能够运行,虽然它只是草草地拼凑在一起。但是它肯定可以正常工作。


1
然后,迭代器将被提前处理,从而触发任何使用和最终块。这会导致处置。这并不是特例。像File.EnumerateAllLines这样的方法就是这样做的。 - usr
1
只需在 using(_asyncCursor) 中包装 GetEnumerator。为什么这样不起作用?这就像调用 File.ReadLines(...).ToList(); 一样简单。 - usr
1
只有当可枚举类型的消费者枚举完所有项目时,才会处理该类型。不,正如我所说,Dispose触发所有的using和finally块。这是C#语言的规则。您可以尝试一下。关于我的答案,我提供给消费者的是... 我也是这样做的,只不过更简洁。当然,您的答案也是正确的。 - usr
1
当您使用foreach循环时,首先获取IEnumerator,然后稍后将其处理。所有这些都不是此答案或有争议的独特内容。实际上,只需尝试一下,反编译它或在某些教程中查找即可。 - usr
1
另一个例子,如果您连接两个可枚举对象,其中第一个是普通列表,第二个是包装“_asyncCursor”的可枚举对象。 如果消费者决定在结果上执行 Take(5).ToList() 操作,并且列表具有 5 个或更多项,则 _asyncCursor 将不会被处理。 - Yacoub Massad
显示剩余20条评论

1
您可以创建一个名为IDisposableEnumerable的新接口,其定义如下:
public interface IDisposableEnumerable<T> : IEnumerable<T>, IDisposable
{

}

然后使你的DeferredResultCollection类实现这样的接口。然后,您应该创建另一个实现此接口的实现,它包装任何IEnumerable<T>。这样的类可以称为EnumerableWrapper,其Dispose方法内部不执行任何操作。
您还需要创建另一个实现,它可以使用组合模式将两个(或多个)IDisposableEnumerable对象连接在一起,这样的类可以称为CompositeDisposableEnumerable
请注意,当调用异步方法时,您的DeferredResultCollection类会阻塞,这是不理想的。
您可能还想考虑使用.NET的Reactive Extensions,但那是完全不同的事情,可能会导致设计更改。
更新:
以下是EnumerableWrapperCompositeDisposableEnumerable的实现:
public class EnumerableWrapper<T> : IDisposableEnumerable<T>
{
    private readonly IEnumerable<T> m_Enumerable;

    public EnumerableWrapper(IEnumerable<T> enumerable)
    {
        m_Enumerable = enumerable;
    }

    public void Dispose()
    {

    }

    public IEnumerator<T> GetEnumerator()
    {
        return m_Enumerable.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable) m_Enumerable).GetEnumerator();
    }
}

public class CompositeDisposableEnumerable<T> : IDisposableEnumerable<T>
{
    private readonly IDisposableEnumerable<T>[] m_DisposableEnumerables;

    public CompositeDisposableEnumerable(params IDisposableEnumerable<T>[] disposable_enumerables)
    {
        m_DisposableEnumerables = disposable_enumerables;
    }

    public IEnumerator<T> GetEnumerator()
    {
        foreach (var disposable_enumerable in m_DisposableEnumerables)
        {
            foreach (var item in disposable_enumerable)
                yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Dispose()
    {
        foreach (var disposable_enumerable in m_DisposableEnumerables)
            disposable_enumerable.Dispose();
    }
}

更新:
以下是示例用法:
public IDisposableEnumerable<T> GetItems<T>()
{
    List<T> collection1 = ....;

    DeferredResultCollection<T> collection2 = new DeferredResultCollection<T> (async_cursor);

    return new CompositeDisposableEnumerable<T>(new EnumerableWrapper<T>(collection1), collection2);

}

而从消费者的角度来看:

using(var items = GetItems<string>())
{
    //do something with items
} //This will invoke `Dispose` which will be propagated finally to DeferredResultCollection.Dispose and thus to IAsyncCursor.Dispose

请问您能否提供一个实现组合模式的代码,因为我对它们有些困惑。非常感谢。 - Anatoly
你认为我需要使用 usr 这个想法吗? - Anatoly
1
@Anatoly,我认为我的方法更安全。我将资源处理的责任放在最终消费者身上。@usr的方法有一些优点。它不需要很多代码,通常可以使用LINQ方法来组合序列。他的方法最重要的优点是消费者不必显式调用“Dispose”。 - Yacoub Massad
1
@Anatoly,请阅读@usr回答下的评论,我认为它有一些限制,但这些可能不适用于您。此外,您可能想考虑在@usr方法中使用“Lazy”参数。我在他的回答中的一个评论中提出了这样的建议。 - Yacoub Massad
1
@Anatoly,您需要能够连接两个可丢弃的可枚举对象。您不能使用Concat LINQ方法,因为它不返回可丢弃的可枚举对象。请参阅我的更新以获取示例用法。 - Yacoub Massad
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接