Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ gets confused when implementing IEnumerable<T> twice

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.

like image 244
Daniel A.A. Pelsmaeker Avatar asked Feb 14 '13 15:02

Daniel A.A. Pelsmaeker


People also ask

Does Linq work with IEnumerable?

All LINQ methods are extension methods to the IEnumerable<T> interface. That means that you can call any LINQ method on any object that implements IEnumerable<T> . You can even create your own classes that implement IEnumerable<T> , and those classes will instantly "inherit" all LINQ functionality!

Is IEnumerable deferred?

You can implement deferred execution for your custom extension methods for IEnumerable using the yield keyword of C#. For example, you can implement custom extension method GetTeenAgerStudents for IEnumerable that returns a list of all students who are teenagers.

What is IEnumerable and what significance does it hold?

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. It is the base interface for all non-generic collections that can be enumerated. This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.


1 Answers

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.

like image 89
Jon Skeet Avatar answered Oct 06 '22 16:10

Jon Skeet