C# .NET Unexpected Typing - Expanded foreach explanation - c#

As I was refactoring some code this morning, I noticed some weird behavior. I was iterating over a collection of type A. The declaration and usage of the Enumerable were split (I was declaring and defining a variable using some Linq, then iterating over it later via foreach). However, when I changed the type of the enumerable from IEnumerable<A> to IEnumerable<B>, I left the foreach as the following where enumerable was of type IEnumerable<B>.
IEnumerable<B> enumerable = someEnumerableOfB
foreach(A a in enumerable)
Following is a contrived example of the behavior I found:
IEnumerable<IEnumerable> enumerables = Enumerable.Range(1, 5).Select(x => new List<int> { x });
foreach (StringComparer i in enumerables) //this compiles
{
//do something here
}
foreach (int i in enumerables) //this doesn't compile
{
//do something here
}
IEnumerable<StringBuilder> stringBuilders = Enumerable.Range(1, 5).Select(x => new StringBuilder(x.ToString()));
foreach (FileStream sb in stringBuilders) //this doesn't compile
{
//do something here
}
I was surprised to see the first one compile. Can someone explain exactly why this works? I assume it has something to do with the fact that the IEnumerable is of an interface, but I can't explain it.

According to the algorithm described section §15.8.4. of the specification, the compiler will expand the foreach into the following:
{
IEnumerator<IEnumerable> e = ((IEnumerable<IEnumerable>)(x)).GetEnumerator();
try
{
StringComparer v;
while (e.MoveNext())
{
v = (StringComparer)(IEnumerable)e.Current; // (*)
// do something here
}
}
finally
{
// Dispose of e
}
}
The line I've marked with an asterisk is the reason why it compiles for the first and not for the second. That is a valid cast because you can have a subclass of StringComparer that implements IEnumerable. Now change it to:
v = (int)(IEnumerable)e.Current; // (*)
And it doesn't compile, because this is not a valid cast: int does not implement IEnumerable, and it can't have any subclasses.

Because the compiler cant know whats in the Enumerable but it knows that it cant be a value type (e.g. int).
foreach (StringComparer i in enumerables) will compile since StringComparer is a reference type and for the compilers sake might just be in enumerables.

Related

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[]).

Implicit typing on ListItemCollection not possible using foreach, but possible with for loop

A ListBox control has an Items property of type ListItemCollection.
I sort of understand why I can't write
foreach (var item in ShipperListBox.Items)
{
if (item.Selected) count++;
}
But instead have to write
foreach (ListItem item in ShipperListBox.Items)
{
if (item.Selected) count++;
}
It has to do with ListItemCollection implementing IEnumerable and not IEnumerable<ListItem> (as explained in this question).
But what I don't get is why the following is no problem.
for (int i = 0; i < ListBox1.Items.Count; i++)
{
if (ListBox1.Items[i].Selected) count++;
}
What part of ListItemCollection is making it clear to the compiler that ListBox.Items[i] is of type ListItem?
Because ListItemCollection implements an indexer that returns a ListItem.
This is separate from IEnumerable.
This is part of what .OfType<ListItem>() and .Cast<ListItem>() explicitly exist for:
The Cast(IEnumerable) method enables the standard query operators to be invoked on non-generic collections by supplying the necessary type information. For example, ArrayList does not implement IEnumerable, but by calling Cast(IEnumerable) on the ArrayList object, the standard query operators can then be used to query the sequence. (source)
So you can write
foreach (var item in ShipperListBox.Items.OfType<ListItem>())
{
if (item.Selected) count++;
}
I couldn't tell you why, though.
ListItemCollection.GetEnumerator does return an enumerator which was used since .NET 1.0 which does return object as value. The foreach pattern (as Eric Lippert does explain in much greater detail) requires an Enumerator returned by the object via the GetEnumerator method.
When you use var the compiler infers the type of you loop variable as object since Current of the Enumerator does return only an object.
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
But when you use foreach(ListItem item in xxx) ... the compiler does add a cast to ListItem from object automatically for you. You can try it out when you do a foreach(string str in new object[] { "str", 1 }) which will result in an InvalidCastException. There is no magic going with the var keyword. It simply does infer the type without doing any extra magic.
When you expect a ListItem in your loop you should write it out clearly. From the method signature of the enumerator it is not clear what objects it will return. You have to tell the compiler which types you expect. One more reason to not use the var keyword since the readers of your code will also not be able to deduce the type of your looping variable as well.

What is the for/while equivalent of foreach?

I have a foreach loop that needs converting to a for or while loop. My loop looks like this:
foreach (Item item in Items)
{
// some stuff
}
What is the equivalent for or while loop?
I think I need to use GetEnumerator to get an IEnumerator<Item>, but I don't know how to use it properly. Items isn't a list, otherwise I'd use indexing.
In the simplest case(no disposing etc.) you can use:
var enumerator = Items.GetEnumerator();// `var` to take advantage of more specific return type
while(enumerator.MoveNext())
{
Item item = enumerator.Current;
...
}
For the exact rules check the C# specification 8.8.4 The foreach statement.
A foreach statement of the form
foreach (V v in x) embedded-statement
is then expanded to:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
(Quoted from the C# Language Specification Version 4.0)
The types using here are: "a collection type C, enumerator type E and element type T". E is the return type of GetEnumerator, and not necessarily IEnumerator<V> since foreach uses duck typing. The spec also describes a number of corner cases and how to infer these types, but those details are probably not relevant here.
In C# 5 the declaration of v will be moved into the while loop to get more intuitive closure semantics.
If you're going to use a for loop, it generally means there's some way of quickly accessing the n-th item (usually an indexer).
for(int i = 0; i < Items.Count; i++)
{
Item item = Items[i]; //or Items.Get(i) or whatever method is relevant.
//...
}
If you're just going to access the iterator, you usually just want to use a foreach loop. If, however, you can't, this is usually the model that makes sense:
using(IEnumerator<Item> iterator = Items.GetEnumerator())
while(iterator.MoveNext())
{
Item item = iterator.Current;
//do stuff
}
you could, technically, do this in a for loop, but it would be harder because the construct just doesn't align well with this format. If you were to discuss the reason that you can't use a foreach loop we may be able to help you find the best solution, whether or not that involves using a for loop or not.
This is an equivalent in a for-loop
for (IEnumerator i = Items.GetEnumerator(); i.MoveNext(); )
{
Item item = (Item)i.Current;
// some stuff
}

How to check if a variable is an IEnumerable of some sort

basically I'm building a very generic T4 template and one of the things I need it to do is say print variable.ToString(). However, I want it to evaluate lists and foreach through them and instead print ListItem.ToString() My T4 template does not know what type variable will be ahead of time, that is why this is so generic.
But my current code that gets generated looks like this:
if(variable!=null)
if(variable is IEnumerable) //error here
foreach(var item in variable)
Write(item.ToString());
I get a compiler error on the marked line for "Using the generic type System.Generic.Collections.IEnumerable requires one type argument"
I don't actually care what type it is though, I just want to know if you can foreach through the variable. What code should I use instead?
You have already accepted an answer however,since generic IEnumerable<T> implements the non generic IEnumerable you can just cast to that.
// Does write handle null? Might need some sanity aswell.
var enumerable = variable as System.Collections.IEnumerable;
if (enumerable != null)
foreach(var item in enumerable)
Write(item);
else
Write(item);
If you want to test for the non-generic IEnumerable then you'll need to include a using System.Collections directive at the top of your source file.
If you want to test for an IEnumerable<T> of some kind then you'll need something like this instead:
if (variable != null)
{
if (variable.GetType().GetInterfaces().Any(
i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
// foreach...
}
}
The other answers have pointed out the generic/non-generic IEnumerable difference but I should also point out that you will also want to test for String specifically because it implements IEnumerable but I doubt you'll want to treat it as a collection of characters.
Since C# 7.0 you can also achieve this so:
if (variable is IEnumerable enumVar)
{
foreach (var e in enumVar)
{
...
}
}
Well, somewhat simple but... if you only have:
using System.Collections.Generic;
you might need to add:
using System.Collections;
The former defines IEnumerable<T> and latter defines IEnumerable.
In general, with no non-generic base type/interface, this requires GetType and a recursive look-up through the base types/interfaces.
However, that doesn't apply here :-)
Just use the non-generic IEnumerable (System.Collections.IEnumerable), from which the generic IEnumerable (System.Collections.Generic.IEnumerable<T>) inherits.
You can actually test the base class of any generic type directly.
instance.GetGenericTypeDefinition() == typeof(IEnumerable<>)
If you don't care about object type and you are not in Generic method in C# 7.0+
if (item is IEnumerable<object> enumVar)
{
foreach (var e in enumVar)
{
e.ToString();
}
}
In C# < 7.0
if (item is IEnumerable<object>)
{
var enumVar = item as IEnumerable<object>;
foreach (var e in enumVar)
{
e.ToString();
}
//or you can cast an array to set values,
//since IEnumerable won't let you, unless you cast to IList :)
//but array version here
//https://stackoverflow.com/a/9783253/1818723
}
This is an old question, but I wanted to show an alternative method for determining if a SomeType is IEnumerable:
var isEnumerable = (typeof(SomeType).Name == "IEnumerable`1");

Changing element value in List<T>.ForEach ForEach method

I have the following code:
newsplit.ToList().ForEach(x => x = "WW");
I would expect that all elements in the list are now "WW" but they are still the original value. How come? What do I have to do different?
Assuming that newsplit is an IEnumerable<string>, you want:
newsplit = newsplit.Select(x => "WW");
The code that you currently have is equivalent to the following:
foreach(string x in newsplit.ToList()) {
AssignmentAction(x);
}
...
public static void AssignmentAction(string x) {
x = "WW";
}
This method won't modify x because of the pass-by-value semantics of C# and the immutability of strings.
Other answers have explained why your current code doesn't work. Here's an extension method which would fix it:
// Must be in a static non-nested class
public static void ModifyEach<T>(this IList<T> source,
Func<T,T> projection)
{
for (int i = 0; i < source.Count; i++)
{
source[i] = projection(source[i]);
}
}
Then use like this:
newsplit.ModifyEach(x => "WW");
That will work with any implementation of IList<T> such as arrays and List<T>. If you need it to work with an arbitrary IEnumerable<T> then you've got a problem, as the sequence itself may not be mutable.
Using Select() is a more functional approach of course, but sometimes mutating an existing collection is worth doing...
The ForEach will allow you to manipulate the elements of the IEnumerable, but not change the reference of the element.
ie, this would set a Foo property of each element in the IEnumerable to the string "WW":
newsplit.ToList().ForEach(x => x.Foo = "WW");
However, you won't be able to modify the values inside the IEnumerable itself.
It's because the LINQ expression is creating Anonymous Types, and these are read-only. They can't be assigned to. Also, in a standard for each loop you cannot assign to a collection that is being enumerated. Try:
foreach (var item in newsplit)
{
item = "this won't work";
}
You can write like this:
newsplit = newsplit.Select(x => "WW").ToList();

Categories

Resources