Should a GetEnumerator method still be idempotent when the class does not implement IEnumerable

雨燕双飞 提交于 2019-12-12 16:28:36

问题


This question piggy backs of another question which I raised regarding abusing the IEnumerable interface by modifying an object as you iterate over it.

The general consensus is that no anything that Implements IEnumerable should be idempotent. But .net supports compile time duck typing with the foreach statement. Any object that provides an IEnumerator GetEnumerator() method can be used inside a foreach statement.

So should the GetEnumerator method be idempotent or is it when it implements IEnumerable?

EDIT (Added context)

To put some context round this what I am suggesting is that when iterating over a queue each item is dequeued as it goes. Additionally any new objects pushed onto the queue after the call to GetEnumerator would still be iterated over.


回答1:


It's not the type which is idempotent - that doesn't even make much sense; you may mean immutable, but that's not clear. It's the GetEnumerator method itself which is typically idempotent.

While I'd say that's typically the case, I can envisage special cases where it makes sense to have a non-idempotent GetEnumerator method. For example, it could be that you've got data which can only be read once (because it's streaming from a web server which won't service the same request again, or something like that). In that case, GetEnumerator would have to effectively invalidate the data source, so that future calls would throw an exception.

Such types and methods should be documented very carefully, of course, but I think they're reasonable.




回答2:


This discussion is an old one and to my knowledge there's no common consensus.

Please do not confuse the concept of (runtime) Duck-Typing with abusing the compiler supported foreach to support your desired semantics.

Another concept you seem to confuse is Idempotence vs. Immutability. According to your wording you try to describe the second, which means the object providing the enumerator gets modified during enumeration. Idempotence on the other hand means your enumerator, when called twice will yield the same results.

Now that we're clear on this, you need to carefully decide on the semantics your IEnumerable operation should support. Certain kind of enumerations are hard to make idempotent (i.e. involve caching), and do usually fall into one of the following categories:

  • Enumerating over randomly changing data (i.e. a random number generator, sensor streams)
  • Enumerating over shared state (e.g. files, databases, streams etc.)

On the other hand, this only accounts for "source" operations. If you are implementing filter or transformation operations using enumerators, you should always try to make them idempotent.




回答3:


It seems you want a queue class from which you can dequeue all items in a nice one-liner.

There's nothing wrong with this idea per se; I'd just question your preference to specifically use GetEnumerator to achieve what you're after.

Why not simply write a method that is more explicit in terms of what it does? For example, DequeueAll, or something of the sort.

Example:

// Just a simplistic example. Not the way I'd actually write it.
class CustomQueue<T> : Queue<T>
{
    public IEnumerable<T> DequeueAll()
    {
        while (Count > 0)
        {
            yield return Dequeue();
        }
    }
}

(Note that the above could even be an extension method, if it represents literally the only functionality you'd want above and beyond what is already provided by Queue<T>.)

This way you could still get the "clean"-looking code I suspect you're after, without the (potential) confusion of a non-idempotent GetEnumerator:

// Pretty clean, right?
foreach (T item in queue.DequeueAll())
{
    Console.WriteLine(item);
}



回答4:


I would suggest that using ForEach on a collection shouldn't change it unless the name of the collection type implies that's going to happen. The issue in my mind would be what should be returned if a method is performed to consume a collection into something enumerable (e.g. to allow "For Each Foo in MyThing.DequeueAsEnum"). If DequeueAsEnum returns an iEnumerable, then someone could expect to get away with "Dim myIEnumerable As IEnumerable = MyThing.DequeueAsEnum" and then use MyIEnumerable in two disjoint For-Each loops. If DequeueAsEnum returns a type EnumerableOnlyOnce, then it would be a little clearer that its return should only be enumerated once. To be sure, the existence of implicit typing in newer C# and VB.Net dialects makes it a bit more likely that someone might assign the function return to a variable when they shouldn't, but I don't know how to prevent that.

BTW, there are a number of circumstances where it would be helpful to prevent a class reference from being stored into a variable; is there any way to declare a class in such a way that outside code can use expressions of that class type, but cannot declare variables of it?



来源:https://stackoverflow.com/questions/4194900/should-a-getenumerator-method-still-be-idempotent-when-the-class-does-not-implem

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!