当实现IEnumerable <T>两次时,LINQ会感到困惑

Dan*_*ker 14 c# linq generics collections ienumerable

我的班级实施了IEnumerable<T>两次.我怎样才能让LINQ无需hashtable每次都进行投射?


我编写了自己的协变哈希表实现,它也继承自.NET IDictionary<TKey, TValue>.最终,它实现了IEnumerable<T>两次不同的类型T.我隐式地实现了主要的可枚举接口,并且明确地实现了另一个.像这样的东西(伪代码):

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();
}
Run Code Online (Sandbox Code Playgroud)

当我foreach在哈希表中时,它按预期采用主要的可枚举:

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>
}
Run Code Online (Sandbox Code Playgroud)

现在我希望它在LINQ中做同样的事情,但它在我身上编译错误,因为它不知道为扩展方法选择哪个接口:

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

var xs2 = hashtable.Select(x => x);    // <-- 2
Run Code Online (Sandbox Code Playgroud)

错误1:无法找到源类型"HashTable"的查询模式的实现.找不到"选择".考虑明确指定范围变量'x'的类型.

错误2:'HashTable'不包含'Select'的定义,并且没有扩展方法'Select'接受类型为'HashTable'的第一个参数'(您是否缺少using指令或程序集引用?)

也许有一些我不知道的接口或继承技巧?


对于那些问过的人,这里是完整的接口树:

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>
Run Code Online (Sandbox Code Playgroud)

现在你可以看到为什么我不能做KeyValuePair<TKey, TValue>IAssociation<TKey, TValue>相同.

Jon*_*eet 25

重要的是要理解,当使用表达式作为方法调用的参数时,编译器没有"主要"和"次要"接口实现的概念.就这些类型的转换而言,您的类型同时实现两者IEnumerable<IAssociation<...>>并且IEnumerable<KeyValuePair<...>>同样良好.这就是编译器需要更多信息的原因.

最简单的方法(IMO)将引入两个新属性:

public IEnumerable<IAssociation<TKey, TValue>> Associations { get { return this; } }
public IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs { get { return this; } }
Run Code Online (Sandbox Code Playgroud)

这意味着你可以很容易地具体化:

var query = from x in table.Associations
            ...;
Run Code Online (Sandbox Code Playgroud)

要么

var query = from x in table.KeyValuePairs
            ...;
Run Code Online (Sandbox Code Playgroud)

这不仅有助于保持编译器的快乐 - 它还可以帮助任何尝试阅读代码的人.如果你发现你使用其中的一个比对方多,你总是可以让HashTable只实现单一IEumerable<>和类型,并保持其他财产.