- 
                Notifications
    
You must be signed in to change notification settings  - Fork 5.2k
 
Description
Background and motivation
Interface-based programming is encouraged as a best practice. Unfortunately, enumerating collections that are exposed through interface types triggers memory allocations and involves 2 interface calls per item iterated. In other words, enumerating an interface is slow:
List<int> l0 = new List<int>();
IList<int> l1 = new List<int>();
foreach (var x in l0)
{
    // speedy due to List<T>.Enumerator
}
foreach (var x in l1)
{
    // slow due to allocating an enumerator object, and making 2 interface calls
    // (IEnumerator<T>.MoveNext and IEnumerator<T>.Current) for each item
}API Proposal
Please see https://github.com/geeknoid/AcceleratedEnumeration for a simple model that can deliver considerable perf benefits by avoiding allocations in many cases and cutting down the number of interface calls. The README file in that repo has perf numbers. The code I show provides benefits for most enumerations of IEnumerable, IList, IReadOnlyList, IDictionary, IReadOnlyDictionary, ISet, and IReadOnlySet.
The basic idea is to do something like:
foreach (var x in l.AcceleratedEnum())
{
}The above works like normal enumeration, just faster. Although the model I show here requires explicit coding by the programmer to leverage, it would be conceivable to build similar tech directly into C# so that enumeration of interface types just gets faster by magic.
Note that my implementation also handles null collections, which is a nice simplification for the developer. You don't need to do an if check before you enumerate.
API Usage
foreach (var x in l.AcceleratedEnum())
{
}Alternative Designs
As I mentioned above, you can imagine building this ugly stuff straight into the C# compiler. In fact, putting this into the compiler can likely deliver perf benefits by using byref semantics for the enumerator structs, which would avoid copying overhead. This is not possible right now since the C# compiler doesn't know how to deal with enumerators by reference.
Risks
The design intentionally avoids extra error checks, so edge cases will throw different exceptions than in the non-accelerated case. In particular, the implementation of IEnumerator.Current when there isn't anything in the collection throws different exceptions. That could be remedied at the cost of a few cycles.