14

My class implements IEnumerable<T> twice. How can I get LINQ to work without casting hashtable every time?


I wrote my own covariant hashtable implementation that also inherits from .NET's IDictionary<TKey, TValue>. Ultimately, it implements IEnumerable<T> twice with different types for T. I implemented the primary enumerable interface implicitly, and the other one explicitly. Something like this (pseudocode):

class HashTable<TKey, TValue> :
    ...
    IEnumerable<out IAssociation<out TKey, out TValue>>,
    IEnumerable<out KeyValuePair<TKey, TValue>>
{
    // Primary:
    public IEnumerator<IAssociation<TKey, TValue>> GetEnumerator();
    // Secondary:
    IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator();
}

When I foreach the hash table, it takes as expected the primary enumerable:

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

var hashtable = new HashTable<string, int>();
foreach (var kv in hashtable)
{
    // kv is IAssociation<string, int>
}

Now I want it to do the same thing in LINQ, but it flings compiler errors at me because it does not know which interface to pick for the extension methods:

var xs1 = from x in hashtable          // <-- 1
          select x;

var xs2 = hashtable.Select(x => x);    // <-- 2

Error 1: Could not find an implementation of the query pattern for source type 'HashTable'. 'Select' not found. Consider explicitly specifying the type of the range variable 'x'.

Error 2: 'HashTable' does not contain a definition for 'Select' and no extension method 'Select' accepting a first argument of type 'HashTable' could be found (are you missing a using directive or an assembly reference?)

Maybe there's some interface or inheritance trick I don't know about?


For those who asked, here is the full tree of interfaces:

using SCG = System.Collections.Generic;

public class HashTable<TKey, TValue>
    : IKeyedCollection<TKey, TValue>, SCG.IDictionary<TKey, TValue>

public interface IKeyedCollection<out TKey, out TValue>
    : ICollection<IAssociation<TKey, TValue>>

public interface ICollection<out T> : SCG.IEnumerable<T>

public interface IAssociation<out TKey, out TValue>

// .NET Framework:
public interface IDictionary<TKey, TValue>
    : ICollection<KeyValuePair<TKey, TValue>>

public interface ICollection<T>
    : IEnumerable<T>

Now you can see why I couldn't make KeyValuePair<TKey, TValue> and IAssociation<TKey, TValue> the same.

Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • 4
    Have you tried specifying the [generic arguments](http://msdn.microsoft.com/en-GB/library/bb548891.aspx) of `Select` manually? Sounds like it is having trouble inferring them. In particular, you need to specify `TSource` manually, but once you do one, you need to do both I think. Unfortunately, this will preclude you from returning anonymous types without some annoying workarounds. – Adam Houldsworth Feb 14 '13 at 15:39
  • Can you include the rest of the inheritance/interfaces for `HashTable`? – Ryan Gates Feb 14 '13 at 15:40
  • 1
    Well it tells you what to do, at least regarding the first error. You have to explicitly say what type 'x' is: from IAssociation x in hashtable select x; – Stonehead Feb 14 '13 at 15:41
  • Why does one of them use `IAssociation` and one use `KeyValuePair`? I'd expect to see them both use the same type. – Doctor Jones Feb 14 '13 at 15:42
  • Note that the declaration you've given is invalid in terms of covariance - you've used `out` rather more than you should have done... you only need it in the type *parameters* for `TKey`, `TValue`. – Jon Skeet Feb 14 '13 at 15:43
  • 1
    You could also perhaps use an adapter class that would know how to convert IAssociation to KeyValuePair and vice-versa (via implicit operators) and then make your HashTable inherit from it. But i'd rather go with Jon Skeet solution – Pierluc SS Feb 14 '13 at 15:47

1 Answers1

25

It's important to understand that the compiler has no concept of "primary" and "secondary" interface implementations when it comes to using an expression as an argument for a method call. Your type implements both IEnumerable<IAssociation<...>> and IEnumerable<KeyValuePair<...>> equally well, as far as conversions to those types are concerned. That's why the compiler needs more information.

The simplest approach (IMO) would be to introduce two new properties:

public IEnumerable<IAssociation<TKey, TValue>> Associations { get { return this; } }
public IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs { get { return this; } }

That means you can really easily be specific:

var query = from x in table.Associations
            ...;

or

var query = from x in table.KeyValuePairs
            ...;

Not only does this help keep the compiler happy - it'll help anyone trying to read the code, too. If you find you use one of these much more than the other, you could always make HashTable only implement a single IEumerable<> and type and keep the other property.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194