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.
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!
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With