为什么我的代码需要使用HashSet <t>而不是ISet <t>进行重载?

时间:2016-11-24 08:13:58

标签: c#

我有以下方法:

    private string TickIfHeadingPresent<TKey, TValue>(Dictionary<TKey, ISet<TValue>> data, TKey key, TValue value)
    {
        ISet<TValue> set;
        return data.TryGetValue(key, out set) && set.Contains(value) ? "<i class='icon-check'></i>" : String.Empty;
    }

有时我用我刚刚创建的对象调用方法,其中C#知道我特意有Dictionary<whatever, HashSet<something>>.

在这些情况下,我收到错误:

  

无法转换为'System.Collections.Generic.Dictionary&lt; string,   System.Collections.Generic.HashSet&LT;串GT;&GT;”至   “System.Collections.Generic.Dictionary&LT;串,   System.Collections.Generic.ISet&LT;串GT;&GT;'

这让我感到困惑,因为HashSet<T>ISet<T>的一个子集 - 当然,任何接受ISet<T>的代码都不介意获取特定类型的集合吗?

根据Visual Studio的“修复”是添加一个具有更具体的重载的重复方法:

    // I have no idea why this won't just hit the ISet overload...
    private string TickIfHeadingPresent<TKey, TValue>(Dictionary<TKey, HashSet<TValue>> data, TKey key, TValue value)
    {
        HashSet<TValue> set;
        return data.TryGetValue(key, out set) && set.Contains(value) ? "<i class='icon-check'></i>" : String.Empty;
    }

有没有办法编写我的代码,所以我只需要定义一次方法?

2 个答案:

答案 0 :(得分:7)

假设您描述了Dictionary<string, HashSet<string>>。假设您以某种方式将其作为Dictionary<string, ISet<string>>传递。如果是这种情况,您现在可以将实现ISet<string>的其他对象添加到此词典中,而不仅仅是HashSet<string>。例如,您现在可以将SortedSet<string>添加到词典中。但SortedSet不是HashSet,也不是HashSet<string>,而您原来的Dictionary只能包含HashSet。该示例显示为什么不允许从Dictionary<string, HashSet<string>>转换为Dictionary<string, ISet<string>>

现在,使用某些接口,您无法执行违反类型安全的操作。例如,取IEnumerable<T>。如果您有类似的方法:

 private string TickIfHeadingPresent<TKey, TValue>(IEnumerable<ISet<TKey>> data, TKey key, TValue value)

现在您可以List<HashSet<string>>作为IEnumerable<ISet<string>>传递。您无法将新项目添加到IEnumerable<T>,也不能执行此类违反类型安全的任何其他操作。这是确保的,因为IEnumerable的定义如下:

public interface IEnumerable<out T> : IEnumerable

“out”关键字表示T是协变的,编译器将确保此接口上的任何方法都不会违反该规则(基本上它不允许将类型T用作任何方法的参数,仅作为返回类型)。

答案 1 :(得分:0)

如上所述,这是一个方差问题。这是一种替代方法,允许您仍然只有一个TickIfHeadingPresent方法。

TickIfHeadingPresent中您需要的不一定data中的值 特定的集合类型;相反,您需要data 中的值可以告诉您它们是否包含特定的value 。所以,如果你这样做:

private string TickIfHeadingPresent<TKey, TValue, TValueSet>
    (Dictionary<TKey, TValueSet> data, TKey key, TValue value)
    where TValueSet : ICollection<TValue>
{
    TValueSet set;
    return data.TryGetValue(key, out set) && set.Contains(value) ? "<i class='icon-check'></i>" : String.Empty;
}

然后你可以用Dictionary<whatever, HashSet<something>>Dictionary任何其他ISet<>调用此方法 - 将集合作为值的类型实现。

我们在这里所做的就是告诉编译器我们需要字典中值的类型来实现我们将在其中查找的相同类型的ICollection<> < / em>,然后编译器知道它可以安全地调用Contains value,无论它从字典中取出什么。类型推断意味着虽然此方法具有更复杂的签名,但我们仍然不需要显式指定类型参数。