IDictionary和IReadOnlyDictionary的扩展方法

时间:2013-09-05 16:40:34

标签: c# idictionary

我的问题类似于the previous one,但该问题的答案不适用于此问题。

好吧,我想为IDictionaryIReadOnlyDictionary接口编写扩展方法:

public static TValue? GetNullable<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
    where TValue : struct
{
    return dictionary.ContainsKey(key)
        ? (TValue?)dictionary[key]
        : null;
}

public static TValue? GetNullable<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
    where TValue : struct
{
    return dictionary.ContainsKey(key)
        ? (TValue?)dictionary[key]
        : null;
}

但是当我将它用于实现两个接口的类(例如Dictionary<Tkey, TValue>)时,我得到了“模糊的调用”。我不想输入var value = myDic.GetNullable<IReadOnlyDictionary<MyKeyType, MyValueType>>(key),我希望它只是var value = myDic.GetNullable(key)

这可能吗?

3 个答案:

答案 0 :(得分:9)

您只需要一种扩展方法。重新定义如下:

public static TValue? GetNullableKey<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dictionary, TKey, key) where TValue : struct
{
    // Your code here.
    // Per @nmclean: to reap performance benefits of dictionary lookup, try to cast
    //               dictionary to the expected dictionary type, e.g. IDictionary<K, V> or
    //               IReadOnlyDictionary<K, V>. Thanks for that nmclean!
}

IDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>都继承自IEnumerable<KeyValuePair<TKey, TValue>>

HTH。

更新:回答您的评论问题

如果您不需要调用只出现在一个接口或另一个接口上的方法(即您只调用IEnumerable<KeyValuePair<TKey, TValue>>上存在的方法),那么不,您不需要该代码。

如果你确实需要调用公共基接口上不存在的IDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>接口上的方法,那么是的,你需要查看你的对象是否是一个或者另一个知道哪些方法可以被调用。

更新:您可能(很可能)不应使用此解决方案

因此,从技术上讲,这个解决方案在回答OP的问题时是正确的,如上所述。然而,这真的不是一个好主意,正如其他人在下面评论过的那样,并且我已经承认这些评论是正确的。

首先,字典/地图的整个点是O(1)(常数时间)查找。通过使用上面的解决方案,您现在已将O(1)操作转换为O(n)(线性时间)操作。如果你的词典中有1,000,000个项目,那么查找关键值需要多达100万次(如果你真的不走运)。这可能会对性能产生重大影响。

小字典怎么样?那么问题就变成了,你真的需要一本字典吗?列表会更好吗?或许是一个更好的问题:你在哪里开始注意到使用O(n)优于O(1)查找的性能影响以及你期望执行这样的查找的频率是多少?

最后,由于IReadOnlyDictionary<TKey, TValue>的行为是一个超集,OP的实现 IDictionary<TKey, TValue>IDictionary<TKey, TValue>的对象的情况很奇怪。 IReadOnlyDictionary<TKey, TValue>的行为。标准Dictionary<TKey, TValue>类已经实现了这两个接口。因此,如果OP(我假设,专门)类型继承自Dictionary<TKey, TValue>,那么当需要只读功能时,类型可能只是被转换为IReadOnlyDictionary<TKey, TValue>,可能完全避免这个问题。即使问题不是不可避免的(例如某处,对其中一个接口进行了转换),实现两个扩展方法仍然会更好,每个接口都有一个扩展方法。

我认为在完成这个主题之前还需要一个程序问题。将Dictionary<TKey, TValue>转换为IReadOnlyDictionary<TKey, TValue>只能确保接收到转换值的任何内容本身都不能改变底层集合。但是,这并不意味着对创建转换引用的字典实例的其他引用不会改变底层集合。这是IReadOnly*集合接口背后的抱怨之一 - 这些接口引用的集合可能不是真正的“只读”(或者,通常是暗示的,不可变的)集合,它们只是防止集合的变异。一个特定的引用(尽管具体的ReadOnly*类的实际实例)。这是(以及其他)创建集合类的System.Collections.Immutable命名空间的一个原因。此命名空间中的集合表示真正不可变的集合。这些集合的“突变”导致返回由突变状态组成的全新集合,使原始集合不受影响。

答案 1 :(得分:4)

经过一些实验,我记得/发现C#愿意通过优先选择基类而不是基类来解决歧义。因此,要使用类型安全来执行此操作,您需要 3 扩展方法而不是一个。如果您有任何其他类,例如Dictionary<TKey, TValue>,同时实现IDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>,则需要编写越来越多的扩展方法......

以下内容使编译器非常满意。它不需要强制转换将传递的字典用作字典。我的策略是为最低公分母IReadOnlyDictionary<TKey, TValue>实现我的实际代码,使用façade来适应IDictionary<TKey, TValue>(因为你可以传递一个没有实现IReadOnlyDictionary<TKey, TValue>的对象),并且,为避免方法重载决策歧义错误,请直接显式扩展Dictionary<TKey, TValue>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;

// Proof that this makes the compiler happy.
class Program
{
    static void Main(string[] args)
    {
        // This is written to try to demonstrate an alternative,
        // type-clean way of handling http://stackoverflow.com/q/18641693/429091
        var x = "blah".ToDictionary(
            c => c,
            c => (int)c);
        // This would be where the ambiguity error would be thrown,
        // but since we explicitly extend Dictionary<TKey, TValue> dirctly,
        // we can write this with no issue!
        x.WriteTable(Console.Out, "a");
        IDictionary<char, int> y = x;
        y.WriteTable(Console.Out, "b");
        IReadOnlyDictionary<char, int> z = x;
        z.WriteTable(Console.Out, "lah");
    }
}

// But getting compile-time type safety requires so much code duplication!
static class DictionaryExtensions
{
    // Actual implementation against lowest common denominator
    public static void WriteTable<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
    {
        writer.WriteLine("-");
        foreach (var key in keys)
            // Access values by key lookup to prove that we’re interested
            // in the concept of an actual dictionary/map/lookup rather
            // than being content with iterating over everything.
            writer.WriteLine($"{key}:{dict[key]}");
    }

    // Use façade/adapter if provided IDictionary<TKey, TValue>
    public static void WriteTable<TKey, TValue>(this IDictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
        => new ReadOnlyDictionary<TKey, TValue>(dict).StaticCast<IReadOnlyDictionary<TKey, TValue>>().WriteTable(writer, keys);

    // Use an interface cast (a static known-safe cast).
    public static void WriteTable<TKey, TValue>(this Dictionary<TKey, TValue> dict, TextWriter writer, IEnumerable<TKey> keys)
        => dict.StaticCast<IReadOnlyDictionary<TKey, TValue>>().WriteTable(writer, keys);

    // Require known compiletime-enforced static cast http://stackoverflow.com/q/3894378/429091
    public static T StaticCast<T>(this T o) => o;
}

这种方法最烦人的是你必须编写这么多样板文件 - 两个瘦包装器扩展方法和一个实际实现。但是,我认为这是合理的,因为使用扩展的代码可以保持简单明了。丑陋包含在扩展类中。此外,由于扩展实现包含在一个方法中,因此在更新IReadOnlyDictionary<TKey, TValue>变体时无需担心更新重载。如果您更改参数类型,编译器甚至会提醒您更新重载,除非您使用默认值添加新参数。

答案 2 :(得分:1)

要获得所需的行为,请参阅fourpastmidnight's answer


要直接回答您的问题,,当有两种扩展方法时,无法使myDic.GetNullable语法有效。你得到的错误信息是正确的,因为它不明确,因为它无法知道要调用哪一个。如果您的IReadOnlyDictionary方法碰巧做了不同的事情,那么它是否会对Dictionary<TKey, TValue>执行此操作?

出于这个原因,AsEnumerable方法存在。为IEnumerableIQueryable定义了具有相同名称的扩展方法,因此有时需要转换实现两者的对象以确保将调用特定的扩展方法。

假设您的两个方法 有不同的实现,您可以包含类似的方法:

public static IReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary) {
    return (IReadOnlyDictionary<TKey, TValue>)dictionary;
}

public static IDictionary<TKey, TValue> AsReadWrite<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary) {
    return (IDictionary<TKey, TValue>)dictionary;
}

然后调用这样的方法:

dictionary.AsReadOnly().GetNullable(key);
dictionary.AsReadWrite().GetNullable(key);

(从技术上讲,一个名为AsReadOnly的方法应该返回一个真实的ReadOnlyDictionary,但这仅用于演示)