why does it not iterate all the elements of the IEnumerable? - c#

I have this code:
public static void myMethodMytype paramObject, IEnumerable<MyType> paramObjects)
{
IEnumerable<Mytype> ieFilteredObjects = paramObjects.Where(x=>x.IDType == paramObject.IDType);
if (ieFilteredObjects.Count() == 2)
{
foreach (MyType iterator in ieFilteredObjects)
{
iterator.MyProperty = null;
}
}
}
In this case, ieFilteredObjects has 2 elements, but in the foreach, only update the first element, and the exit of the foreach.
If in the foreach I use this:
foreach (MyType iterator in ieFilteredObjects.ToList())
then it works as expected.
Why I have to convert the IEnumerable to a list?
Thanks.

IEnumerable can only be iterated once. You cannot iterate twice on an IEnumerable and expect to get the same values.
For instance, the IEnumerable may come from a yield return function, each time you call GetEnumerator() on the IEnumerable you start a new call to this function which may create new values.
This is not a common use case, but it exists. I already had this kind of bug in production.
The solution is to cache the result of the first iteration. A common technique is to use a linq: .ToList() to create a IList, that you can iterate ad infinitum
In your code you iterate at least twice:
ieFilteredObjects.Count()
foreach (MyType iterator in ieFilteredObjects)

Related

List<T>.AddRange and the yield statement

I am aware that the yield keyword indicates that the method in which it appears is an iterator. I was just wondering how that works with something like List<T>.AddRange.
Let's use the below example:
static void Main()
{
foreach (int i in MyInts())
{
Console.Write(i);
}
}
public static IEnumerable<int> MyInts()
{
for (int i = 0; i < 255; i++)
{
yield return i;
}
}
So in the above example after each yield, a value is returned in the foreach loop in Main and is printed to the console.
If we change Main to this:
static void Main()
{
var myList = new List<int>();
myList.AddRange(MyInts());
}
how does that work? Does AddRange get called for each int returned by the yield statement or does it somehow wait for all 255 values before adding the entire range?
The implementation of AddRange will iterate over the IEnumerable input using the iterator's .MoveNext() method until all values have been produced by your yielding method. This can be seen here.
So myList.AddRange(MyInts()); is called once and its implementation forces MyInts to return all of it values before moving on.
AddRange exhausts all values of the iterator because of how is implemented, but the following hypothetic method would only evaluate the first value of the iterator:
public void AddFirst<T>(IEnumerable<T> collection)
{
Insert(collection.First());
}
An interesting experiment while you play around with this is to add a Console.WriteLine(i); line in your MyInts method to see when each number is generated.
Short answer: When you call AddRange, it will internally iterate every item in your IEnumerable and add to the list.
If you did something like this:
var myList = new List<int>();
myList.AddRange(MyInts());
foreach (int i in myList)
{
Console.Write(i);
}
Then your values would be iterated twice, from the start to the end:
Once when adding to your list
Then in your for loop
Playing a bit
Now, let's suppose you created your own extension method for AddRange like this:
public static IEnumerable<T> AddRangeLazily<T>(this ICollection<T> col, IEnumerable<T> values)
{
foreach (T i in values)
{
yield return i; // first we yield
col.Add(i); // then we add
}
}
Then you could use it like this:
foreach (int i in myList.AddRangeLazily(MyInts()))
{
Console.Write(i);
}
...and it would be iterated twice as well, without going from the start to the end both times. It would lazily add each value to the list/collection and at the same time allow you to do something else (like printing it to output) after every new item being added.
If you had some sort of logic to stop the adding to the list in the middle of the operation, this should be helpful somehow.
The downside if this AddRangeLazily is: values will only be added to the collection once you iterate over AddRangeLazily like my code sample. If you just do this:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.Any())
// it wouldn't enter here...
...it won't add values at all. If you wanted that behaviour, you should use AddRange. Forcing the iterationg over AddRangeLazily method would work, though:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.AddRangeLazily(MyInts()).Count())
// it would enter here...thus adding all values to the someList
...however, depending on how lazy is the method you calling, it wouldn't iterate everything. For example:
var someList = new List<int>();
someList.AddRangeLazily(MyInts());
if (someList.AddRangeLazily(MyInts()).Any())
// it would enter here, plus adding only the first value to someList
Since Any() is true as soon as any item exists, then Any() just needs one iterationg to return true, therefore it just needs the first item to be iterated over.
I actually don't remember having to do something like this, it was just to play around with yield.
Fiddle here!!!
Interesting question.
The behavior is different if the enumerable is for a class that implements ICollection, such as another list or an array, but let's say it doesn't since your example doesn't. AddRange() simply uses the enumerator to insert items into the list one at a time.
using(IEnumerator<T> en = collection.GetEnumerator()) {
while(en.MoveNext()) {
Insert(index++, en.Current);
If the type of the enumerator is ICollection then AddRange first expands the list and then does a block copy.
If you want to see the code yourself:
https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,51decd510e5bfe6e

Foreach and enumerable

Why we can iterate item ex
mList.ForEach((item)
{
item.xyz ....
}
and for a simple array we need to force foreach loop?
foreach(int i in arr)
i.xyz
or use delegate type ?
Action<int> action = new Action<int>(myfunc);
Array.ForEach(intArray, action);
What is the differemce?
The first syntax is not correct. It should be like this:
mList.ForEach(item =>
{
// item.xyz
});
The ForEach is a method of List<T> that enables you for each item in a list to call an Action<T>.
On the other hand the foreach
statement repeats a group of embedded statements for each element in
an array or an object collection that implements the
System.Collections.IEnumerable or
System.Collections.Generic.IEnumerable interface.
That being said, ForEach can be called only on lists and foreach can be called on any object that implements either IEnumerable or IEnumerable. That's the big difference here.
Regarding the delegate type, there isn't any difference. Actually, lambda expressions item=>{ item.xyz = ...} are a shorthand for delegates.
The language defines foreach as an operation of IEnumerable. Therefore, everything which implements IEnumerable is iteratable. However, not all IEnumerables 'make sense' when using a ForEach block.
Take this for example:
public static IEnumerable<MyObject> GetObjects()
{
var i = 0;
while(i < 30)
yield return new MyObject { Name = "Object " + i++ };
}
And then you do something like this:
var objects = GetObjects();
objects.ForEach(o => o.Name = "Rob");
foreach (var obj in objects)
Console.WriteLine(obj.Name);
IF that compiled, it would print out Object 0 to Object 29 - NOT Rob 30 times.
The reason for this is that the iterator is reset each time you iterate the enumerable. It makes sense for ForEach on a list, as the enumerable has been materialized, and objects are not re-created every time you iterate it.
In order to make ForEach work on an enumerable, you'd need to materialize the collection as well (such as putting it into a list), but even that is not always possible, as you can have an enumerable with no defined end:
public static IEnumerable<MyObject> GetObjects()
{
while(true)
yield return new MyObject { Name = "Object " };
}
It also makes sense to have ForEach on Array - but for reasons I'm unaware of, it was defined as Array.ForEach(arr) rather than arr.ForEach()
Moral of the story is, if you think you need a ForEach block, you probably want to materialize the enumerable first, usually to a List<T> or an array (T[]).

Any benefit to loop with nested condition in the ReSharper way?

When using a foreach loop with a nested condition inside, I ever write in the following way:
foreach (RadioButton item in listOfRadioButtons)
{
if (item.IsChecked == true)
{
// sometging
}
}
But I've installed ReSharper and it suggests to change this loop to the following form (removing the if and using a lambda):
foreach (RadioButton item in listOfRadioButtons.Where(item => item.IsChecked == true))
{
// something
}
In my experience, the ReSharper way will loop two times: one to generate the filtered IEnumerable, and after to loop the results of the .Where query again.
I am correct? If so, why is ReSharper suggesting this? Because in my opinion, the first is also more reliable.
Note: The default IsChecked property of the WPF RadioButton is a Nullable bool, so it's need a == true, a .Value, or a cast to bool inside a condition to return bool.
In my experience, the ReSharper way will loop two times: one to
generate the filtered IEnumerable, and after to loop the results of
the .Where query again.
Nope, it will loop only once. Where does not loop your collection - it only creates iterator which will be used to enumerate your collection. Here is how LINQ solution looks like:
using(var iterator = listOfRadioButtons.Where(rb => rb.IsChecked == true))
{
while(iterator.MoveNext())
{
RadioButton item = iterator.Current;
// something
}
}
Your original code is better for performance - you will avoid creating delegate and passing it to instance of WhereEnumerableIterator, and then executing delegate for each item in source sequence. But you should note, as #dcastro pointed, difference will be really small and does not worth noting until you will have to optimize this particular loop.
Solution suggested by ReSharper is (maybe) better for readability. I personally like simple if condition in a loop.
UPDATE: Where iterator can be simplified to (also some interfaces are omitted)
public class WhereEnumerableIterator<T> : IEnumerable<T>, IDisposable
{
private IEnumerator<T> _enumerator;
private Func<T,bool> _predicate;
public WhereEnumerableIterator(IEnumerable<T> source, Func<T,bool> predicate)
{
_predicate = predicate;
_enumerator = source.GetEnumerator();
}
public bool MoveNext()
{
while (_enumerator.MoveNext())
{
if (_predicate(_enumerator.Current))
{
Current = _enumerator.Current;
return true;
}
}
return false;
}
public T Current { get; private set; }
public void Dispose()
{
if (_enumerator != null)
_enumerator.Dispose();
}
}
Main idea here - it enumerates original source only when you ask it to move to next item. Then iterator goes to next item in original source and checks if it matches predicate. If match found, then it returns current item and puts enumerating source on hold.
So, until you will not ask items from this iterator, it will not enumerate source. If you will call ToList() on this iterator, it will enumerate source sequence and return all matched items, which will be saved to new list.

decorate IEnumerable without looping

I need to create an IEnummerable of DcumentSearch object from IQueryable
The following code causes the database to load the entire result which makes my app slow.
public static IEnumerable<DocumentSearch> BuildDocumentSearch(IQueryable<Document> documents)
{
var enumerator = documents.GetEnumerator();
while(enumerator.MoveNext())
{
yield return new DocumentSearch(enumerator.Current);
}
}
The natural way of writing this is:
public static IEnumerable<DocumentSearch> BuildDocumentSearch(IQueryable<Document> documents)
{
return documents.Select(doc => new DocumentSearch(doc));
}
When you call one of the IEnumerable extension methods like Select, Where, OrderBy etc, you are still adding to the recipe for the results that will be returned. When you try to access an element of an IEnumerable (as in your example), the result set must be resolved at that time.
For what it's worth, your while loop would be more naturally written as a foreach loop, though it should have the same semantics about when the query is executed.

How to make this linq efficient

I have this code snippet where we get a collection from COM Dll
public BOCollection SelectedObjects{
get
{
IMSICDPInterfacesLib.IJMonikerElements oIJMonikerElements;
oIJMonikerElements = m_oIJSelectSet.Elements as IMSICDPInterfacesLib.IJMonikerElements;
BOCollection oBusinessObjects = new BOCollection(oIJMonikerElements);
return oBusinessObjects;
}
}
Now BOCollection does implement IEnumerable. So would it be better to change it to
public IEnumerable<BusinessObject> SelectedObjects
So as to get the iterator goodness ? Or is there another way ?
thanks
Sunit
Are you wanting to return IEnumerable so you get deferred execution? First off, you wouldn't want to do this in a property, as I'm sure FxCop will yell at you for that. Here's how I suggest you change things so you can benefit from both deferred execution and LINQ.
Change the m_oIJSelectSet.Elements property to a method that returns IEnumerable like so:
public IEnumerable<IJMonikeElements> GetElements() {
// Do some magic here to determine which elements are selected
return (from e in this.allElements where e.IsSelected select e).AsEnumerable();
// This could also be a complicated loop
// while (someCondition()) {
// bool isSelected = false;
// var item = this.allItems[i++];
// Complicated logic determine if item is selected
// if (isSelected) {
// yield return item;
// }
}
}
public IEnumerable<BusinessObject> GetSelectedObjects() {
return m_oIJSelectSet.GetElements().Cast<BusinessObject>();
}
Now, you'll have complete deferred execution and LINQ support.
If BOCollection implements IEnumerable, then you've already got the iterator goodness. Just throw it in a for or foreach loop.
The problem with IEnumerable<T> is yes, it will give you "Linq goodness", but the lowest common denominator of Linq goodness. Better to return IList<T> or even IQueryable<T> (if you can do this).
For example if somebody wanted to get the 4th element, IEnumerable<T> doesn't makes sense if you are already storing the objects in an array or list.
To get IQueryable<T> from a List<T> do this:
IQueryable<int> query = list.AsQueryable();

Categories

Resources