IDictionary和IReadOnlyDictionary的扩展方法

use*_*514 7 c# idictionary

我的问题类似于前一个问题,但这个问题的答案不适用于此问题.

好吧,我想为两者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;
}
Run Code Online (Sandbox Code Playgroud)

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

这可能吗?

fou*_*ght 8

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

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

无论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>,可能完全避免这个问题.即使问题不是不可避免的(例如某处,对其中一个接口进行转换),实现两个扩展方法仍然会更好,每个接口一个.

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

  • 那么“你的代码在这里”应该看起来像“if(dictionary is IReadonlyDictionary) ... else if(dictionary is IDictionary) ... else throw(new NotDictionaryException())”吗? (2认同)
  • @ user1067514你肯定可以拥有专门为`IDictionary <K,V>`和`IReadOnlyDictionary <K,V>`运行的代码,但没有理由不实现一些有效的东西,如果它只是'IEnumerable <KeyValuePair <K,V >>` (2认同)
  • @ user1067514和juharr一样,你可以完全基于`IEnumerable <KeyValuePair>`来实现它,但你需要枚举所有的键,从而破坏字典的性能优势.最好先尝试转换为`IReadOnlyDictionary`和`IDictionary`,这样你就可以使用`ContainsKey`方法,然后回到`IEnumerable`和`Where`,如果它不起作用. (2认同)

bin*_*nki 5

经过一些试验,我记得/发现C#愿意通过偏爱子类而不是基类来解决歧义。因此,要实现类型安全,您需要3种扩展方法而不是一种。而且,如果您还有其他类(例如Dictionary<TKey, TValue>实现IDictionary<TKey, TValue>和)IReadOnlyDictionary<TKey, TValue>,则需要编写越来越多的扩展方法……

以下内容使编译器非常满意。不需要强制转换就可以将传递的字典用作字典。我的策略是为最低的公分母实现我的实际代码IReadOnlyDictionary<TKey, TValue>,使用外观适应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;
}
Run Code Online (Sandbox Code Playgroud)

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


归档时间:

查看次数:

2204 次

最近记录:

7 年,10 月 前