12

My question is similar to the previous one, but the answer to that question is inapplicable to this one.

Well, I want to write an extension method for both IDictionary and IReadOnlyDictionary interfaces:

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;
}

But when I use it with classes implementing both interfaces (e.g. Dictionary<Tkey, TValue>), I get the ‘ambiguous invocation’. I don't want to type var value = myDic.GetNullable<IReadOnlyDictionary<MyKeyType, MyValueType>>(key), I want it to be just var value = myDic.GetNullable(key).

Is this possible?

4

3 回答 3

10

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

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) 查找的性能影响以及您希望多久执行一次此类查找?

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

我认为在我完成这个主题之前还需要一个程序点。将 aDictionary<TKey, TValue>转换为IReadOnlyDictionary<TKey, TValue>仅可确保接收转换值的任何内容本身都无法改变基础集合。然而,这并不意味着对创建转换引用的字典实例的其他引用不会改变基础集合。这是IReadOnly*集合接口背后的问题之一——这些接口引用的集合可能不是真正的“只读”(或者,通常暗示的,不可变的)集合,它们只是防止特定引用对集合进行突变(尽管具体类的实际实例ReadOnly*)。这是(除其他外)原因之一System.Collections.Immutable集合类的命名空间已创建。此命名空间中的集合代表真正不可变的集合。这些集合的“突变”会导致返回一个包含突变状态的全新集合,而原始集合不受影响。

于 2013-09-05T16:45:17.797 回答
8

After some experimenting, I remembered/found that C# is willing to resolve ambiguities by preferring subclasses over base classes. Thus, to do this with type safety, you need 3 extension methods rather than one. And if you have any other classes which, like Dictionary<TKey, TValue>, implement both IDictionary<TKey, TValue> and IReadOnlyDictionary<TKey, TValue>, you need to write more and more extension methods…</p>

The following makes the compiler perfectly happy. It does not require casting to use the passed dictionary as a dictionary. My strategy is to implement my actual code for the lowest common denominator, IReadOnlyDictionary<TKey, TValue>, use a façade to adapt IDictionary<TKey, TValue> to that (because you could be passed an object which doesn’t implement IReadOnlyDictionary<TKey, TValue>), and, to avoid the method overload resolution ambiguity error, explicitly extend Dictionary<TKey, TValue> directly.

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;
}

The most annoying thing with this approach is that you have to write so much boilerplate—two thin wrapper extension methods and one actual implementation. However, I think this can be justified because code using the extensions can be kept simple and clear. The ugliness is contained in the the extensions class. Also, because the extension implementation is contained in one method, you do not need to worry about updating the overloads when the IReadOnlyDictionary<TKey, TValue> variant is updated. The compiler will even remind you to update the overloads if you change the parameter types unless you’re adding new parameters with default values.

于 2016-01-25T17:32:03.207 回答
1

要在您的情况下获得所需的行为,请参阅fourpastmidnight's answer


但是,要直接回答您的问题,myDic.GetNullable,当有两种扩展方法时,不可能使语法起作用。您收到的错误消息说它模棱两可是正确的,因为它无法知道要调用哪一个。如果你的IReadOnlyDictionary方法碰巧做了一些不同的事情,它应该这样做还是不这样做Dictionary<TKey, TValue>

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

假设您的两种方法确实有不同的实现,您可以包含类似的方法,如下所示:

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,但这只是为了演示)

于 2013-09-05T17:36:04.093 回答