Is there a canonical way to "fix" "dynamic" IEnumerable?

IEnumerable does not guarantee that enumeration will give the same result twice. In fact, it’s quite simple to create an example in which myEnumerable.First() returns two values ​​when executed twice:

 class A { public A(string value) { Value = value; } public string Value {get; set; } } static IEnumerable<A> getFixedIEnumerable() { return new A[] { new A("Hello"), new A("World") }; } static IEnumerable<A> getDynamicIEnumerable() { yield return new A("Hello"); yield return new A("World"); } static void Main(string[] args) { IEnumerable<A> fix = getFixedIEnumerable(); IEnumerable<A> dyn = getDynamicIEnumerable(); Console.WriteLine(fix.First() == fix.First()); // true Console.WriteLine(dyn.First() == dyn.First()); // false } 

This is not just an academic example: using the popular from ... in ... select new A(...) will create just this situation. This may lead to unexpected behavior:

 fix.First().Value = "NEW"; Console.WriteLine(fix.First().Value); // prints NEW dyn.First().Value = "NEW"; Console.WriteLine(dyn.First().Value); // prints Hello 

I understand why this is happening. I also know that this could be fixed by doing ToList() in Enumerable or overriding == for class A This is not my question.

The question is: when you write a method that takes an arbitrary IEnumerable, and you want the property to be evaluated only once (and then the links are “fixed”), what is the canonical way to do this? ToList() apparently used mainly, but if the source is already fixed (for example, if the source is an array), the links are copied to the list (unnecessarily, since all I need is a fixed property). Is there something more appropriate or ToList() "canonical" solution for this problem?

+6
source share
5 answers

ToList very definitely suitable.

It has several optimizations: if the input enumeration is an ICollection (for example, an array or a list), it calls ICollection.CopyTo to copy the collection to the array and not actually enumerate - so the cost is unlikely to be significant if the collection is not huge.

IMHO, in most cases it is better that most methods return ICollection<T> or IList<T> rather than IEnumerable<T> , if you do not want users to use a method that the implementation can use a lazy evaluation (for example, output).

In cases where the method should return an immutable list, return the shell with readonly ( ReadOnlyCollection<T> ), for example. calling ToList().AsReadOnly() , but still returns the IList<T> interface type.

If you follow this guide, method consumers will not need an extra ToList call.

+3
source

You mean, if your function accepts a parameter, and you want to make sure that the parameter is not lazy, because you need to iterate over it more than once in the method? Usually I make an ICollection parameter in this case, forcing the caller to re-enumerate if he is lazy.

+2
source

The IEnumerable interface simply provides a "path" to iterate through the elements (creating an IEnumerable from a linq query is a great example, since IEnumerable is like a SQL query for a database). It will always be dynamic until you save the result in ICollection (ToList, ToArray), so that the iteration is processed to the end, and the result is saved in a "fixed" way.

+2
source

The most common issue outlined by Joel Spolsky in his Legislative Listing . With the IEnumerable parameter in your method, you expect an object that can simply be enumerated, and nothing more. Nevertheless, you care about the past meeting, whether it is “fixed” or “dynamic”. You can see that the abstraction made by IEnumerable leaked. There is no solution that works well for all cases. In your case, you can pass List<T> or T[] (or some other types that you expect to be actual types for parameters in your method) instead of IEnumerable . The most common advice is to implement your abstraction and develop your code in relation to it.

+1
source

By the way you explained your question, it seems you want to list the sequence several times and get the same order every time. This means that you either keep a reference to the sequence to iterate later, or perform comparisons several times within the same method.

Generally speaking, when accepting the IEnumerable<T> that you want to keep the link or manipulate more than once, it is safer to copy the sequence into the internal representation. I prefer to use the ToList() extension, as it is very well understood and, possibly, the simplest collection with predictable order.

Internally, the .NET Framework tends to use List<T> whenever a "generic" collection is required.

0
source

Source: https://habr.com/ru/post/908024/


All Articles