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 IGrouping
s 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 IGrouping
s.
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
C#: Triggering an Event When an Object Is Added to a Queue
Entity-Framework Code Is Slow When Using Include() Many Times
How to Get a Count of the Total Number of Digits in a Number
Excel Error Hresult: 0X800A03Ec While Trying to Get Range with Cell's Name
How to Fix Wpf Error: "Program Does Not Contain a Static 'Main' Method Suitable for an Entry Point"
Cookie Confusion with Formsauthentication.Setauthcookie() Method
Rx: How to Respond Immediately, and Throttle Subsequent Requests
ASP.NET Core Content-Disposition Attachment/Inline
Inheriting Comments from an Interface in an Implementing Class
How to Perform an Insert and Return Inserted Identity with Dapper
Is It There Any Lru Implementation of Idictionary
What Is Meant by "Managed" VS "Unmanaged" Resources in .Net
Give Some Command to View in Mvvm
Invalid Uri: the Format of the Uri Could Not Be Determined
Ocr with the Tesseract Interface
Do Interfaces Derive from System.Object? C# Spec Says Yes, Eric Says No, Reality Says No