LINQ 在实现两次 IEnumerable<T> 时会混淆。

15
我的类两次实现了 IEnumerable<T> 接口。 我该如何在不需要每次强制转换 hashtable 的情况下使用 LINQ?
我编写了自己的协变哈希表实现,它还继承了.NET的IDictionary<TKey, TValue>。最终,它使用不同类型的T两次实现了IEnumerable<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();
}

当我使用foreach循环哈希表时,它按预期地使用主要的可枚举项:as expected
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>
}

现在我希望它在LINQ中做同样的事情,但是编译器会抛出错误,因为它不知道选择哪个接口用于扩展方法:
var xs1 = from x in hashtable          // <-- 1
          select x;

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

错误1:无法找到源类型为'HashTable'的查询模式实现。未找到“Select”。考虑明确指定范围变量“x”的类型。 错误2:'HashTable'不包含名为'Select'的定义,也找不到接受类型为'HashTable'的第一个参数的扩展方法'Select'(是否缺少使用指令或程序集引用?) 也许有一些接口或继承技巧我不知道?

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

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>

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

4
你尝试手动指定 Select泛型参数 了吗?听起来它在推断这些参数上有点问题。特别是,你需要手动指定 TSource,但一旦你指定了一个,我认为你就需要同时指定另一个。不幸的是,这将阻止你返回匿名类型,除非使用一些麻烦的解决方法。 - Adam Houldsworth
你能否包含HashTable<TKey, TValue>的其余继承/接口? - Ryan Gates
1
好的,至少它告诉你该怎么做,关于第一个错误。你必须明确说明“x”的类型: 从哈希表中选择IAssociation<TKey, TValue> x; - Stonehead
为什么其中一个使用 IAssociation<TKey, TValue>,而另一个使用 KeyValuePair<TKey, TValue>?我本来期望它们都使用相同的类型。 - Doctor Jones
请注意,您提供的声明在协变方面是无效的 - 您使用了“out”比您应该使用的更多...您只需要在“TKey”、“TValue”的类型参数中使用它。 - Jon Skeet
1
你也可以使用一个适配器类,该类将知道如何通过隐式操作符将IAssociation转换为KeyValuePair,反之亦然,然后使你的HashTable<TKey, TValue>从它继承。但我更倾向于采用Jon Skeet的解决方案。 - Pierluc SS
1个回答

25
重要的是要了解,当使用表达式作为方法调用的参数时,编译器没有“主要”和“次要”接口实现的概念。就转换为这些类型而言,您的类型同时很好地实现了IEnumerable<IAssociation<...>>IEnumerable<KeyValuePair<...>>。这就是编译器需要更多信息的原因。
最简单的方法(在我看来)是引入两个新属性:
public IEnumerable<IAssociation<TKey, TValue>> Associations { get { return this; } }
public IEnumerable<KeyValuePair<TKey, TValue>> KeyValuePairs { get { return this; } }

这意味着你可以非常轻松地变得具体:

var query = from x in table.Associations
            ...;
或者
var query = from x in table.KeyValuePairs
            ...;

这不仅可以让编译器高兴- 它也可以帮助任何试图阅读代码的人。如果你发现你使用其中一个比另一个 更多,你可以将 HashTable 仅实现单个 IEumerable <>类型并保留另一个属性。


有没有其他方法可以通过直接实现它们来满足编译器的要求? - antonijn
1
@Antonijn:是的,您可以通过显式指定类型参数来满足编译器的要求... - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接