Click here to Skip to main content
15,896,269 members
Please Sign up or sign in to vote.
4.00/5 (2 votes)
See more:
Hi
I have a few general questions about IEnumerable<t>. I know that it uses deferred execution i.e. it is only executed when it needs to be. What I don't know is when is best to use IEnumerable<t> as a method parameter.

Let say I have a method that reads from an xml file and returns an IEnumerable<t>, where T is the type representing the data read from the file. If I pass that data around(as input parameter for other methods), as IEnumerable, the query will be executed only when it needs to be. Using IEnumerable<t> as method parameter has an added plus that the method can also work with List<t> or T[] array and that the called method cannot change the collection without type casting it to something else. On the down side the query will be executed multiple times and always return the same results since the file does not change. Another thing I don't quite get is what happens when i call a method that receives an IEnumerable<t> with a List<t> and that method iterates the list. Since the method was called with list, there isn't any query to be executed, so is that (performance vise) the same as if the method would receive a List.

I know i haven't ask many concrete questions in the text above, so I can not expect concrete answers, but maybe somebody can explains what are the best practices regarding the topic described above.

Uroš
Posted
Updated 20-Nov-12 12:09pm
v2
Comments
Sergey Alexandrovich Kryukov 20-Nov-12 14:12pm    
You are right about expecting the answers ("garbage in -- garbage out" principle works here), but this question is not bad at all; I voted 4.
The utility of this interface is not clearly understood or misunderstood by many developers, especially the novice, and, as I can see, the MSDN documentation on this topic is not as good as I would wish it to be.

So, thank you for asking. I tried to explain things, but if you have any follow-up questions or concerns, you are more than welcome to ask.

Cheers,
--SA

1 solution

The main reason to develop any type implementing System.Collections.IEnumerable, http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx[^], is to introduce the possibility of traversing some set of objects through forеаch statement:
http://msdn.microsoft.com/en-us/library/ttw7t8t6%28v=vs.110%29.aspx[^],
http://msdn.microsoft.com/en-us/library/9yb8xew9.aspx[^].

If you don't need this, you don't have to implement this interface. This is an interesting example for you. Despite of the keyword "enum", .NET enumeration type do not implement enumeration through those declared (static) type members. To use foreach, you would need to iterate through its values (for example), but in general case, this is not exactly the same. To introduce foreach traversing those members (not anything else), I created a separate generic type using user's enumeration type as a generic parameter. This generic type provides comprehensive information of this enumeration type and enables foreach through the implementation of System.Collections.IEnumerable, as explained in the article:
Enumeration Types do not Enumerate! Working around .NET and Language Limitations[^].

It can give you a very general idea of the utility of this interface you could be able to apply to a wide range of data structures representing any arbitrary data sets.

[EDIT #1]

As a method parameter? As always with interface-type parameters, this is good to abstract out from the implementation. The method with such parameter will be able to traverse through the set accessible via forarch and access each element in this set. In many cases, this is all what matters.

This idea is not limited to parameter passing. This is just the general approach based on abstraction. If too related objects depend on each other, or one depends on another, the depending object may need a reference member pointing to another object. Depending on the purpose of dependency, very often, the reference does not need to be a class or structure type, because only small part of functionality should be exposed. In this case, interfaces come very handy.

[EDIT #2]

Performance of foreach is almost totally depends on the performance of the implementation of System.Collections.IEnumerator. As you can see, the only role of IEnumerable is to provide an access to the reference to IEnumerator which makes the major job:
http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.getenumerator.aspx[^],
http://msdn.microsoft.com/en-us/library/system.collections.ienumerator.aspx[^].

Almost everything depends on what the implementation of IEnumerator.Current and IEnumerator.MoveNext do. These method can access the elements of the set in a very simple way like indexing (in arrays or System.Collections.Generic.List, but they can also perform complex calculations, query something, etc. Even the reference to the element to be returned might not be available immediately, so the algorithm calculates out the required object on the fly. If sounds not wise, but there are certain cases when this makes sense. You need to understand what happens in each implementation.

Actually, I would like to give you another advice. Not always you can trust your own judgement on performance. To gain some experience and get better idea on performance, you can simple time some operations. The most accurate tool for that it the class System.Diagnostics.Stopwatch.

There is one catch specific to .NET (CLI) though. You need to take into account the time taken by JIT. Most usually, the code is JIT-compiled on per-method basis. So, the method is usually JIT-compiled when a method is about to be called for the very first time in the process life time. Be careful not to include the first call in timing. Please see:
http://en.wikipedia.org/wiki/Just-in-time_compilation[^],
http://msdn.microsoft.com/en-us/magazine/cc163791.aspx[^],
http://msdn.microsoft.com/en-us/library/k5532s8a.aspx[^].

If you time things sometime, some results will come at surprise to you. However, you should not get into this activity too much: it is needed only to get some idea and to reveal the bottlenecks. There are usually to few bottlenecks. Most performance issues are related to general code design way more than to fine implementation detail.

—SA
 
Share this answer
 
v3
Comments
koleraba 20-Nov-12 17:47pm    
Hi
Thank you for your answer. I am most conserned with performance panalties due to mutliple executions of the query so maybe a follow up question about this. Let me try to explain what I mean using an example. I have a method(as described above) which queries a xml file and returns an IEnumerable<t> without executing the actual query(it does not call .Count() or .ToList() or any other method the executes the query). Then lets say I have two additional methods. They both receive an IEnumerable<t> and the first one prints the number of items returned from the query(using .Count() method), and the other prints the actual items using a foreach. If I call both of this methods with the instance returned from the first method, the query will be obviously executed twice. What would happenen if I would modify the first method(the one that queries the xml file) to return an actual List<t> by calling a ToList() method on a query, but leaving the return type of the method as IEnumerable<t>. I know the query would be executed before the method returned, but what about on consequential calls to other two methods. Would they still execute the query, or not since they actualy received an List<t>(even though it was casted as a IEnumerable<t>)
Sergey Alexandrovich Kryukov 20-Nov-12 18:12pm    
Please see another update, [EDIT #2].
--SA
Sergey Alexandrovich Kryukov 20-Nov-12 18:13pm    
So, you delay the query. It can actually improve performance. It all depends on how you do it; hard to say without seeing your code...
--SA
koleraba 20-Nov-12 18:48pm    
Sergey thank you for your time. You have been very helpfull.
Sergey Alexandrovich Kryukov 20-Nov-12 19:15pm    
You are very welcome.
Good luck, call again.
--SA

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900