Apologies if I’m in the wrong forum, this seemed the most appropriate although I suppose I’m not technically asking for support…
I’m trying to gauge what dependencies people rely on when working with collections of objects to add support for my asset - mostly regarding parameter types in method signatures as opposed to object fields.
Personally, I usually start with Lists and tend to abstract into IEnumerable if I don’t need List functionality. I don’t think I’ve ever depended on IList and I’ll only use arrays internally when I can get away with it. Anyone else work differently?
Small sample so far, but I’d be willing to bet it’s a good representation of the greater set.
Anybody have a particular reason for using List over IList? I think the only functionality I’ve noticed lacking in the interface is a ForEach method, but that can easily be extended off IEnumerable.
The question is really pretty vague. It totally depends on what you’re doing. Most times I use IEnumerable because it can represent Arrays, Lists, etc. There are times when I know my collection will have a fixed size and I’ll only access by index so I’ll use an array directly, but if I need to be able to depend on it to convert to any time (i.e. calling ToArray or ToList) then I’ll use IEnumerable. Keep in mind that List implements both IList and IEnumerable. Also know that there can be some issues with AOT on iOS when doing a ForEach on an IEnumerable. The benefit of using List directly is that it supports indexing so rather than doing a:
foreach(var itm in MyItemEnumerable)
{
}
You can do:
for(var i = 0; i < MyItemList.Count; i++)
{
}
This has the benefit of being safer for AOT as you’re not getting an enumerator to step through the collection and it also saves from adding another object on the heap (as happens with a foreach).
Hey, thanks for chiming in!
I just realized that there’s a difference between IList vs IList and IEnumerable vs IEnumerable. Oddly enough, though IEnumerable implements IEnumerable, IList does not implement IList. Anyways, I meant to be referring to the generic variants.
An actual array (as in T[ ] vs the Array class) explicitly implements both IList and IList, which both expose indexing functionality. IList also implements IEnumerable, which would give one the ToArray(), ToList() extension methods. It does leave me thinking that I should generally be using IList over the actual implementation of List. And yet I hesitate. Maybe just not used to it.
I’ve never heard of there being issues with doing a foreach on iOS - that’s something worth considering, any more details about that? And good point about the extra object on the heap using foreach, although I wonder if the effect of that is negligible?
What it really boils down to for you probably is extensibility. If it’s an internal function and you know you’re only ever going to be passing a specific type, then use that type (such as List). But, if you want people to be able to pass in their own derived collections of some type, or think you may be in the future, then use IList or IEnumerable. That would let you even define your own collection types.
As for the iOS, it’s specifically with foreach on IEnumerable. Evidently, the GetEnumerator() method in mono when using AOT sometimes executes the non-generic version and returns a string. There are some workarounds. Here is a workaround and at the end is my proposed alternative that uses a delegate to execute the GetEnumerator method rather than calling Invoke. The delegate approach offers more speed if you’re going to be doing a lot of enumerations.
http://forum.unity3d.com/threads/168019-quot-System-String-doesn-t-implement-interface-System-Collections-IEnumerator-quot-crash
As for the extra object on the heap, this is because of the call to GetEnumerator. It may get even worse using the workarounds for AOT as it will result in boxing and unboxing for lists of value types (i.e. List) but it’s something you just have to accept. Now the real problem comes in the following scenario. Let’s say I have two IEnumerable instances. Each one has a property called “Target”. Now they also have a Group property. This isn’t really important but if items in ListB have the same target as items in ListA, then I want to add the item in ListB to assign the same group.
foreach(var itm in listA)
{
foreach(var itm2 in listB)
{
if(itm2.Target == itm.Target)
itm2.Group = itm.Group;
}
}
While this is not a super useful example, it’s actually a fairly common practice. So, one enumerator will be created for the outer foreach loop, but for every single loop of listA, a new enumerator is created for listB. In this case it’s much better to use an indexer.
Really it just depends on where you want to trade performance and flexibility. There is no right or wrong or one better than the other. They’re each suited for difference scenarios.
Awesome man, thanks for the input. Will definitely keep that AOT issue in mind.