Ilookup<Tkey, Tval> VS. Igrouping<Tkey, Tval>

ILookupTKey, TVal vs. IGroupingTKey, TVal

Yes, all of those are correct.

And ILookup<TKey, TValue> also extends IEnumerable<IGrouping<TKey, TValue>> so you can iterate over all the key/collection pairs as well as (or instead of) just looking up particular keys.

I basically think of ILookup<TKey,TValue> as being like IDictionary<TKey, IEnumerable<TValue>>.

Bear in mind that ToLookup is a "do it now" operation (immediate execution) whereas a GroupBy is deferred. As it happens, with the way that "pull LINQ" works, when you start pulling IGroupings from the result of a GroupBy, it has to read all the data anyway (because you can't switch group midway through) whereas in other implementations it may be able to produce a streaming result. (It does in Push LINQ; I would expect LINQ to Events to be the same.)

ILookup versus IGrouping

You should call ToLookup if you need to lookup values by key, but you don't need ordering.

You should call GroupBy if you just need to loop through the groups.

Shouldn't ILookupTKey, TElement be (declared) covariant in TElement?

Tracking the MSDN documentation, Covariance and Contravariance in Generics have been introduced in .NET Framework 4 Prior to that, there was IEnumerable<T> since the .NET Framework 2.0 up to .NET Framework 3.5. Then in .NET Framework 4.0 we can see IEnumerable<out T> with type parameter T as covariance.

IGrouping<TKey, TElement> and ILookup<TKey, TElement> have existed since .NET Framework 3.5. In .NET Framework 4.0 the former has been updated to IGrouping<out TKey, out TElement> but the latter has been omitted without specifying the reason.

TKey can't be covariant since implementations of Contains(TKey) and this[TKey] prevent that.

With regard to TElement the issue is not clear. I don't believe that designers just missed it. Perhaps cause lies in the plans for the future. Or they wanted to prevent something like the below, but I don't know why:

string[] strings = new[] {"a", "a", "b", "b", "b", "c"};
ILookup<string, string> lookup = strings.ToLookup(s => s); // Valid.
ILookup<string, object> lookup = strings.ToLookup(s => s); // Now invalid, but would correct if TElement was covariant (out TElement).

There are also other authors, that pay attention to that issue:

ToLookup:

One slightly odd point to note is that while IGrouping is covariant in TKey and TElement, ILookup is invariant in both of its type parameters. While TKey has to be invariant, it would be reasonable for TElement to be covariant

How can interface IGroupingout TKey, out TElement yield multiple values?

If you look at the declaration of IGrouping,

public interface IGrouping<out TKey,out TElement> : System.Collections.Generic.IEnumerable<out TElement>

You can see that it says "IGrouping<TKey, TElement> is a kind of IEnumerable<TElement>".

It's a special kind of IEnumerable<TElement>. Special in what way? It has a Key associated.


And no, this is not valid C# syntax:

interface IGrouping<out TKey, out IEnumerable<TElement>>

Remember that this is an interface declaration, and in the <>, it is declaring the type parameters, which are identifiers, for the interface. IEnumerable<TElement> is not a valid identifier. You also seem to want to refer to the existing interface System.Collections.Generic.IEnumerable<T>, but it makes no sense to put it in this place in the declaration, the same way that it wouldn't make sense to write interface Foo<List<T>> as an interface declaration in Java.

If you mean:

interface IGrouping<out TKey, out TElement> where TElement : IEnumerable

that could work, and there are many ways in which you can design this IGrouping interface. The following way probably makes sense to you the most?

interface IMyGrouping<TKey, TElement> {
TKey Key { get; }
IEnumerable<TElement> Elements { get; }
}

But what's really great about .NET framework's design is that it implements an existing interface, and that allows it to be used as an IEnumerable<TElement> directly, which is a very powerful thing. If your class have the ability that the interface promises, implement that interface. See also.

Does the followed mean ILookup used by GroupBy has multiple keys with the association of multiple values?

Yes. Since ILookup implements IEnumerable<IGrouping>, and IGrouping implements IEnumerable, ILookup is kind of like a IEnumerable<IEnumerable> (forgive me using the generics notation very loosely here). So an ILookup an enumerable of enumerables, i.e. like a 2D array. The difference is, that each inner "array" has a Key, since the inner "arrays" are actually IGroupings.

How to cast TElement of lookup; i.e. ILookupTKey, Derived to ILookupTKey, Base?

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).

Recommended way to convert IGroupingTKey, TValue to IDictionaryTKey, IEnumerableTValue

This should work:

var dict = yourGroup.ToDictionary(g=>g.Key, g=>g.Select(x=>x));

Or use AsEnumerable for generating values:

var dict = yourGroup.ToDictionary(g=>g.Key, g=>g.AsEnumerable());

ILookup store item under multiple keys

Assuming it needs to be from LocationID -> ItemID, one way would be:

items.SelectMany(item => item.LocationIDs,
(item, locationID) => new { item.ItemID, LocationID = locationID })
.ToLookup(tuple => tuple.LocationID, tuple => tuple.ItemID)

This first flattens all your data to a sequence of (itemID, locationID) tuples before turning that into a lookup.

It's a trivial modification to the above if the lookup needs to be from the LocationID -> Item object itself.



Related Topics



Leave a reply



Submit