Why ListItem must be declared explicitly in foreach C# - c#

I'm confused as to why this code would not compile if listControl is a ListControl object such as a DropDownList:
foreach (var item in listControl.Items) {
item.Value = string.empty;
}
The compiler considers item to be of type object. It works if I replace var with ListItem, declaring the variable explicitly. The Items property is a ListItemCollection, which implements IEnumerable. Shouldn't the compiler be able to tell that the objects in the collection are of type ListItem?

This is a strange behavior since, as you point out, ListItemCollection is a collection of ListItems. This appears related to the fact that this was implemented before C# supported generics. And so it implements IEnumerable instead of IEnumerable<ListItem>, and isn't able to determine the correct type.
I suggest rewriting your loop this way:
foreach (ListItem item in listControl.Items) {
item.Value = string.empty;
}
Check out this question for additional information.

Compiler can't tell that to you at compile time because the items are object. It will compile with any type, for example if you use
foreach(int item in collection)
it will compile, because casting from object to int is valid, but it will throw an exception at runtime because the types are not compatible.

Original Question
The Items property is a ListItemCollection, which implements IEnumerable. Shouldn't the compiler be able to tell that the objects in the collection are of type ListItem?
Short Answer
No. The compile-time type is object because that's the compile-time type of IEnumerator.Current. The var keyword is still statically typed! If ListItemCollection implemented IEnumerable<T> instead of IEnumerable, then the compiler would know more.
More Details
From the docs:
... a System.Collections.IEnumerator [is] used to get items from the collection.
When you use a foreach loop, you're implicitly using the IEnumerator interface. So, when you access each item, the Current property returns an object even though it's a ListItem underneath at run-time.
Importantly, the var keyword is still static, so you might as well have written the following code. Would you expect the compiler to figure out this:
foreach (var item in (object)listControl.Items) {
item.Value = string.empty;
}
Your var is a statically-typed object. If you want the run-time to figure out that it's a ListItem, then you have to use dynamic instead of var.

Related

C#; InvalidCastException, what´s wrong?

I want to cast the Objects of an List to it´s Subclass.
For that I used Casting populated List<BaseClass> to List<ChildClass>
Now, when I execute the Code I always get an InvalidCastException in System.Core.dll ""The Object Item cannot be cast to SubItem "
[Serializable]
public class SubItem: Item
{
public SubItem(int id) : base( id)
{
}
}
public getStuff(int id){
//where the Error is thrown
//Inventory has Items in it, furthermore atm Inventory is definitely not empty, but could be later on
var items = Inventory.FindAll(x=> x.id==id).Cast<SubItem>().ToList();
}
Item is from an API, I am not able to change anything there.
Item inherits from IEquatable.
The LINQ is definitely working, but when I am casting I get the Error. What do i do wrong?
It could be that some of the items in your list are not of the derived class type, you should be able to see this in the debugger. Also as someone has pointed out you may have to pull the data back from the database before doing the cast, the linq provider may not be capable of doing that for you.
So you could try either of the following:
Filter out items that are not of SubItem
var items = Inventory.FindAll(x=> x.id==id).OfType<SubItem>().ToList();
Pull the data back from the database before casting
var items = Inventory.FindAll(x=> x.id==id).ToList().Cast<SubItem>().ToList();
Or a combination of the two if the linq provider cannot do the OfType
var items = Inventory.FindAll(x=> x.id==id).ToList().OfType<SubItem>().ToList();
I am going to assume that your property Inventory is strongly typed collection/list/IEnumerable of generic type Item (example: ICollection<Item>).
The InvalidCastException will occur when calling Cast if one or more items is not of the type specified in the generic argument. See details in the Microsoft page Enumerable.Cast. To get around that you need to only retrieve items of that type which will execute the cast on them during the filter. This can be done with Enumerable.OfType. Your code then becomes:
var items = Inventory.FindAll(x=> x.id==id).OfType<SubItem>().ToList();
From the documentation Enumerable.Cast
If an element cannot be cast to type TResult, this method will throw an exception. To obtain only those elements that can be cast to type TResult, use the OfType method instead of Cast(IEnumerable).
Edit based on these comments
Established that all items returned are of type Item and not SubItem. This explains the exception.
... said that the List consists of Item that´s why I want to downcast them to SubItem.
Your inheritance hierarchy as you have posted it in your question is System.Object -> Item -> SubItem. (I include Object as I am assuming we are dealing with reference types and also everything eventually inherits from System.Object). What this means is that a SubItem could be cast to an Item or an System.Object, an Item instance could be cast to a System.Object. However the inverse is not true, you cant cast up the graph. An instance of System.Object cannot become an Item and an Item cannot become a SubItem.
Having a generic List<Item> is fine, this could contain types SubItem or Item or anything that derives from Item. But again, you cannot cast an instance of Item to SubItem.
If this is not clear then I recommend you do some reading up on inheritance as well as Polymorphism which is a different topic but also very important if you want to know why inheritance is used and what makes it such a great tool for abstraction.
Assuming that Inventory contains a list of "Item" and you want to get a list of "SubItem" replace your line with this:
var items = Inventory.Where(x => x.id == id).Select(x => new SubItem(x.id)).ToList();
So the first part is going to get you the items that meet your conditions and the second part will create a new SubItem for each one and give you a list of them.

why does type inference break with SelectedItemCollection?

The ASPXCheckBoxList has a collection called 'SelectedItems' of type 'SelectedItemCollection' (it implements ICollection, IEnumerable)
for some reason, there's a difference between this:
foreach (ListEditItem si in cblCommonJointOwnership.SelectedItems)
{
//do stuff and have si.Value and si.Text and si.Foo
}
and this:
foreach (var si in cblCommonJointOwnership.SelectedItems)
{
//seems that si is just a plain object here...
}
I realize this isn't DevExpress specific.. so I suppose a general question: why does the 'var' version become inferred as an object instead of a ListEditItem?
Thanks
SelectedItemCollection implements IEnumerable but not IEnumerable<T>. Because of that, the compiler doesn't know anything about the type stored in the collection and has to assume the most generic type (which is Object).
Your first statement specifies the type. This actually will result in a cast of each item in the collection back to your specified type and would fail if any of the elements in the collection couldn't be properly cast.
Because SelectedItems is a specialized collection. It's not generic either. This makes it impossible for type inference to operate.
Put another way, the objects in that list could be anything; even of different types. Therefore, the collection doesn't provide any typed result.

foreach-ing through a listview and accessing subitems?

I'm having difficulty using a foreach statement with a WinForm ListView control. The following two code blocks demonstrates what I'm trying to do. It works with a for loop, but not a foreach.
foreach(var item in listView.Items){
item. <-Can't access any of the subitems of this item
}
vs
for(int i=0;i<listView.Items.Count;i++){
listView.Items[i].Subitems[1] <- Here I can access the sub items
}
I'm trying to use a foreach loop so I can more easily remove items from the ListView.
You need to specify the type:
foreach(ListViewItem item in listView.Items){
To answer your comments:
This is because most controls' item collections implement the non-generic ICollection (and IEnumerable), see this MSDN entry for ListViewItemCollection for example. Since it doesn't implement the generic ICollection<T> or IEnumerable<T>, the compiler can't guess the type of the items from looking at the collections themselves, so you have to tell it that they're of type ListViewItem instead of using var.
You need to specify the type if the item in the collection explicitly. The var keyword uses type inference in order to determine the type of the variable. In the case of var in a foreach clause, it uses the particular implementation of IEnumerable to determine the type.
If the collection only implements IEnumerable (and not a generic IEnumerable<T>), then var will be object
If the collection implements one generic IEnumerable<T> (say, IEnumerable<int>), then var will be T (in the example here, var would be int)
In your case, ListViewItemCollection does not implement any generic form of IEnumerable<T>, so var is assumed to be object. However, the compiler will allow you to specify a more specific type for the iterator variable if the enumerable only implements IEnumerable, and it automatically inserts a cast to that particular type.
Note that, because there's a casting operator, the cast will fail at runtime if the object is not of that particular type. For instance, I can do this:
List<object> foo = new List<object>();
foo.Add("bar");
foo.Add(1);
foreach(string bar in foo)
{
}
This is legal, but will fail when the iterator reaches the second item, since it is not a string.
You need to have the type of the item - in this case: ListViewItem.
Also, if you're planning to remove items from the collection and are using a foreach loop, you cannot directly remove from the you're looping through - you'd need to add each item to remove to a new collection and remove all items in that collection from the original after the termination of the loop.
Use the beautiful collection caster of LINQ
using System.Linq;
foreach(var item in listView.Items.Cast<ListViewItem>()){
item.BackColor = ...
}

How to know which type to use when use foreach on an IEnumerable type?

I have an object which implements IEnumerable interface. In C#, I can use foreach to iterate all its element. However I am wondering how to determine the type in the foreach loop?
If you have an old style IEnumerable (not IEnumerable<T>) you can usually call .Cast<T> or .OfType<T> to get a strongly typed one to .ForEach<T> over...
If you don't know T then you can call Debug.Write(item.GetType())
If you are using only IEnumerable you have to use object. If you know the real type then you can cast that object.
You can also use the generic IEnumberable and then you won't need to do the cast.
Why don't you try var?
foreach(var item in myEnumerable)
{
...
}
Without having the generic variant of IEumerable there is no nice way of knowing.
You could use the is operator to check first item in enumerable along with different foreach loops but I wouldn't recomend it.
If you are using the non-generic IEnumerable you cannot infer the type of items returned through the interface at compile-time. If you know enough about the object, you may know it only contains a specific type, which means you can (with relative safety) construct a foreach loop with the known type. For example, if you know it only returns strings, you can iterate over the object like so:
foreach(string s in myObject)
{
// Do some operation.
}
Unfortunately, you can return any item from a non-generic IEnumerable. If the IEnumerable returns a float, you will receive an InvalidCastException on the first line.
Worse still, there's nothing to prevent the elements being a variety of types within the same sequence. The object could legally return the following types within one foreach loop.
string
int
float
DateTime
If you don't know from the object's documentation, the only other certain way to know the type of any one item in the loop is to check at runtime.
These are the reasons why it's always preferable to use the generic IEnumerable<T> over IEnumerable when you know your object will only really return one type of value.

var keyword isn't inferring the type of RepeaterItem, why is that?

This is a quick one. I have the following code:
foreach (var item in myRepeater.Items)
{
MyViewModelItem x = new MyViewModelItem();
MapToEntity(x, item);
myList.Add(report);
}
void MapToEntity(object entity, Control control);
I expected this code to compile with no problems. It didn't, however.
It resulted in a compile time error saying that the method "MapToEntity" has some invalid arguments. The compiler failed to infer the type of the RepeaterItem, it recognizes it as a plain System.Object.
Why is this happening? Am I missing something?
Ps: I fixed the code by deleting the var keyword and explicitly defining the type of the item "RepeaterItem".
RepeaterItemCollection does not implement IEnumerable<RepeaterItem> just plain IEnumerable. Thus, it's impossible for the compiler in infer the type.
First, your code sample shows that you are using a variable named "item" in the foreach statement and then declaring one of another type below it.
Second the reason you probably seeing it as type Object is that myRepeater.Items is probably a general collection and not a specifically typed one so it would return of type Object. You can specifically state of what type in the ForEach loop and it will return objects of that type if any exist.
A possible solution would be to do myRepeater.Items.OfType() and then you could use the var keyword.
As Anton said, the Items only implements IEnumerable which is why it cannot infer the type.
One thing that you may find useful though is the Cast method.
myRepeater.Items.Cast<RepeaterItem>()
Whilst your example is simple enough for this to not really be needed it may help for more complex examples where you need a typed enumerable.

Categories

Resources