I have an ILookup<int, Derived> and I want to return an ILookup<int, Base> where Derived implements or extends Base.
Currently I use SelectMany() and then ToLookup() to first extract the key value pairs of ILookup<int, Derived> into a flat IEnumerable and then create a new ILookup<int, Base>:
class Base { }
class Derived: Base { }
class Test
{
ILookup<int, Base> CastLookup(ILookup<int, Derived> existing)
{
IEnumerable<KeyValuePair<int, Base>> flattened = existing.SelectMany(
(x) => x,
(gr, v) => new KeyValuePair<int, Base>(gr.Key, (Base)v)); // I know the explicit cast can be implicit here; it is just to demonstrate where the up casting is happening.
ILookup<int, Base> result = flattened.ToLookup(
(x) => x.Key,
(x) => x.Value);
return result;
}
}
How can I convert an ILookup without iterating its entries and then repacking them?
NOTE: A related question is Shouldn't ILookup<TKey, TElement> be (declared) covariant in TElement? by bigge. Ryszard Dżegan answers that it is mostly for historical reasons: ILookup<TKey, TElement> has been developed before generics with covariance.
Herzmeister asks in .NET 4 not covariant - Stack Overflow">something similar for a Dictionary<TKey, TValue>. Mehrdad Afshari answers that for the mutable dictionary the covariance would not be safe.
Indeed, if Ilookup<TKey, TElement> was covariant in TElement, I would not have run into this instance of the ILookup<TKey, TElement> casting question; but it is not, so my quest for a better way still continues.
NOTE: I can of course write an extension method to do it, but that does not prevent the required computational work of iterating and repacking.
You could create a proxy:
public static ILookup<TKey, TValueBase> ToLookupBase<TKey, TValue, TValueBase>(this ILookup<TKey, TValue> lookup)
where TValue : class, TValueBase
{
return new LookupProxy<TKey, TValue, TValueBase>(lookup);
}
public class LookupProxy<TKey, TValue, TValueBase> : ILookup<TKey, TValueBase>
where TValue : class, TValueBase
{
private readonly ILookup<TKey, TValue> lookup;
public LookupProxy(ILookup<TKey, TValue> lookup)
{
this.lookup = lookup;
}
public IEnumerable<TValueBase> this[TKey key] => lookup[key];
public int Count => lookup.Count;
public bool Contains(TKey key) => lookup.Contains(key);
public IEnumerator<IGrouping<TKey, TValueBase>> GetEnumerator() => lookup.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Note that you'll have to:
var base = existing.ToLookupBase<int, Derived, Base>();
so explicitly tell all the generic parameters. If you want to even support covariance for TKey it is a little more complex, and needs a separate support class and a separate method:
public static ILookup<TKeyBase, TValueBase> ToLookupBase2<TKey, TValue, TKeyBase, TValueBase>(ILookup<TKey, TValue> lookup)
where TKey : class, TKeyBase
where TValue : class, TValueBase
{
return new LookupProxy2<TKey, TValue, TKeyBase, TValueBase>(lookup);
}
public class LookupProxy2<TKey, TValue, TKeyBase, TValueBase> : ILookup<TKeyBase, TValueBase>
where TKey : class, TKeyBase
where TValue : class, TValueBase
{
private readonly ILookup<TKey, TValue> lookup;
public LookupProxy2(ILookup<TKey, TValue> lookup)
{
this.lookup = lookup;
}
public IEnumerable<TValueBase> this[TKeyBase key] => key is TKey ? lookup[(TKey)key] : Enumerable.Empty<TValueBase>();
public int Count => lookup.Count;
public bool Contains(TKeyBase key) => key is TKey ? lookup.Contains((TKey)key) : false;
public IEnumerator<IGrouping<TKeyBase, TValueBase>> GetEnumerator() => lookup.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
this because you need to add a where TKey : class, TKeyBase (that won't support value types for the key, like in your example).
How about creating your own class that implements ILookup<TKey, TBase>, like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var lookup1 = new List<Derived> { new Derived() { Key = 1, Prop1 = "A", Prop2 = "B"} } .ToLookup(x => x.Key, x => x);
var baseLookup = new BaseLookup<int, Base, Derived>(lookup1);
Console.WriteLine(baseLookup[1]);
}
}
public class BaseLookup<TKey, TBase, TDerived> : ILookup<TKey, TBase> where TDerived : TBase
{
private readonly ILookup<TKey, TDerived> _inner;
public BaseLookup(ILookup<TKey, TDerived> inner)
{
_inner = inner;
}
IEnumerator<IGrouping<TKey, TBase>> IEnumerable<IGrouping<TKey, TBase>>.GetEnumerator()
{
return (IEnumerator<IGrouping<TKey, TBase>>) _inner.GetEnumerator();
}
public IEnumerator GetEnumerator()
{
return ((IEnumerable) _inner).GetEnumerator();
}
public bool Contains(TKey key)
{
return _inner.Contains(key);
}
public int Count => _inner.Count;
public IEnumerable<TBase> this[TKey key] => _inner[key].Cast<TBase>();
}
public class Base
{
public int Key { get; set; }
public string Prop1 { get; set; }
}
public class Derived : Base
{
public string Prop2 { get; set; }
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With