What Is the Point of Lookup<Tkey, Telement>

What is the point of LookupTKey, TElement?

It's a cross between an IGrouping and a dictionary. It lets you group items together by a key, but then access them via that key in an efficient manner (rather than just iterating over them all, which is what GroupBy lets you do).

For example, you could take a load of .NET types and build a lookup by namespace... then get to all the types in a particular namespace very easily:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;

public class Test
{
static void Main()
{
// Just types covering some different assemblies
Type[] sampleTypes = new[] { typeof(List<>), typeof(string),
typeof(Enumerable), typeof(XmlReader) };

// All the types in those assemblies
IEnumerable<Type> allTypes = sampleTypes.Select(t => t.Assembly)
.SelectMany(a => a.GetTypes());

// Grouped by namespace, but indexable
ILookup<string, Type> lookup = allTypes.ToLookup(t => t.Namespace);

foreach (Type type in lookup["System"])
{
Console.WriteLine("{0}: {1}",
type.FullName, type.Assembly.GetName().Name);
}
}
}

(I'd normally use var for most of these declarations, in normal code.)

Any good reason why LookupTKey, TElement class have no public constructor?

Two possible reasons:

  1. Because it is immutable, so all initialization must be done at construction, therefore a factory method (Enumerable.ToLookup) is more appropriate in this case than a complex public constructor.
  2. Because the factory method is sufficient to create a Lookup, no constructor is necessary.

In the end, the designers don't have to justify not doing something. I can see little value of a constructor when a factory method exists to create one. If you feel that there is a use case where the value of a constructor outweighs the cost of adding one (including designing, building, testing, documenting, and supporting), then feel free to make a suggestion on http://connect.microsoft.com.

Difference between Lookup() and Dictionary(Of list())

Two significant differences:

  • Lookup is immutable. Yay :) (At least, I believe the concrete Lookup class is immutable, and the ILookup interface doesn't provide any mutating members. There could be other mutable implementations, of course.)
  • When you lookup a key which isn't present in a lookup, you get an empty sequence back instead of a KeyNotFoundException. (Hence there's no TryGetValue, AFAICR.)

They're likely to be equivalent in efficiency - the lookup may well use a Dictionary<TKey, GroupingImplementation<TValue>> behind the scenes, for example. Choose between them based on your requirements. Personally I find that the lookup is usually a better fit than a Dictionary<TKey, List<TValue>>, mostly due to the first two points above.

Note that as an implementation detail, the concrete implementation of IGrouping<,> which is used for the values implements IList<TValue>, which means that it's efficient to use with Count(), ElementAt() etc.

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

Linq GroupBy and LookupTKey, TElement.Item

Simply use Select instead:

var dates = timetableEvents.Select(x => x.DateTimeStart);

Or if you just want one from each group:

var dates = timetableEvents.GroupBy(x => x.DateTimeStart).Select(x => x.Key);

And if you want it to run a little bit faster, then you can use Distinct (as in Rawling's answer), but you probably won't notice much difference, unless you have a lot objects (though, the intention is much cleaner):

var dates = timetableEvents.Select(x => x.DateTimeStart).Distinct();

I find this site very useful when trying to learn LINQ: 101 LINQ Samples

Converting LookupTKey, TElement into other data structures c#

You can do that using these methods:

  • Enumerable.ToDictionary<TSource, TKey>
  • Enumerable.ToList<TSource>

Lets say you have a Lookup<int, string> called mylookup with the strings containing several words, then you can put the IGrouping values into a string[] and pack the whole thing into a dictionary:

var mydict = mylookup.ToDictionary(x => x.Key, x => x.ToArray());

Update

Having read your comment, I know what you actually want to do with your lookup (see the ops previous question). You dont have to convert it into a dictionary or list. Just use the lookup directly:

var wordlist = " aa bb cc ccc ddd ddd aa ";
var lookup = wordlist.Trim().Split().Distinct().ToLookup(word => word.Length);

foreach (var grouping in lookup.OrderBy(x => x.Key))
{
// grouping.Key contains the word length of the group
Console.WriteLine("Words with length {0}:", grouping.Key);

foreach (var word in grouping.OrderBy(x => x))
{
// do something with every word in the group
Console.WriteLine(word);
}
}

Also, if the order is important, you can always sort the IEnumerables via the OrderBy or OrderByDescending extension methods.

Edit:

look at the edited code sample above: If you want to order the keys, just use the OrderBy method. The same way you could order the words alphabetically by using grouping.OrderBy(x => x).

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

Difference between ReadOnlyDictionary and Lookup in .Net

Lookups are an easy way to group collections, where you could have one or more values for a given key. A Dictionary gives you one value for a give key, and one value only. Depending on your scenario, it may make most sense to have a Dictionary where you get back one value, or you may want to have a Lookup which would give you a collection of values.

Without a Lookup if you wanted a collection of values for a certain key you'd be stuck with something ugly like

Dictionary<int, IEnumerable<String>>

Yuk. See https://msdn.microsoft.com/en-us/library/bb460184%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 for more on Lookups.

ILookup interface vs IDictionary

ILookup entries can contain multiple items per key - each key is mapped to an IEnumerable<TElement>.

Also as hinted to in the comments an ILookup is immutable, while you can update values in an IDictionary (it exposes an Add() method and an indexer that allows getting and setting values).

In summary their use case is very different - you use a lookup when you need a 1:N map with values that are fixed and won't (and can't) change. A dictionary on the other hand offers a mutable 1:1 mapping of key value pairs, so it can be updated to add or remove values.



Related Topics



Leave a reply



Submit