12

私の質問は前の質問と似ていますが、その質問への回答はこの質問には当てはまりません。

IDictionaryさて、私はとIReadOnlyDictionaryインターフェイスの両方の拡張メソッドを書きたいと思います:

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)

これは可能ですか?

4

3 に答える 3

10

必要な拡張メソッドは 1 つだけです。次のように再定義します。

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 の質問に答える上で正しいと述べられています。ただし、他の人が以下にコメントしているように、これは本当に良い考えではありません.

1 つには、ディクショナリ/マップの要点は O(1) (一定時間) ルックアップです。上記のソリューションを使用して、O(1) 操作を O(n) (線形時間) 操作に変換しました。ディクショナリに 1,000,000 項目ある場合、キー値を検索するのに最大で 100 万倍の時間がかかります (本当に運が悪い場合)。これは、パフォーマンスに重大な影響を与える可能性があります。

小さな辞書はどうですか?さて、質問は、本当に辞書が必要ですか? リストにしたほうがいいですか?または、さらに良い質問: O(1) ルックアップに対して O(n) を使用することのパフォーマンスへの影響にどこから気付き始めますか? また、そのようなルックアップを実行する頻度はどれくらいになると予想されますか?

最後に、 の動作が の動作のスーパーセットであるため、と の両方 IReadOnlyDictionary<TKey, TValue>を実装するオブジェクトを使用した OP の状況IDictionary<TKey, TValue>は、奇妙です。そして、標準クラスはすでにこれらのインターフェースの両方を実装しています。したがって、OP の (特殊化されていると思います) 型が代わりに継承された場合、読み取り専用機能が必要な場合、型は単純に にキャストされ、おそらくこの問題全体を完全に回避できます。問題が避けられない場合でも (たとえば、どこかでインターフェイスの 1 つにキャストが行われるなど)、インターフェイスごとに 1 つずつ、2 つの拡張メソッドを実装したほうがよいでしょう。IDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>Dictionary<TKey, TValue>Dictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>

このトピックを終了する前に、もう 1 つ注文する必要があると思います。a を to にキャストするDictionary<TKey, TValue>IReadOnlyDictionary<TKey, TValue>、キャストされた値を受け取るもの自体が基になるコレクションを変更できないことが保証されるだけです。ただし、キャストされた参照の作成元のディクショナリ インスタンスへの他の参照が、基になるコレクションを変更しないという意味ではありません。IReadOnly*これは、コレクション インターフェイスの背後にある問題の 1 つです。これらのインターフェイスによって参照されるコレクションは、真の意味で「読み取り専用」(または、しばしば暗に示される不変) コレクションではない可能性があり、特定の参照によるコレクションの変更を防止するだけです (にもかかわらず、具体的なReadOnly*クラスの実際のインスタンス)。これが (とりわけ) 理由の 1 つです。System.Collections.Immutableコレクション クラスの名前空間が作成されました。この名前空間のコレクションは、真に不変のコレクションを表しています。これらのコレクションの「変異」により、変異した状態で構成されるまったく新しいコレクションが返され、元のコレクションは影響を受けません。

于 2013-09-05T16:45:17.797 に答える
8

いくつかの実験の後、C# は基本クラスよりもサブクラスを優先することであいまいさを解決しようとしていることを思い出しました/発見しました。したがって、タイプ セーフでこれを行うには、1 つではなく3 つの拡張メソッドが必要です。また、 のように と の両方をDictionary<TKey, TValue>実装する他のクラスがある場合は、さらに多くの拡張メソッドを作成する必要があります…</p> 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;
}

このアプローチで最も厄介なのは、非常に多くのボイラープレート (2 つのシン ラッパー拡張メソッドと 1 つの実際の実装) を書かなければならないことです。ただし、拡張機能を使用したコードはシンプルかつ明確に保つことができるため、これは正当化できると思います。醜さは拡張クラスに含まれています。IReadOnlyDictionary<TKey, TValue>また、拡張機能の実装は 1 つのメソッドに含まれているため、バリアントが更新されたときにオーバーロードを更新することを心配する必要はありません。デフォルト値で新しいパラメーターを追加しない限り、パラメーターの型を変更すると、コンパイラはオーバーロードを更新するように通知します。

于 2016-01-25T17:32:03.207 に答える
1

あなたのケースで望ましい動作を得るには、fourpastmidnight's answerを参照してください。


ただし、質問に直接答えるには、いいえmyDic.GetNullable、 2 つの拡張メソッドがある場合に構文を機能させることはできません。どちらを呼び出すかを知る方法がないため、あいまいであるというエラーメッセージは正しかったです。あなたのIReadOnlyDictionaryメソッドがたまたま何か違うことをした場合、それをすべきDictionary<TKey, TValue>かどうか?

AsEnumerableメソッドが存在するのはこのためです。IEnumerable両方に対して拡張メソッドが同じ名前で定義されているIQueryableため、特定の拡張メソッドが確実に呼び出されるように、両方を実装するオブジェクトをキャストする必要がある場合があります。

2 つのメソッドの実装が異なると仮定すると次のような同様のメソッドを含めることができます。

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

(技術的には、という名前のメソッドは実際のReadOnlyDictionaryAsReadOnlyを返す必要がありますが、これはデモ用です)

于 2013-09-05T17:36:04.093 に答える