20

The definition System.Linq.ILookUp<TKey, TElement> reads

interface ILookup<TKey, TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable 
{
    int Count { get; }

    IEnumerable<TElement> this[TKey key] { get; }

    bool Contains(TKey key);
}

Since IEnumerable is covariant in IGrouping<TKey, TElement> , IGrouping<TKey, TElement> is covariant in TElement and the interface only exposes TElement as a return type, I would assume that ILookup is also covariant in TElement. Indeed, the definition

interface IMyLookup<TKey, out TElement> : IEnumerable<IGrouping<TKey, TElement>>, IEnumerable 
{
    int Count { get; }

    IEnumerable<TElement> this[TKey key] { get; }

    bool Contains(TKey key);
}

compiles without problems.

So, what might be the reason why the out keyword is missing in the original definition? Might it be added future versions of Linq?

spaleet
  • 838
  • 2
  • 10
  • 23
bigge
  • 1,488
  • 15
  • 27
  • 2
    ...nobody thought of, designed, implemented, tested, released....or maybe an entirely different reason. – Anthony Pegram Feb 14 '13 at 20:49
  • Well, I was just wondering whether that already is the whole story. On the other hand, I'm curious if such cases are a) common b) likely to be _fixed_ in the future – bigge Feb 15 '13 at 11:35
  • LINQ was introduced in .NET Framework 3.5 (C# 3.0), while variance for generic type parameters in interfaces since C# 4.0. Could this be the reason? – A. Rodas Feb 17 '13 at 14:04
  • 4
    @A.Rodas Don't think so, `IEnumerable` is now `IEnumerable` and it doesn't break backward compatibility. – NOtherDev Feb 17 '13 at 21:01
  • @AnthonyPegram: I'm almost sure I've heard that words (or very similar) somewhere else.. do you have by chance any ref to the origin? – quetzalcoatl Apr 05 '13 at 08:03
  • 1
    @quetzalcoatl, Eric Lippert has used that phrase on occasion. What can I say, it's been burned into my brain. Here's one of them. [Paragraph 2](http://blogs.msdn.com/b/ericlippert/archive/2009/06/22/why-doesn-t-c-implement-top-level-methods.aspx). – Anthony Pegram Apr 05 '13 at 12:41
  • @AnthonyPegram: "(..), documented and shipped" perfect match! I even remember reading that article. Thank you very much for the link! – quetzalcoatl Apr 05 '13 at 15:21

1 Answers1

7

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

Ryszard Dżegan
  • 24,366
  • 6
  • 38
  • 56
  • Really nice answer, thanks. Even though this does not directly answer my question, your answer shows that there is an issue here (even if just a small). I've accepted your answer since that's probably all one can find out about the problem. +1 for finding the quote. – bigge Feb 21 '13 at 12:35
  • @bigge: Thank you for appreciating my time. I had the case in mind. If I find something new, I will add it to the answer. – Ryszard Dżegan Feb 21 '13 at 12:46
  • When you say "prevent something like the below", what do you mean? You specify that both `ToLookup` calls are legal (one "OK", and one "Legal"). What is the issue if everything works? – Peter Ritchie Feb 21 '13 at 15:37
  • @PeterRitchie: _OK_ means _Legal for TElement_. The second is _Legal for out TElement_. I mean, that the latter would be valid if `TElement` is declarated as covariant i.e. `out TElement`. Thank you for your comment. I will write it more clearly. – Ryszard Dżegan Feb 22 '13 at 07:40
  • can you please clarify what exactly you see invalid in `ILookup lookup = strings.ToLookup(s => s); // Now invalid`? – Philipp Munin Dec 03 '19 at 17:30