Can I cache partially-executed LINQ queries? - c#

I have the following code:
IEnumerable<KeyValuePair<T, double>> items =
sequence.Select(item => new KeyValuePair<T, double>(item, weight(item)));
if (items.Any(pair => pair.Value<0))
throw new ArgumentException("Item weights cannot be less than zero.");
double sum = items.Sum(pair => pair.Value);
foreach (KeyValuePair<T, double> pair in items) {...}
Where weight is a Func<T, double>.
The problem is I want weight to be executed as few times as possible. This means it should be executed at most once for each item. I could achieve this by saving it to an array. However, if any weight returns a negative value, I don't want to continue execution.
Is there any way to accomplish this easily within the LINQ framework?

Sure, that's totally doable:
public static Func<A, double> ThrowIfNegative<A, double>(this Func<A, double> f)
{
return a=>
{
double r = f(a);
// if r is NaN then this will throw.
if ( !(r >= 0.0) )
throw new Exception();
return r;
};
}
public static Func<A, R> Memoize<A, R>(this Func<A, R> f)
{
var d = new Dictionary<A, R>();
return a=>
{
R r;
if (!d.TryGetValue(a, out r))
{
r = f(a);
d.Add(a, r);
}
return r;
};
}
And now...
Func<T, double> weight = whatever;
weight = weight.ThrowIfNegative().Memoize();
and you're done.

One way is to move the exception into the weight function, or at least simulate doing so, by doing something like:
Func<T, double> weightWithCheck = i =>
{
double result = weight(i);
if (result < 0)
{
throw new ArgumentException("Item weights cannot be less than zero.");
}
return result;
};
IEnumerable<KeyValuePair<T, double>> items =
sequence.Select(item => new KeyValuePair<T, double>(item, weightWithCheck(item)));
double sum = items.Sum(pair => pair.Value);
By this point, if there is an exception to be had, you should have it. You do have to enumerate items before you can be assured of getting the exception, though, but once you get it, you will not call weight again.

Both answers are good (where to throw the exception, and memoizing the function).
But your real problem is that your LINQ expression is evaluated every time you use it, unless you force it to evaluate and store as a List (or similar). Just change this:
sequence.Select(item => new KeyValuePair<T, double>(item, weight(item)));
To this:
sequence.Select(item => new KeyValuePair<T, double>(item, weight(item))).ToList();

You could possibly do it with a foreach loop. Here is a way to do it in one statement:
IEnumerable<KeyValuePair<T, double>> items = sequence
.Select(item => new KeyValuePair<T, double>(item, weight(item)))
.Select(kvp =>
{
if (kvp.Value < 0)
throw new ArgumentException("Item weights cannot be less than zero.");
else
return kvp;
}
);

No, there is nothing already IN the LINQ framework to do this, but you could surely write up your own methods and invoke them from the linq query (As has already been shown by many).
Personally, I would either ToList the first query or use Eric's suggestion.

Instead of a functional memoization suggested by other answers, you can also employ a memoization for the whole data sequence:
var items = sequence
.Select(item => new KeyValuePair<T, double>(item, weight(item)))
.Memoize();
(Note a call to Memoize() method at the end of the expression above)
A nice property of data memoization is that it represents a drop-in replacement for ToList() or ToArray() approaches.
The fully featured implementation is pretty involved though:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
static class MemoizationExtensions
{
/// <summary>
/// Memoize all elements of a sequence, e.g. ensure that every element of a sequence is retrieved only once.
/// </summary>
/// <remarks>
/// The resulting sequence is not thread safe.
/// </remarks>
/// <typeparam name="T">The type of the elements of source.</typeparam>
/// <param name="source">The source sequence.</param>
/// <returns>The sequence that fully replicates the source with all elements being memoized.</returns>
public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source) => Memoize(source, false);
/// <summary>
/// Memoize all elements of a sequence, e.g. ensure that every element of a sequence is retrieved only once.
/// </summary>
/// <typeparam name="T">The type of the elements of source.</typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="isThreadSafe">Indicates whether resulting sequence is thread safe.</param>
/// <returns>The sequence that fully replicates the source with all elements being memoized.</returns>
public static IEnumerable<T> Memoize<T>(this IEnumerable<T> source, bool isThreadSafe)
{
switch (source)
{
case null:
return null;
case CachedEnumerable<T> existingCachedEnumerable:
if (!isThreadSafe || existingCachedEnumerable is ThreadSafeCachedEnumerable<T>)
{
// The source is already memoized with compatible parameters.
return existingCachedEnumerable;
}
break;
case IList<T> _:
case IReadOnlyList<T> _:
case string _:
// Given source types are intrinsically memoized by their nature.
return source;
}
if (isThreadSafe)
return new ThreadSafeCachedEnumerable<T>(source);
else
return new CachedEnumerable<T>(source);
}
class CachedEnumerable<T> : IEnumerable<T>, IReadOnlyList<T>
{
public CachedEnumerable(IEnumerable<T> source)
{
_Source = source;
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
IEnumerable<T> _Source;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
IEnumerator<T> _SourceEnumerator;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
protected readonly IList<T> Cache = new List<T>();
public virtual int Count
{
get
{
while (_TryCacheElementNoLock()) ;
return Cache.Count;
}
}
bool _TryCacheElementNoLock()
{
if (_SourceEnumerator == null && _Source != null)
{
_SourceEnumerator = _Source.GetEnumerator();
_Source = null;
}
if (_SourceEnumerator == null)
{
// Source enumerator already reached the end.
return false;
}
else if (_SourceEnumerator.MoveNext())
{
Cache.Add(_SourceEnumerator.Current);
return true;
}
else
{
// Source enumerator has reached the end, so it is no longer needed.
_SourceEnumerator.Dispose();
_SourceEnumerator = null;
return false;
}
}
public virtual T this[int index]
{
get
{
_EnsureItemIsCachedNoLock(index);
return Cache[index];
}
}
public IEnumerator<T> GetEnumerator() => new CachedEnumerator<T>(this);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal virtual bool EnsureItemIsCached(int index) => _EnsureItemIsCachedNoLock(index);
bool _EnsureItemIsCachedNoLock(int index)
{
while (Cache.Count <= index)
{
if (!_TryCacheElementNoLock())
return false;
}
return true;
}
internal virtual T GetCacheItem(int index) => Cache[index];
}
sealed class ThreadSafeCachedEnumerable<T> : CachedEnumerable<T>
{
public ThreadSafeCachedEnumerable(IEnumerable<T> source) :
base(source)
{
}
public override int Count
{
get
{
lock (Cache)
return base.Count;
}
}
public override T this[int index]
{
get
{
lock (Cache)
return base[index];
}
}
internal override bool EnsureItemIsCached(int index)
{
lock (Cache)
return base.EnsureItemIsCached(index);
}
internal override T GetCacheItem(int index)
{
lock (Cache)
return base.GetCacheItem(index);
}
}
sealed class CachedEnumerator<T> : IEnumerator<T>
{
CachedEnumerable<T> _CachedEnumerable;
const int InitialIndex = -1;
const int EofIndex = -2;
int _Index = InitialIndex;
public CachedEnumerator(CachedEnumerable<T> cachedEnumerable)
{
_CachedEnumerable = cachedEnumerable;
}
public T Current
{
get
{
var cachedEnumerable = _CachedEnumerable;
if (cachedEnumerable == null)
throw new InvalidOperationException();
var index = _Index;
if (index < 0)
throw new InvalidOperationException();
return cachedEnumerable.GetCacheItem(index);
}
}
object IEnumerator.Current => Current;
public void Dispose()
{
_CachedEnumerable = null;
}
public bool MoveNext()
{
var cachedEnumerable = _CachedEnumerable;
if (cachedEnumerable == null)
{
// Disposed.
return false;
}
if (_Index == EofIndex)
return false;
_Index++;
if (!cachedEnumerable.EnsureItemIsCached(_Index))
{
_Index = EofIndex;
return false;
}
else
{
return true;
}
}
public void Reset()
{
_Index = InitialIndex;
}
}
}
More info and a readily available NuGet package: https://github.com/gapotchenko/Gapotchenko.FX/tree/master/Source/Gapotchenko.FX.Linq#memoize

Related

How to apply contain on last record and delete if found in LINQ?

I have a list of strings like
AAPL,28/03/2012,88.34,88.778,87.187,88.231,163682382
AAPL,29/03/2012,87.54,88.08,86.747,87.123,151551216
FB,30/03/2012,86.967,87.223,85.42,85.65,182255227
Now I want to delete only last record if it does not contains AAPL(symbol name) using LINQ.
Below I have write my code which contains multiple line but I want to make it single line code,
fileLines = System.IO.File.ReadAllLines(fileName).AsParallel().Skip(1).ToList();
var lastLine = fileLines.Last();
if (!lastLine.Contains(item.sym))
{
fileLines.RemoveAt(fileLines.Count - 1);
}
So How can I make all it in single line linq query ?
You could use the ternary operator to decide on the tail to concatenate as follows.
fileLines
= fileLines.Take(fileLines.Count())
.Concat(fileLines.Last().Contains(item.sym) ? Enumerable.Empty
: new string[]{ item.sym });
You could formulate it even more contracted as follows.
fileLines
= System.IO.File.ReadAllLines(fileName)
.AsParallel()
.Skip(1)
.Take(fileLines.Count())
.Concat(fileLines.Last().Contains(item.sym) ? Enumerable.Empty
: new string[]{ item.sym });
.ToList();
That being said, such an endeavour is questionable. The accumulation of lazily evaluated Linq extension methods is difficult to debug.
I understand you need to simplify the filtering operation, and, from what I see in your case, you're missing only one piece of information (i.e whether or not current item is the last one in an enumerated collection) that will help you define your predicate. What I'm about to write now might not seem "a simple single line"; however, it's gonna be a reusable extension that will provide this piece of information (and more) without performing extra and unnecessary loops or iterations.
The final product of that will be:
IEnumerable<string> fileLines = System.IO.File.ReadLines(fileName).RichWhere((item, originalIndex, countedIndex, hasMoreItems) => hasMoreItems || item.StartsWith("AAPL"));
The LINQ-like extension that I wrote inspired by Microsoft's Enumerable at ReferenceSource:
public delegate bool RichPredicate<T>(T item, int originalIndex, int countedIndex, bool hasMoreItems);
public static class EnumerableExtensions
{
/// <remarks>
/// This was contributed by Aly El-Haddad as an answer to this Stackoverflow.com question:
/// https://stackoverflow.com/q/54829095/3602352
/// </remarks>
public static IEnumerable<T> RichWhere<T>(this IEnumerable<T> source, RichPredicate<T> predicate)
{
return new RichWhereIterator<T>(source, predicate);
}
private class RichWhereIterator<T> : IEnumerable<T>, IEnumerator<T>
{
private readonly int threadId;
private readonly IEnumerable<T> source;
private readonly RichPredicate<T> predicate;
private IEnumerator<T> enumerator;
private int state;
private int countedIndex = -1;
private int originalIndex = -1;
private bool hasMoreItems;
public RichWhereIterator(IEnumerable<T> source, RichPredicate<T> predicate)
{
threadId = Thread.CurrentThread.ManagedThreadId;
this.source = source ?? throw new ArgumentNullException(nameof(source));
this.predicate = predicate ?? ((item, originalIndex, countedIndex, hasMoreItems) => true);
}
public T Current { get; private set; }
object IEnumerator.Current => Current;
public void Dispose()
{
if (enumerator is IDisposable disposable)
disposable.Dispose();
enumerator = null;
originalIndex = -1;
countedIndex = -1;
hasMoreItems = false;
Current = default(T);
state = -1;
}
public bool MoveNext()
{
switch (state)
{
case 1:
enumerator = source.GetEnumerator();
if (!(hasMoreItems = enumerator.MoveNext()))
{
Dispose();
break;
}
++originalIndex;
state = 2;
goto case 2;
case 2:
if (!hasMoreItems) //last predicate returned true and that was the last item
{
Dispose();
break;
}
T current = enumerator.Current;
hasMoreItems = enumerator.MoveNext();
++originalIndex;
if (predicate(current, originalIndex - 1, countedIndex + 1, hasMoreItems))
{
++countedIndex;
Current = current;
return true;
}
else if (hasMoreItems)
{ goto case 2; }
//predicate returned false and there're no more items
Dispose();
break;
}
return false;
}
public void Reset()
{
Current = default(T);
hasMoreItems = false;
originalIndex = -1;
countedIndex = -1;
state = 1;
}
public IEnumerator<T> GetEnumerator()
{
if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0)
{
state = 1;
return this;
}
return new RichWhereIterator<T>(source, predicate) { state = 1 };
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
RichPredicate<T>, which could be thought of as Func<T, int, int, bool, bool> provide this information about each item:
item: the item to evaluate.
originalIndex: the index of that item in its original IEnumerable<T> source (the one which was directly passed to RichWhere).
countedIndex: the index of that item IF the predicate would evaluate to true.
hasMoreItems: tells whether or not this would be the last item from the original IEnumerable<T> source.

Creating a collection with a function to obtain the next member

I need to accumulate values into a collection, based on an arbitrary function. Each value is derived from calling a function on the previous value.
My current attempt:
public static T[] Aggregate<T>(this T source, Func<T, T> func)
{
var arr = new List<T> { };
var current = source;
while(current != null)
{
arr.Add(current);
current = func(current);
};
return arr.ToArray();
}
Is there a built-in .Net Framework function to do this?
This operation is usually called Unfold. There's no built-in version but it is implemented in FSharp.Core, so you could wrap that:
public static IEnumerable<T> Unfold<T, TState>(TState init, Func<TState, T> gen)
{
var liftF = new Converter<TState, Microsoft.FSharp.Core.FSharpOption<Tuple<T, TState>>>(x =>
{
var r = gen(x);
if (r == null)
{
return Microsoft.FSharp.Core.FSharpOption<Tuple<T, TState>>.None;
}
else
{
return Microsoft.FSharp.Core.FSharpOption<Tuple<T, TState>>.Some(Tuple.Create(r, x));
}
});
var ff = Microsoft.FSharp.Core.FSharpFunc<TState, Microsoft.FSharp.Core.FSharpOption<Tuple<T, TState>>>.FromConverter(liftF);
return Microsoft.FSharp.Collections.SeqModule.Unfold<TState, T>(ff, init);
}
public static IEnumerable<T> Unfold<T>(T source, Func<T, T> func)
{
return Unfold<T>(source, func);
}
however writing your own version would be simpler:
public static IEnumerable<T> Unfold<T>(T source, Func<T, T> func)
{
T current = source;
while(current != null)
{
yield return current;
current = func(current);
}
}
You are referring to an anamorphism as mentioned here linq-unfold-operator, which is the dual of a catamorphism.
Unfold is the dual of Aggregate. Aggregate exists in the .Net Framework; Unfold does not (for some unknown reason). Hence your confusion.
/// seeds: the initial data to unfold
/// stop: if stop(seed) is True, don't go any further
/// map: transform the seed into the final data
/// next: generate the next seed value from the current seed
public static IEnumerable<R> UnFold<T,R>(this IEnumerable<T> seeds, Predicate<T> stop,
Func<T,R> map, Func<T,IEnumerable<T>> next) {
foreach (var seed in seeds) {
if (!stop(seed)) {
yield return map(seed);
foreach (var val in next(seed).UnFold(stop, map, next))
yield return val;
}
}
}
Usage Example:
var parents = new[]{someType}.UnFold(t => t == null, t => t,
t => t.GetInterfaces().Concat(new[]{t.BaseType}))
.Distinct();

Distinct of items in dictionary<T,IEnumerable<T>>

I am Looking for a cleaner way to perform the following operation:
Filter out distinct values from a dictionary of type Dictionary<T,IEnumerable<T>> , based on the uniqueness of the Value (i.e. unique by one of the attributes of T in the IEnumerable<T>).
We can ignore the key on the dictionary. Can someone suggest a good way of achieving the above ?
Jurgen Camilleri has the piece for 'SelectMany().Distinct()'. For the comparer, we use the following comparer all the time to compare based on a property:
Usage
//this uses 'Product' for 'T'. If you want to just use 'T' you'd have to constrain it
var distinctValues = dictionary.SelectMany((entry) => entry.Value)
.Distinct(new LambdaEqualityComparer<Product>(p=>p.ProductName));
Code
public class LambdaEqualityComparer<T> : IEqualityComparer<T>
{
private Func<T,object> _action;
public LambdaEqualityComparer(Func<T, object> action)
{
_action = action;
}
public bool Equals(T x, T y)
{
var areEqual = baseCheck(x, y) ?? baseCheck(getObj(x), getObj(y));
if (areEqual != null)
{
return areEqual.Value;
}
return _action(x).Equals(_action(y));
}
public int GetHashCode(T obj)
{
return _action(obj).GetHashCode();
}
/// <summary>
/// True = return true
/// False = return false
/// null = continue evaluating
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
private bool? baseCheck(object x, object y)
{
if (x == null && y == null)
{
return true;
}
else if (x == null || y == null)
{
return false;
}
return null;
}
private object getObj(T t)
{
try
{
return _action(t);
}
catch (NullReferenceException)
{
}
return null;
}
}
This should work for your case:
IEnumerable<T> allValues = dictionary.SelectMany((entry) => entry.Value);
IEnumerable<T> distinctValues = allValues.Distinct(comparer);
Make sure that you create a comparer class which implements IEqualityComparer<T> so that Distinct() can differentiate between instances of T (unless T is a class which already has a comparer in the .NET Framework such as System.String).
I wrote a method that will merge dictionaries removing duplicates. Perhaps you could use the guts of this to perform something similar.
public static Dictionary<TKey, List<TValue>> MergeDictionaries<TKey, TValue>(this IEnumerable<Dictionary<TKey, List<TValue>>> dictionaries)
{
return dictionaries.SelectMany(dict => dict)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.SelectMany(value => value).Distinct().ToList());
}
Maybe, just this section:
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key, group => group.SelectMany(value => value).Distinct().ToList());

No generic implementation of OrderedDictionary?

There doesn't appear to be a generic implementation of OrderedDictionary (which is in the System.Collections.Specialized namespace) in .NET 3.5. Is there one that I'm missing?
I've found implementations out there to provide the functionality, but wondered if/why there isn't a generic implementation out-of-the-box and if anyone knows whether it's something in .NET 4.0?
Implementing a generic OrderedDictionary isn't terribly difficult, but it's unnecessarily time consuming and frankly this class is a huge oversight on Microsoft's part. There are multiple ways of implementing this, but I chose to use a KeyedCollection for my internal storage. I also chose to implement various methods for sorting the way that List<T> does since this is essentially a hybrid IList and IDictionary. I've included my implementation here for posterity.
Here's the interface. Notice that it includes System.Collections.Specialized.IOrderedDictionary, which is the non-generic version of this interface that was provided by Microsoft.
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace mattmc3.Common.Collections.Generic {
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IOrderedDictionary {
new TValue this[int index] { get; set; }
new TValue this[TKey key] { get; set; }
new int Count { get; }
new ICollection<TKey> Keys { get; }
new ICollection<TValue> Values { get; }
new void Add(TKey key, TValue value);
new void Clear();
void Insert(int index, TKey key, TValue value);
int IndexOf(TKey key);
bool ContainsValue(TValue value);
bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer);
new bool ContainsKey(TKey key);
new IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
new bool Remove(TKey key);
new void RemoveAt(int index);
new bool TryGetValue(TKey key, out TValue value);
TValue GetValue(TKey key);
void SetValue(TKey key, TValue value);
KeyValuePair<TKey, TValue> GetItem(int index);
void SetItem(int index, TValue value);
}
}
Here's the implementation along with helper classes:
// http://unlicense.org
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
namespace mattmc3.Common.Collections.Generic {
/// <summary>
/// A dictionary object that allows rapid hash lookups using keys, but also
/// maintains the key insertion order so that values can be retrieved by
/// key index.
/// </summary>
public class OrderedDictionary<TKey, TValue> : IOrderedDictionary<TKey, TValue> {
#region Fields/Properties
private KeyedCollection2<TKey, KeyValuePair<TKey, TValue>> _keyedCollection;
/// <summary>
/// Gets or sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get or set.</param>
public TValue this[TKey key] {
get {
return GetValue(key);
}
set {
SetValue(key, value);
}
}
/// <summary>
/// Gets or sets the value at the specified index.
/// </summary>
/// <param name="index">The index of the value to get or set.</param>
public TValue this[int index] {
get {
return GetItem(index).Value;
}
set {
SetItem(index, value);
}
}
public int Count {
get { return _keyedCollection.Count; }
}
public ICollection<TKey> Keys {
get {
return _keyedCollection.Select(x => x.Key).ToList();
}
}
public ICollection<TValue> Values {
get {
return _keyedCollection.Select(x => x.Value).ToList();
}
}
public IEqualityComparer<TKey> Comparer {
get;
private set;
}
#endregion
#region Constructors
public OrderedDictionary() {
Initialize();
}
public OrderedDictionary(IEqualityComparer<TKey> comparer) {
Initialize(comparer);
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary) {
Initialize();
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
public OrderedDictionary(IOrderedDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
Initialize(comparer);
foreach (KeyValuePair<TKey, TValue> pair in dictionary) {
_keyedCollection.Add(pair);
}
}
#endregion
#region Methods
private void Initialize(IEqualityComparer<TKey> comparer = null) {
this.Comparer = comparer;
if (comparer != null) {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key, comparer);
}
else {
_keyedCollection = new KeyedCollection2<TKey, KeyValuePair<TKey, TValue>>(x => x.Key);
}
}
public void Add(TKey key, TValue value) {
_keyedCollection.Add(new KeyValuePair<TKey, TValue>(key, value));
}
public void Clear() {
_keyedCollection.Clear();
}
public void Insert(int index, TKey key, TValue value) {
_keyedCollection.Insert(index, new KeyValuePair<TKey, TValue>(key, value));
}
public int IndexOf(TKey key) {
if (_keyedCollection.Contains(key)) {
return _keyedCollection.IndexOf(_keyedCollection[key]);
}
else {
return -1;
}
}
public bool ContainsValue(TValue value) {
return this.Values.Contains(value);
}
public bool ContainsValue(TValue value, IEqualityComparer<TValue> comparer) {
return this.Values.Contains(value, comparer);
}
public bool ContainsKey(TKey key) {
return _keyedCollection.Contains(key);
}
public KeyValuePair<TKey, TValue> GetItem(int index) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
}
return _keyedCollection[index];
}
/// <summary>
/// Sets the value at the index specified.
/// </summary>
/// <param name="index">The index of the value desired</param>
/// <param name="value">The value to set</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the index specified does not refer to a KeyValuePair in this object
/// </exception>
public void SetItem(int index, TValue value) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException("The index is outside the bounds of the dictionary: {0}".FormatWith(index));
}
var kvp = new KeyValuePair<TKey, TValue>(_keyedCollection[index].Key, value);
_keyedCollection[index] = kvp;
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
return _keyedCollection.GetEnumerator();
}
public bool Remove(TKey key) {
return _keyedCollection.Remove(key);
}
public void RemoveAt(int index) {
if (index < 0 || index >= _keyedCollection.Count) {
throw new ArgumentException(String.Format("The index was outside the bounds of the dictionary: {0}", index));
}
_keyedCollection.RemoveAt(index);
}
/// <summary>
/// Gets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to get.</param>
public TValue GetValue(TKey key) {
if (_keyedCollection.Contains(key) == false) {
throw new ArgumentException("The given key is not present in the dictionary: {0}".FormatWith(key));
}
var kvp = _keyedCollection[key];
return kvp.Value;
}
/// <summary>
/// Sets the value associated with the specified key.
/// </summary>
/// <param name="key">The key associated with the value to set.</param>
/// <param name="value">The the value to set.</param>
public void SetValue(TKey key, TValue value) {
var kvp = new KeyValuePair<TKey, TValue>(key, value);
var idx = IndexOf(key);
if (idx > -1) {
_keyedCollection[idx] = kvp;
}
else {
_keyedCollection.Add(kvp);
}
}
public bool TryGetValue(TKey key, out TValue value) {
if (_keyedCollection.Contains(key)) {
value = _keyedCollection[key].Value;
return true;
}
else {
value = default(TValue);
return false;
}
}
#endregion
#region sorting
public void SortKeys() {
_keyedCollection.SortByKeys();
}
public void SortKeys(IComparer<TKey> comparer) {
_keyedCollection.SortByKeys(comparer);
}
public void SortKeys(Comparison<TKey> comparison) {
_keyedCollection.SortByKeys(comparison);
}
public void SortValues() {
var comparer = Comparer<TValue>.Default;
SortValues(comparer);
}
public void SortValues(IComparer<TValue> comparer) {
_keyedCollection.Sort((x, y) => comparer.Compare(x.Value, y.Value));
}
public void SortValues(Comparison<TValue> comparison) {
_keyedCollection.Sort((x, y) => comparison(x.Value, y.Value));
}
#endregion
#region IDictionary<TKey, TValue>
void IDictionary<TKey, TValue>.Add(TKey key, TValue value) {
Add(key, value);
}
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
return ContainsKey(key);
}
ICollection<TKey> IDictionary<TKey, TValue>.Keys {
get { return Keys; }
}
bool IDictionary<TKey, TValue>.Remove(TKey key) {
return Remove(key);
}
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
return TryGetValue(key, out value);
}
ICollection<TValue> IDictionary<TKey, TValue>.Values {
get { return Values; }
}
TValue IDictionary<TKey, TValue>.this[TKey key] {
get {
return this[key];
}
set {
this[key] = value;
}
}
#endregion
#region ICollection<KeyValuePair<TKey, TValue>>
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) {
_keyedCollection.Add(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear() {
_keyedCollection.Clear();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
_keyedCollection.CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<TKey, TValue>>.Count {
get { return _keyedCollection.Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return false; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) {
return _keyedCollection.Remove(item);
}
#endregion
#region IEnumerable<KeyValuePair<TKey, TValue>>
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IEnumerable
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
#endregion
#region IOrderedDictionary
IDictionaryEnumerator IOrderedDictionary.GetEnumerator() {
return new DictionaryEnumerator<TKey, TValue>(this);
}
void IOrderedDictionary.Insert(int index, object key, object value) {
Insert(index, (TKey)key, (TValue)value);
}
void IOrderedDictionary.RemoveAt(int index) {
RemoveAt(index);
}
object IOrderedDictionary.this[int index] {
get {
return this[index];
}
set {
this[index] = (TValue)value;
}
}
#endregion
#region IDictionary
void IDictionary.Add(object key, object value) {
Add((TKey)key, (TValue)value);
}
void IDictionary.Clear() {
Clear();
}
bool IDictionary.Contains(object key) {
return _keyedCollection.Contains((TKey)key);
}
IDictionaryEnumerator IDictionary.GetEnumerator() {
return new DictionaryEnumerator<TKey, TValue>(this);
}
bool IDictionary.IsFixedSize {
get { return false; }
}
bool IDictionary.IsReadOnly {
get { return false; }
}
ICollection IDictionary.Keys {
get { return (ICollection)this.Keys; }
}
void IDictionary.Remove(object key) {
Remove((TKey)key);
}
ICollection IDictionary.Values {
get { return (ICollection)this.Values; }
}
object IDictionary.this[object key] {
get {
return this[(TKey)key];
}
set {
this[(TKey)key] = (TValue)value;
}
}
#endregion
#region ICollection
void ICollection.CopyTo(Array array, int index) {
((ICollection)_keyedCollection).CopyTo(array, index);
}
int ICollection.Count {
get { return ((ICollection)_keyedCollection).Count; }
}
bool ICollection.IsSynchronized {
get { return ((ICollection)_keyedCollection).IsSynchronized; }
}
object ICollection.SyncRoot {
get { return ((ICollection)_keyedCollection).SyncRoot; }
}
#endregion
}
public class KeyedCollection2<TKey, TItem> : KeyedCollection<TKey, TItem> {
private const string DelegateNullExceptionMessage = "Delegate passed cannot be null";
private Func<TItem, TKey> _getKeyForItemDelegate;
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate)
: base() {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
public KeyedCollection2(Func<TItem, TKey> getKeyForItemDelegate, IEqualityComparer<TKey> comparer)
: base(comparer) {
if (getKeyForItemDelegate == null) throw new ArgumentNullException(DelegateNullExceptionMessage);
_getKeyForItemDelegate = getKeyForItemDelegate;
}
protected override TKey GetKeyForItem(TItem item) {
return _getKeyForItemDelegate(item);
}
public void SortByKeys() {
var comparer = Comparer<TKey>.Default;
SortByKeys(comparer);
}
public void SortByKeys(IComparer<TKey> keyComparer) {
var comparer = new Comparer2<TItem>((x, y) => keyComparer.Compare(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void SortByKeys(Comparison<TKey> keyComparison) {
var comparer = new Comparer2<TItem>((x, y) => keyComparison(GetKeyForItem(x), GetKeyForItem(y)));
Sort(comparer);
}
public void Sort() {
var comparer = Comparer<TItem>.Default;
Sort(comparer);
}
public void Sort(Comparison<TItem> comparison) {
var newComparer = new Comparer2<TItem>((x, y) => comparison(x, y));
Sort(newComparer);
}
public void Sort(IComparer<TItem> comparer) {
List<TItem> list = base.Items as List<TItem>;
if (list != null) {
list.Sort(comparer);
}
}
}
public class Comparer2<T> : Comparer<T> {
//private readonly Func<T, T, int> _compareFunction;
private readonly Comparison<T> _compareFunction;
#region Constructors
public Comparer2(Comparison<T> comparison) {
if (comparison == null) throw new ArgumentNullException("comparison");
_compareFunction = comparison;
}
#endregion
public override int Compare(T arg1, T arg2) {
return _compareFunction(arg1, arg2);
}
}
public class DictionaryEnumerator<TKey, TValue> : IDictionaryEnumerator, IDisposable {
readonly IEnumerator<KeyValuePair<TKey, TValue>> impl;
public void Dispose() { impl.Dispose(); }
public DictionaryEnumerator(IDictionary<TKey, TValue> value) {
this.impl = value.GetEnumerator();
}
public void Reset() { impl.Reset(); }
public bool MoveNext() { return impl.MoveNext(); }
public DictionaryEntry Entry {
get {
var pair = impl.Current;
return new DictionaryEntry(pair.Key, pair.Value);
}
}
public object Key { get { return impl.Current.Key; } }
public object Value { get { return impl.Current.Value; } }
public object Current { get { return Entry; } }
}
}
And no implementation would be complete without a few tests (but tragically, SO won't let me post that much code in one post), so I'll have to leave you to write your tests. But, I left a few of them in so that you could get an idea of how it works:
// http://unlicense.org
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using mattmc3.Common.Collections.Generic;
namespace mattmc3.Tests.Common.Collections.Generic {
[TestClass]
public class OrderedDictionaryTests {
private OrderedDictionary<string, string> GetAlphabetDictionary(IEqualityComparer<string> comparer = null) {
OrderedDictionary<string, string> alphabet = (comparer == null ? new OrderedDictionary<string, string>() : new OrderedDictionary<string, string>(comparer));
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(c.ToString(), c.ToString().ToUpper());
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
private List<KeyValuePair<string, string>> GetAlphabetList() {
var alphabet = new List<KeyValuePair<string, string>>();
for (var a = Convert.ToInt32('a'); a <= Convert.ToInt32('z'); a++) {
var c = Convert.ToChar(a);
alphabet.Add(new KeyValuePair<string, string>(c.ToString(), c.ToString().ToUpper()));
}
Assert.AreEqual(26, alphabet.Count);
return alphabet;
}
[TestMethod]
public void TestAdd() {
var od = new OrderedDictionary<string, string>();
Assert.AreEqual(0, od.Count);
Assert.AreEqual(-1, od.IndexOf("foo"));
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
Assert.AreEqual(0, od.IndexOf("foo"));
Assert.AreEqual(od[0], "bar");
Assert.AreEqual(od["foo"], "bar");
Assert.AreEqual(od.GetItem(0).Key, "foo");
Assert.AreEqual(od.GetItem(0).Value, "bar");
}
[TestMethod]
public void TestRemove() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.Remove("foo");
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestRemoveAt() {
var od = new OrderedDictionary<string, string>();
od.Add("foo", "bar");
Assert.AreEqual(1, od.Count);
od.RemoveAt(0);
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestClear() {
var od = GetAlphabetDictionary();
Assert.AreEqual(26, od.Count);
od.Clear();
Assert.AreEqual(0, od.Count);
}
[TestMethod]
public void TestOrderIsPreserved() {
var alphabetDict = GetAlphabetDictionary();
var alphabetList = GetAlphabetList();
Assert.AreEqual(26, alphabetDict.Count);
Assert.AreEqual(26, alphabetList.Count);
var keys = alphabetDict.Keys.ToList();
var values = alphabetDict.Values.ToList();
for (var i = 0; i < 26; i++) {
var dictItem = alphabetDict.GetItem(i);
var listItem = alphabetList[i];
var key = keys[i];
var value = values[i];
Assert.AreEqual(dictItem, listItem);
Assert.AreEqual(key, listItem.Key);
Assert.AreEqual(value, listItem.Value);
}
}
[TestMethod]
public void TestTryGetValue() {
var alphabetDict = GetAlphabetDictionary();
string result = null;
Assert.IsFalse(alphabetDict.TryGetValue("abc", out result));
Assert.IsNull(result);
Assert.IsTrue(alphabetDict.TryGetValue("z", out result));
Assert.AreEqual("Z", result);
}
[TestMethod]
public void TestEnumerator() {
var alphabetDict = GetAlphabetDictionary();
var keys = alphabetDict.Keys.ToList();
Assert.AreEqual(26, keys.Count);
var i = 0;
foreach (var kvp in alphabetDict) {
var value = alphabetDict[kvp.Key];
Assert.AreEqual(kvp.Value, value);
i++;
}
}
[TestMethod]
public void TestInvalidIndex() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict[100];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("index is outside the bounds"));
}
}
[TestMethod]
public void TestMissingKey() {
var alphabetDict = GetAlphabetDictionary();
try {
var notGonnaWork = alphabetDict["abc"];
Assert.IsTrue(false, "Exception should have thrown");
}
catch (Exception ex) {
Assert.IsTrue(ex.Message.Contains("key is not present"));
}
}
[TestMethod]
public void TestUpdateExistingValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
alphabetDict[2] = "CCC";
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "CCC");
}
[TestMethod]
public void TestInsertValue() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("c"));
Assert.AreEqual(alphabetDict[2], "C");
Assert.AreEqual(26, alphabetDict.Count);
Assert.IsFalse(alphabetDict.ContainsValue("ABC"));
alphabetDict.Insert(2, "abc", "ABC");
Assert.IsTrue(alphabetDict.ContainsKey("c"));
Assert.AreEqual(2, alphabetDict.IndexOf("abc"));
Assert.AreEqual(alphabetDict[2], "ABC");
Assert.AreEqual(27, alphabetDict.Count);
Assert.IsTrue(alphabetDict.ContainsValue("ABC"));
}
[TestMethod]
public void TestValueComparer() {
var alphabetDict = GetAlphabetDictionary();
Assert.IsFalse(alphabetDict.ContainsValue("a"));
Assert.IsTrue(alphabetDict.ContainsValue("a", StringComparer.OrdinalIgnoreCase));
}
[TestMethod]
public void TestSortByKeys() {
var alphabetDict = GetAlphabetDictionary();
var reverseAlphabetDict = GetAlphabetDictionary();
Comparison<string> stringReverse = ((x, y) => (String.Equals(x, y) ? 0 : String.Compare(x, y) >= 1 ? -1 : 1));
reverseAlphabetDict.SortKeys(stringReverse);
for (int j = 0, k = 25; j < alphabetDict.Count; j++, k--) {
var ascValue = alphabetDict.GetItem(j);
var dscValue = reverseAlphabetDict.GetItem(k);
Assert.AreEqual(ascValue.Key, dscValue.Key);
Assert.AreEqual(ascValue.Value, dscValue.Value);
}
}
-- UPDATE --
Source for this and other really useful missing core .NET libraries here: https://github.com/mattmc3/dotmore/blob/master/dotmore/Collections/Generic/OrderedDictionary.cs
You're right. There's no generic equivalent of OrderedDictionary in the framework itself.
(That's still the case for .NET 4 too, as far as I'm aware.)
For the record, there is a generic KeyedCollection that allows objects to be indexed by an int and a key. The key must be embedded in the value.
Here's a bizarre find: the System.Web.Util namespace in System.Web.Extensions.dll contains a generic OrderedDictionary<TKey,TValue>
// Type: System.Web.Util.OrderedDictionary`2
// Assembly: System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Web.Extensions.dll
namespace System.Web.Util
{
internal class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable
Not sure why MS placed it there instead of the System.Collections.Generic package, but I assume you can simply copy paste the code and use it (it's internal, so can't use it directly). Looks like the implementation uses a standard dictionary and separate Key/Value lists. Pretty straightforward...
Source code: https://referencesource.microsoft.com/#System.Web.Extensions/Util/OrderedDictionary.cs
A different implementation in System.Runtime.Collections that wraps the non-generic System.Collections.Specialized.OrderedDictionary: https://referencesource.microsoft.com/#System.ServiceModel.Internals/System/Runtime/Collections/OrderedDictionary.cs
For what it's worth, here is how I solved it:
public class PairList<TKey, TValue> : List<KeyValuePair<TKey, TValue>> {
Dictionary<TKey, int> itsIndex = new Dictionary<TKey, int>();
public void Add(TKey key, TValue value) {
Add(new KeyValuePair<TKey, TValue>(key, value));
itsIndex.Add(key, Count-1);
}
public TValue Get(TKey key) {
var idx = itsIndex[key];
return this[idx].Value;
}
}
It can be initialized like this:
var pairList = new PairList<string, string>
{
{ "pitcher", "Ken" },
{ "catcher", "Brad"},
{ "left fielder", "Stan"},
};
and accessed like this:
foreach (var pair in pairList)
{
Console.WriteLine("position: {0}, player: {1}",
pair.Key, pair.Value);
}
// Guaranteed to print in the order of initialization
A major conceptual problem with a generic version of OrderedDictionary is that users of a OrderedDictionary<TKey,TValue> would expect expect to be able to index it either numerically using an int, or by lookup using a TKey. When the only type of key was Object, as was the case with non-generic OrderedDictionary, the type of argument passed to the indexer would be sufficient to distinguish whether what type of indexing operation should be performed. As it is, though, it's unclear how the indexer of an OrderedDictionary<int, TValue> should behave.
If classes like Drawing.Point had recommended and followed a rule that piecewise-mutable structures should expose their mutable elements as fields rather than properties, and refrain from using property setters that modify this, then an OrderedDictionary<TKey,TValue> could efficiently expose a ByIndex property that returned an Indexer struct which held a reference to the dictionary, and had an indexed property whose getter and setter would call GetByIndex and SetByIndex upon it. Thus, one could say something like MyDict.ByIndex[5] += 3; to add 3 to the sixth element of the dictionary.
Unfortunately, for the compiler to accept such a thing, it would be necessary to make the ByIndex property return a new class instance rather than a struct every time it's invoked, eliminating the advantages one would get by avoiding boxing.
In VB.NET, one could get around that issue by using a named indexed property (so MyDict.ByIndex[int] would be a member of MyDict, rather than requiring MyDict.ByIndex to be a member of MyDict which includes an indexer), but C# doesn't allow such things.
It might still have been worthwhile to offer an OrderedDictionary<TKey,TValue> where TKey:class, but much of the reason for providing generics in the first place was to allow their use with value types.
For a lot of purposes I've found one can get by with a List<KeyValuePair<K, V>>. (Not if you need it to extend Dictionary, obviously, and not if you need better than O(n) key-value lookup.)
Right, it's an unfortunate omission. I miss Python's OrderedDict
A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
So I wrote my own OrderedDictionary<K,V> class in C#. How does it work? It maintains two collections - a vanilla unordered dictionary and an ordered list of keys. With this solution, the standard dictionary operations keep their fast complexities, and look up by index is fast too.
https://gist.github.com/hickford/5137384
Here's the interface
/// <summary>
/// A dictionary that remembers the order that keys were first inserted. If a new entry overwrites an existing entry, the original insertion position is left unchanged. Deleting an entry and reinserting it will move it to the end.
/// </summary>
/// <typeparam name="TKey">The type of keys</typeparam>
/// <typeparam name="TValue">The type of values</typeparam>
public interface IOrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
/// <summary>
/// The value of the element at the given index.
/// </summary>
TValue this[int index] { get; set; }
/// <summary>
/// Find the position of an element by key. Returns -1 if the dictionary does not contain an element with the given key.
/// </summary>
int IndexOf(TKey key);
/// <summary>
/// Insert an element at the given index.
/// </summary>
void Insert(int index, TKey key, TValue value);
/// <summary>
/// Remove the element at the given index.
/// </summary>
void RemoveAt(int index);
}
For those looking for an "official" package option in NuGet, an implementation of a generic OrderedDictionary has been accepted into .NET CoreFX Lab. If all goes well, the type will eventually be approved and integrated to the main .NET CoreFX repo.
There is a possibility that this implementation will be rejected.
The committed implementation can be referenced here
https://github.com/dotnet/corefxlab/blob/57be99a176421992e29009701a99a370983329a6/src/Microsoft.Experimental.Collections/Microsoft/Collections/Extensions/OrderedDictionary.cs
The NuGet package that definitively has this type available for use can be found here
https://www.nuget.org/packages/Microsoft.Experimental.Collections/1.0.6-e190117-3
Or you can install the package within Visual Studio. Browse for the package "Microsoft.Experimental.Collections" and make sure the "Include prerelease" checkbox is selected.
Will update this post if and when the type is made officially available.
There is SortedDictionary<TKey, TValue>. Although semantically close, I am not claiming it's the same as OrderedDictionary simply because they are not. Even from performance characteristics. However the very interesting and quite important difference between Dictionary<TKey, TValue> (and to that extent OrderedDictionary and implementations provided in answers) and SortedDictionary is that the latter is using binary tree underneath. This is critical distinction because it makes the class immune to memory constraints applied to generic class. See this thread about OutOfMemoryExceptions thrown when generic class is used for handling large set of key-value pairs.
How to figure out the max value for capacity parameter passed to Dictionary constructor to avoid OutOfMemoryException?
As a follow up to the comment from #V.B. here's an accessible implementation of the System.Runtime.Collections.OrderedDictionary<,>. I was originally going to access it by reflection and provide it via a factory but the dll this is in does not seem to be very accessible at all so I just pulled the source itself.
One thing to note is the indexer here will not throw KeyNotFoundException. I absolutely hate that convention and that was the 1 liberty i took in this implementation. If that's important to you, just replace the line for return default(TValue);. Uses C# 6 (compatible with Visual Studio 2013)
/// <summary>
/// System.Collections.Specialized.OrderedDictionary is NOT generic.
/// This class is essentially a generic wrapper for OrderedDictionary.
/// </summary>
/// <remarks>
/// Indexer here will NOT throw KeyNotFoundException
/// </remarks>
public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly OrderedDictionary _privateDictionary;
public OrderedDictionary()
{
_privateDictionary = new OrderedDictionary();
}
public OrderedDictionary(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) return;
_privateDictionary = new OrderedDictionary();
foreach (var pair in dictionary)
{
_privateDictionary.Add(pair.Key, pair.Value);
}
}
public bool IsReadOnly => false;
public int Count => _privateDictionary.Count;
int ICollection.Count => _privateDictionary.Count;
object ICollection.SyncRoot => ((ICollection)_privateDictionary).SyncRoot;
bool ICollection.IsSynchronized => ((ICollection)_privateDictionary).IsSynchronized;
bool IDictionary.IsFixedSize => ((IDictionary)_privateDictionary).IsFixedSize;
bool IDictionary.IsReadOnly => _privateDictionary.IsReadOnly;
ICollection IDictionary.Keys => _privateDictionary.Keys;
ICollection IDictionary.Values => _privateDictionary.Values;
void IDictionary.Add(object key, object value)
{
_privateDictionary.Add(key, value);
}
void IDictionary.Clear()
{
_privateDictionary.Clear();
}
bool IDictionary.Contains(object key)
{
return _privateDictionary.Contains(key);
}
IDictionaryEnumerator IDictionary.GetEnumerator()
{
return _privateDictionary.GetEnumerator();
}
void IDictionary.Remove(object key)
{
_privateDictionary.Remove(key);
}
object IDictionary.this[object key]
{
get { return _privateDictionary[key]; }
set { _privateDictionary[key] = value; }
}
void ICollection.CopyTo(Array array, int index)
{
_privateDictionary.CopyTo(array, index);
}
public TValue this[TKey key]
{
get
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (_privateDictionary.Contains(key))
{
return (TValue) _privateDictionary[key];
}
return default(TValue);
}
set
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary[key] = value;
}
}
public ICollection<TKey> Keys
{
get
{
var keys = new List<TKey>(_privateDictionary.Count);
keys.AddRange(_privateDictionary.Keys.Cast<TKey>());
return keys.AsReadOnly();
}
}
public ICollection<TValue> Values
{
get
{
var values = new List<TValue>(_privateDictionary.Count);
values.AddRange(_privateDictionary.Values.Cast<TValue>());
return values.AsReadOnly();
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
public void Add(TKey key, TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
_privateDictionary.Add(key, value);
}
public void Clear()
{
_privateDictionary.Clear();
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
if (item.Key == null || !_privateDictionary.Contains(item.Key))
{
return false;
}
return _privateDictionary[item.Key].Equals(item.Value);
}
public bool ContainsKey(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
return _privateDictionary.Contains(key);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex));
if (array.Rank > 1 || arrayIndex >= array.Length
|| array.Length - arrayIndex < _privateDictionary.Count)
throw new ArgumentException("Bad Copy ToArray", nameof(array));
var index = arrayIndex;
foreach (DictionaryEntry entry in _privateDictionary)
{
array[index] =
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
index++;
}
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (DictionaryEntry entry in _privateDictionary)
{
yield return
new KeyValuePair<TKey, TValue>((TKey) entry.Key, (TValue) entry.Value);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
if (false == Contains(item)) return false;
_privateDictionary.Remove(item.Key);
return true;
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException(nameof(key));
if (false == _privateDictionary.Contains(key)) return false;
_privateDictionary.Remove(key);
return true;
}
public bool TryGetValue(TKey key, out TValue value)
{
if (key == null) throw new ArgumentNullException(nameof(key));
var keyExists = _privateDictionary.Contains(key);
value = keyExists ? (TValue) _privateDictionary[key] : default(TValue);
return keyExists;
}
}
Pull requests/discussion accepted on GitHub
I implemented a generic OrderedDictionary<TKey, TValue> by wraping around SortedList<TKey, TValue> and adding a private Dictionary<TKey, int> _order. Then I created an internal implementation of Comparer<TKey>, passing a reference to the _order dictionary. Then I use this comparer for the internal SortedList. This class keeps the order of elements passed to the constructor and order of additions.
This implementation has almost the same big O characteristics as SortedList<TKey, TValue> since adding and removing to _order is O(1). Each element will take (according to the book 'C# 4 in a Nutshell', p. 292, table 7-1) additional memory space of 22 (overhead) + 4 (int order) + TKey size (let's assume 8) = 34. Together with SortedList<TKey, TValue>'s overhead of two bytes, the total overhead is 36 bytes, while the same book says that non-generic OrderedDictionary has an overhead of 59 bytes.
If I pass sorted=true to constructor, then _order is not used at all, the OrderedDictionary<TKey, TValue> is exactly SortedList<TKey, TValue> with minor overhead for wrapping, if at all meaningful.
I am going to store not-so-many large reference objects in the OrderedDictionary<TKey, TValue>, so for me this ca. 36 bytes overhead is tolerable.
The main code is below. The complete updated code is on this gist.
public class OrderedList<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
{
private readonly Dictionary<TKey, int> _order;
private readonly SortedList<TKey, TValue> _internalList;
private readonly bool _sorted;
private readonly OrderComparer _comparer;
public OrderedList(IDictionary<TKey, TValue> dictionary, bool sorted = false)
{
_sorted = sorted;
if (dictionary == null)
dictionary = new Dictionary<TKey, TValue>();
if (_sorted)
{
_internalList = new SortedList<TKey, TValue>(dictionary);
}
else
{
_order = new Dictionary<TKey, int>();
_comparer = new OrderComparer(ref _order);
_internalList = new SortedList<TKey, TValue>(_comparer);
// Keep order of the IDictionary
foreach (var kvp in dictionary)
{
Add(kvp);
}
}
}
public OrderedList(bool sorted = false)
: this(null, sorted)
{
}
private class OrderComparer : Comparer<TKey>
{
public Dictionary<TKey, int> Order { get; set; }
public OrderComparer(ref Dictionary<TKey, int> order)
{
Order = order;
}
public override int Compare(TKey x, TKey y)
{
var xo = Order[x];
var yo = Order[y];
return xo.CompareTo(yo);
}
}
private void ReOrder()
{
var i = 0;
_order = _order.OrderBy(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => i++);
_comparer.Order = _order;
_lastOrder = _order.Values.Max() + 1;
}
public void Add(TKey key, TValue value)
{
if (!_sorted)
{
_order.Add(key, _lastOrder);
_lastOrder++;
// Very rare event
if (_lastOrder == int.MaxValue)
ReOrder();
}
_internalList.Add(key, value);
}
public bool Remove(TKey key)
{
var result = _internalList.Remove(key);
if (!_sorted)
_order.Remove(key);
return result;
}
// Other IDictionary<> + IDictionary members implementation wrapping around _internalList
// ...
}
This is not yet another version/solution of an OrderedDictionary<,> but an experiment I did testing each of 4 versions mentioned in the answers: of #Colonel Panic, #mattmc3, #V.B. #Chris Marisic. It is meant as a feedback. Well, partial because I have to admit I haven't dissected the code, so there may be differences in functionality or safety checks. But still, I thought feedback would be useful on their performance. And as you'll see time can get from a couple of milliseconds to a quarter of hour.
Then I scribbled a naive minimal version with 2 lists of key and value class objects with O(n) search just to see the magnitude of the benefit of O(1) access.
Testbed is Microsoft Visual Studio Community 2019 with Unity 3D, 4 consecutive times for each test and the code that I wanted to replicate a real-ish scenario in is
using System.Text;
using UnityEngine;
public class TessyOne : MonoBehaviour
{
public const int iterations = 50000;
private System.Diagnostics.Stopwatch stopwatch;
private System.Random random;
public float stopwatchDuration;
public class Ala
{
public int inta;
public float fla;
public string stra;
public Ben bena;
public Ala(int i, float f, string s, Ben b)
{
inta = i; fla = f; stra = s; bena = b;
}
}
public class Ben
{
public int inte;
public float fle;
public string stre;
public Ben(int i, float f, string s)
{
inte = i; fle = f; stre = s;
}
}
//public Naive.OrderedDictionary<Ala, Ben> alasToBens = new Naive.OrderedDictionary<Ala, Ben>();
//public Hickford.OrderedDictionary<Ala, Ben> alasToBens = new Hickford.OrderedDictionary<Ala, Ben>();
//public Mattmc3.OrderedDictionary<Ala, Ben> alasToBens = new Mattmc3.OrderedDictionary<Ala, Ben>();
public Marisic.OrderedDictionary<Ala, Ben> alasToBens = new Marisic.OrderedDictionary<Ala, Ben>();
//public VB.OrderedList<Ala, Ben> alasToBens = new VB.OrderedList<Ala, Ben>(null, false);
Ala[] alarray = new Ala[iterations];
Ben[] berray = new Ben[iterations];
// This is the entry point of the application
private void Start()
{
stopwatch = new System.Diagnostics.Stopwatch();
random = new System.Random(2020);
for(int i = 0; i < iterations; ++i)
{
berray[i] = new Ben(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)));
alarray[i] = new Ala(random.Next(),
(float)random.NextDouble(),
MakeRandomString((ushort)random.Next(1, 10)),
berray[i]);
// uncomment for testing ContainsKey() and Remove(), comment for Add()
alasToBens.Add(alarray[i], berray[i]);
}
stopwatch.Start();
for(int i = iterations - 1; i > -1; --i)
{
//alasToBens.Add(alarray[i], berray[i]);
//alasToBens.ContainsKey(alarray[i]);
alasToBens.Remove(alarray[i]);
}
stopwatch.Stop();
stopwatchDuration = stopwatch.ElapsedMilliseconds;
}
public string MakeRandomString(ushort length)
{
StringBuilder sb = new StringBuilder();
for(ushort u = 0; u < length; ++u)
{
sb.Append((char)Random.Range(33, 126)); // regular ASCII chars
}
return sb.ToString();
}
}
Note that the tests are for worst case scenarios in the case of naive version at least, as it iterates through the collection from index 0 through iterations and searching is done from end to start. I measured Add(), ContainsKey() and Remove() in milliseconds for a dictionary of 50000 entries.
Results:
+----------+----------------+----------------+--------------------------------+
| ms | Add() | ContainsKey() | Remove() |
+----------+----------------+----------------+--------------------------------+
| Hickford | 7, 8, 7, 8 | 2, 2, 3, 2 | 7400, 7503, 7419, 7421 |
| Mattmc3 | 23, 24, 24, 23 | 3, 3, 3, 3 | 890404, 913465, 875387, 877792 |
| Marisic | 27, 28, 28, 27 | 4, 4, 4, 4 | 27401, 27627, 27341, 27349 |
| V.B. | 76, 76, 75, 75 | 59, 60, 60, 60 | 66, 67, 67, 67 |
| | | | |
| Naive | 19651, 19761 | 25335, 25416 | 25259, 25306 |
+----------+----------------+----------------+--------------------------------+

Caching IEnumerable

public IEnumerable<ModuleData> ListModules()
{
foreach (XElement m in Source.Descendants("Module"))
{
yield return new ModuleData(m.Element("ModuleID").Value);
}
}
Initially the above code is great since there is no need to evaluate the entire collection if it is not needed.
However, once all the Modules have been enumerated once, it becomes more expensive to repeatedly query the XDocument when there is no change.
So, as a performance improvement:
public IEnumerable<ModuleData> ListModules()
{
if (Modules == null)
{
Modules = new List<ModuleData>();
foreach (XElement m in Source.Descendants("Module"))
{
Modules.Add(new ModuleData(m.Element("ModuleID").Value, 1, 1));
}
}
return Modules;
}
Which is great if I am repeatedly using the entire list but not so great otherwise.
Is there a middle ground where I can yield return until the entire list has been iterated, then cache it and serve the cache to subsequent requests?
You can look at Saving the State of Enumerators which describes how to create lazy list (which caches once iterated items).
Check out MemoizeAll() in the Reactive Extensions for .NET library (Rx). As it is evaluated lazily you can safely set it up during construction and just return Modules from ListModules():
Modules = Source.
Descendants("Module").
Select(m => new ModuleData(m.Element("ModuleID").Value, 1, 1)).
MemoizeAll();
There's a good explanation of MemoizeAll() (and some of the other less obvious Rx extensions) here.
I like #tsemer's answer. But I would like to propose my solutions, which has nothing to do with FP. It's naive approach, but it generates a lot less of allocations. And it is not thread safe.
public class CachedEnumerable<T> : IEnumerable<T>, IDisposable
{
IEnumerator<T> _enumerator;
readonly List<T> _cache = new List<T>();
public CachedEnumerable(IEnumerable<T> enumerable)
: this(enumerable.GetEnumerator())
{
}
public CachedEnumerable(IEnumerator<T> enumerator)
{
_enumerator = enumerator;
}
public IEnumerator<T> GetEnumerator()
{
// The index of the current item in the cache.
int index = 0;
// Enumerate the _cache first
for (; index < _cache.Count; index++)
{
yield return _cache[index];
}
// Continue enumeration of the original _enumerator,
// until it is finished.
// This adds items to the cache and increment
for (; _enumerator != null && _enumerator.MoveNext(); index++)
{
var current = _enumerator.Current;
_cache.Add(current);
yield return current;
}
if (_enumerator != null)
{
_enumerator.Dispose();
_enumerator = null;
}
// Some other users of the same instance of CachedEnumerable
// can add more items to the cache,
// so we need to enumerate them as well
for (; index < _cache.Count; index++)
{
yield return _cache[index];
}
}
public void Dispose()
{
if (_enumerator != null)
{
_enumerator.Dispose();
_enumerator = null;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
This is how the matrix test from #tsemer's answer will work:
var ints = new [] { 1, 2, 3, 4, 5 };
var cachedEnumerable = new CachedEnumerable<int>(ints);
foreach (var x in cachedEnumerable)
{
foreach (var y in cachedEnumerable)
{
//Do something
}
}
The outer loop (x) skips first for, because _cache is empty;
x fetches one item from the _enumerator to the _cache;
x pauses before second for loop;
The inner loop (y) enumerates one element from the _cache;
y fetches all elements from the _enumerator to the _cache;
y skips the third for loop, because its index variable equals 5;
x resumes, its index equals 1. It skips the second for loop because _enumerator is finished;
x enumerates one element from the _cache using the third for loop;
x pauses before the third for;
y enumerates 5 elements from the _cache using first for loop;
y skips the second for loop, because _enumerator is finished;
y skips the third for loop, because index of y equals 5;
x resumes, increments index. It fetches one element from the _cache using the third for loop.
x pauses.
if index variable of x is less than 5 then go to 10;
end.
I've seen a handful of implementations out there, some older and not taking advantage of newest .Net classes, some too elaborate for my needs. I ended up with the most concise and declarative code I could muster, which added up to a class with roughly 15 lines of (actual) code. It seems to align well with OP's needs:
Edit: Second revision, better support for empty enumerables
/// <summary>
/// A <see cref="IEnumerable{T}"/> that caches every item upon first enumeration.
/// </summary>
/// <seealso cref="http://blogs.msdn.com/b/matt/archive/2008/03/14/digging-deeper-into-lazy-and-functional-c.aspx"/>
/// <seealso cref="http://blogs.msdn.com/b/wesdyer/archive/2007/02/13/the-virtues-of-laziness.aspx"/>
public class CachedEnumerable<T> : IEnumerable<T> {
private readonly bool _hasItem; // Needed so an empty enumerable will not return null but an actual empty enumerable.
private readonly T _item;
private readonly Lazy<CachedEnumerable<T>> _nextItems;
/// <summary>
/// Initialises a new instance of <see cref="CachedEnumerable{T}"/> using <paramref name="item"/> as the current item
/// and <paramref name="nextItems"/> as a value factory for the <see cref="CachedEnumerable{T}"/> containing the next items.
/// </summary>
protected internal CachedEnumerable(T item, Func<CachedEnumerable<T>> nextItems) {
_hasItem = true;
_item = item;
_nextItems = new Lazy<CachedEnumerable<T>>(nextItems);
}
/// <summary>
/// Initialises a new instance of <see cref="CachedEnumerable{T}"/> with no current item and no next items.
/// </summary>
protected internal CachedEnumerable() {
_hasItem = false;
}
/// <summary>
/// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerable"/>.
/// Notice: The first item is always iterated through.
/// </summary>
public static CachedEnumerable<T> Create(IEnumerable<T> enumerable) {
return Create(enumerable.GetEnumerator());
}
/// <summary>
/// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerator"/>.
/// Notice: The first item is always iterated through.
/// </summary>
private static CachedEnumerable<T> Create(IEnumerator<T> enumerator) {
return enumerator.MoveNext() ? new CachedEnumerable<T>(enumerator.Current, () => Create(enumerator)) : new CachedEnumerable<T>();
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
public IEnumerator<T> GetEnumerator() {
if (_hasItem) {
yield return _item;
var nextItems = _nextItems.Value;
if (nextItems != null) {
foreach (var nextItem in nextItems) {
yield return nextItem;
}
}
}
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
A useful extension method could be:
public static class IEnumerableExtensions {
/// <summary>
/// Instantiates and returns a <see cref="CachedEnumerable{T}"/> for a given <paramref name="enumerable"/>.
/// Notice: The first item is always iterated through.
/// </summary>
public static CachedEnumerable<T> ToCachedEnumerable<T>(this IEnumerable<T> enumerable) {
return CachedEnumerable<T>.Create(enumerable);
}
}
And for the unit testers amongst you: (if you don't use resharper just take out the [SuppressMessage] attributes)
/// <summary>
/// Tests the <see cref="CachedEnumerable{T}"/> class.
/// </summary>
[TestFixture]
public class CachedEnumerableTest {
private int _count;
/// <remarks>
/// This test case is only here to emphasise the problem with <see cref="IEnumerable{T}"/> which <see cref="CachedEnumerable{T}"/> attempts to solve.
/// </remarks>
[Test]
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
[SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")]
public void MultipleEnumerationAreNotCachedForOriginalIEnumerable() {
_count = 0;
var enumerable = Enumerable.Range(1, 40).Select(IncrementCount);
enumerable.Take(3).ToArray();
enumerable.Take(10).ToArray();
enumerable.Take(4).ToArray();
Assert.AreEqual(17, _count);
}
/// <remarks>
/// This test case is only here to emphasise the problem with <see cref="IList{T}"/> which <see cref="CachedEnumerable{T}"/> attempts to solve.
/// </remarks>
[Test]
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
[SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")]
public void EntireListIsEnumeratedForOriginalListOrArray() {
_count = 0;
Enumerable.Range(1, 40).Select(IncrementCount).ToList();
Assert.AreEqual(40, _count);
_count = 0;
Enumerable.Range(1, 40).Select(IncrementCount).ToArray();
Assert.AreEqual(40, _count);
}
[Test]
[SuppressMessage("ReSharper", "ReturnValueOfPureMethodIsNotUsed")]
public void MultipleEnumerationsAreCached() {
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 40).Select(IncrementCount).ToCachedEnumerable();
cachedEnumerable.Take(3).ToArray();
cachedEnumerable.Take(10).ToArray();
cachedEnumerable.Take(4).ToArray();
Assert.AreEqual(10, _count);
}
[Test]
public void FreshCachedEnumerableDoesNotEnumerateExceptFirstItem() {
_count = 0;
Enumerable.Range(1, 40).Select(IncrementCount).ToCachedEnumerable();
Assert.AreEqual(1, _count);
}
/// <remarks>
/// Based on Jon Skeet's test mentioned here: http://www.siepman.nl/blog/post/2013/10/09/LazyList-A-better-LINQ-result-cache-than-List.aspx
/// </remarks>
[Test]
[SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")]
public void MatrixEnumerationIteratesAsExpectedWhileStillKeepingEnumeratedValuesCached() {
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 5).Select(IncrementCount).ToCachedEnumerable();
var matrixCount = 0;
foreach (var x in cachedEnumerable) {
foreach (var y in cachedEnumerable) {
matrixCount++;
}
}
Assert.AreEqual(5, _count);
Assert.AreEqual(25, matrixCount);
}
[Test]
public void OrderingCachedEnumerableWorksAsExpectedWhileStillKeepingEnumeratedValuesCached() {
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 5).Select(IncrementCount).ToCachedEnumerable();
var orderedEnumerated = cachedEnumerable.OrderBy(x => x);
var orderedEnumeratedArray = orderedEnumerated.ToArray(); // Enumerated first time in ascending order.
Assert.AreEqual(5, _count);
for (int i = 0; i < orderedEnumeratedArray.Length; i++) {
Assert.AreEqual(i + 1, orderedEnumeratedArray[i]);
}
var reorderedEnumeratedArray = orderedEnumerated.OrderByDescending(x => x).ToArray(); // Enumerated second time in descending order.
Assert.AreEqual(5, _count);
for (int i = 0; i < reorderedEnumeratedArray.Length; i++) {
Assert.AreEqual(5 - i, reorderedEnumeratedArray[i]);
}
}
private int IncrementCount(int value) {
_count++;
return value;
}
}
I quite like hazzik's answer...nice and simple is always the way.
BUT there is a bug in GetEnumerator
it sort of realises there is a problem, and thats why there is a strange 3rd loop after the 2nd enumerator loop....but it isnt quite as simple as that. The problem that triggers the need for the 3rd loop is general...so it needs to be recursive.
The answer though looks even simpler.
public IEnumerator<T> GetEnumerator()
{
int index = 0;
while (true)
{
if (index < _cache.Count)
{
yield return _cache[index];
index = index + 1;
}
else
{
if (_enumerator.MoveNext())
{
_cache.Add(_enumerator.Current);
}
else
{
yield break;
}
}
}
}
yes you can make it a tiny bit more efficient by yielding current...but I'll take the microsecond hit...it only ever happens once per element.
and its not threadsafe...but who cares about that.
Just to sum up things a bit:
In this answer a solution is presented, complete with an extension method for easy use and unit tests. However, as it uses recursion, performance can be expected to be worse than the other non recursive solution due to fewer allocations.
In this answer a non recursive solution is presented, including some code to account for the case where the enumerable is being enumerated twice. In this situation, however, it might not maintain the order of the original enumerable and it does not scale to more than two concurrent enumerations.
In this answer the enumerator method is rewritten to generalize the solution for the case of multiple concurrent enumeration while preserving the order of the original enumerable.
Combining the code from all answers we get the following class. Beware that this code is not thread safe, meaning that concurrent enumeration is safe only from the same thread.
public class CachedEnumerable<T> : IEnumerable<T>, IDisposable
{
private readonly IEnumerator<T> enumerator;
private readonly List<T> cache = new List<T>();
public CachedEnumerable(IEnumerable<T> enumerable) : this(enumerable.GetEnumerator()) { }
public CachedEnumerable(IEnumerator<T> enumerator)
=> this.enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator));
public IEnumerator<T> GetEnumerator()
{
int index = 0;
while (true) {
if (index < cache.Count) {
yield return cache[index];
index++;
}
else if (enumerator.MoveNext())
cache.Add(enumerator.Current);
else
yield break;
}
}
public void Dispose() => enumerator.Dispose();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
With the static extension method for easy use:
public static class EnumerableUtils
{
public static CachedEnumerable<T> ToCachedEnumerable<T>(this IEnumerable<T> enumerable)
=> new CachedEnumerable<T>(enumerable);
}
And the corresponding unit tests:
public class CachedEnumerableTest
{
private int _count;
[Test]
public void MultipleEnumerationAreNotCachedForOriginalIEnumerable()
{
_count = 0;
var enumerable = Enumerable.Range(1, 40).Select(incrementCount);
enumerable.Take(3).ToArray();
enumerable.Take(10).ToArray();
enumerable.Take(4).ToArray();
Assert.AreEqual(17, _count);
}
[Test]
public void EntireListIsEnumeratedForOriginalListOrArray()
{
_count = 0;
Enumerable.Range(1, 40).Select(incrementCount).ToList();
Assert.AreEqual(40, _count);
_count = 0;
Enumerable.Range(1, 40).Select(incrementCount).ToArray();
Assert.AreEqual(40, _count);
}
[Test]
public void MultipleEnumerationsAreCached()
{
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 40).Select(incrementCount).ToCachedEnumerable();
cachedEnumerable.Take(3).ToArray();
cachedEnumerable.Take(10).ToArray();
cachedEnumerable.Take(4).ToArray();
Assert.AreEqual(10, _count);
}
[Test]
public void FreshCachedEnumerableDoesNotEnumerateExceptFirstItem()
{
_count = 0;
Enumerable.Range(1, 40).Select(incrementCount).ToCachedEnumerable();
Assert.That(_count <= 1);
}
[Test]
public void MatrixEnumerationIteratesAsExpectedWhileStillKeepingEnumeratedValuesCached()
{
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 5).Select(incrementCount).ToCachedEnumerable();
var matrixCount = 0;
foreach (var x in cachedEnumerable) {
foreach (var y in cachedEnumerable) {
matrixCount++;
}
}
Assert.AreEqual(5, _count);
Assert.AreEqual(25, matrixCount);
}
[Test]
public void OrderingCachedEnumerableWorksAsExpectedWhileStillKeepingEnumeratedValuesCached()
{
_count = 0;
var cachedEnumerable = Enumerable.Range(1, 5).Select(incrementCount).ToCachedEnumerable();
var orderedEnumerated = cachedEnumerable.OrderBy(x => x);
var orderedEnumeratedArray = orderedEnumerated.ToArray(); // Enumerated first time in ascending order.
Assert.AreEqual(5, _count);
for (int i = 0; i < orderedEnumeratedArray.Length; i++) {
Assert.AreEqual(i + 1, orderedEnumeratedArray[i]);
}
var reorderedEnumeratedArray = orderedEnumerated.OrderByDescending(x => x).ToArray(); // Enumerated second time in descending order.
Assert.AreEqual(5, _count);
for (int i = 0; i < reorderedEnumeratedArray.Length; i++) {
Assert.AreEqual(5 - i, reorderedEnumeratedArray[i]);
}
}
private int incrementCount(int value)
{
_count++;
return value;
}
}
I don't see any serious problem with the idea to cache results in a list, just like in the above code. Probably, it would be better to construct the list using ToList() method.
public IEnumerable<ModuleData> ListModules()
{
if (Modules == null)
{
Modules = Source.Descendants("Module")
.Select(m => new ModuleData(m.Element("ModuleID").Value, 1, 1)))
.ToList();
}
return Modules;
}

Categories

Resources