2016-10-28 11 views
0

具体的な理由から、アプリケーションでは2種類のディクショナリラッパーを使用する必要があります(値型、次のようになります:"class"型と "struct"型制約の両方で動作するジェネリッククラスを設計する必要がある

internal class StructDictionary<K, V> where V : struct 
{ 
    public IDictionary<K, V> _dictionary = new Dictionary<K, V>(); 

    public StructDictionary(Dictionary<K, V> dictionary) 
    { 
     _dictionary = dictionary; 
    } 

    public V? this[K key] 
    { 
     get 
     { 
      V foundValue; 
      return _dictionary.TryGetValue(key, out foundValue) ? foundValue : (V?)null; 
     } 
     set 
     { 
      if (!value.HasValue) 
       return; 

      _dictionary[key] = value.Value; 
     } 
    } 
} 

とインスタンスタイプ

internal class InstanceDictionary<K, V> where V : class 
{ 
    public IDictionary<K, V> _dictionary = new Dictionary<K, V>(); 

    public InstanceDictionary(Dictionary<K, V> dictionary) 
    { 
     _dictionary = dictionary; 
    } 

    public V this[K key] 
    { 
     get 
     { 
      V foundValue; 
      return _dictionary.TryGetValue(key, out foundValue) ? foundValue : null; 
     } 
     set 
     { 
      if (value == null) 
       return; 

      _dictionary[key] = value; 
     } 
    } 
} 

のためにこれらの2クラスは、次のように使用されることを意図されています

void Main() 
{ 
    var structDictionary = new StructDictionary<string, double>(new Dictionary<string, double>(){{"key1",1}}); 
    var instanceDictionary = new InstanceDictionary<string, string>(new Dictionary<string, string>(){{"key1","value1"}}); 

    structDictionary["key1"] = 70; 
    instanceDictionary["key1"] = "NEW_VAL"; 

} 

2つの辞書ラッパーのロジックは事実上同じなので、2つのクラスを1つのクラスに置き換えて、には影響しません。これらのオブジェクトは広範囲に使用されるためパフォーマンスに影響します。

これまで、私は基本的にインデクサーからオブジェクトを返すこの最適ではない解決策を見つけました。この解決策はボクシング/アンボクシング(下のコードのコメントを参照)があり、非常に高速ではないConvert.ChangeTypeも使用しているので実際には機能しません。

internal class BetterDictionary<K, V> 
{ 
    public IDictionary<K, V> _dictionary = new Dictionary<K, V>(); 

    public BetterDictionary(Dictionary<K, V> dictionary) 
    { 
     _dictionary = dictionary; 
    } 

    public object this[K key] 
    { 
     get 
     { 
      V foundValue; 
      return _dictionary.TryGetValue(key, out foundValue) ? (object)foundValue /* Issue 1: boxing here when V is value type */: null; 
     } 
     set 
     { 
      if (value==null) 
       return; 

      _dictionary[key] = (V)Convert.ChangeType(value, typeof(V)); // Issue 2: slight performance hit due to ChangeType ? 

      // more code here 
     } 
    } 
} 

結論として、パフォーマンスに影響を与えないよりよい解決策(より良いBetterDictionaryラッパー)があり、基本的に私はVジェネリックパラメータの値の型とインスタンスタイプの両方をサポートする汎用オブジェクトをしたいです。私は私が唯一のものでインデクサーを持つオブジェクトにバインドすることができ、いくつかの特定の癖を持っている従来のUIライブラリで働いているので、それはだ、このやや特異な辞書ラッパーを使用する必要がない理由質問に対して

EDITフォーム、追加私は存在しない辞書の値のnullを返す必要があります。基本的には、UIのグリッド列として表示するための辞書データを移調しています。UIを変更することはできませんし、内部の辞書を変更することもできないため、このようなラッパーを使用しなければなりませんでした。

また、インデクサーセット{}は実際には辞書値を設定するだけでなく、辞書を使用できないため、より多くのコードを実行しています。

文脈の欠如に対する謝罪、私はより多くの文脈で例を更新しようとします。

+0

私は混乱していると思います。なぜインデクサーを 'public V this [K key]'にして変換をスキップできないのですか? –

+0

ラッピングの目的の1つである、そのキーの値がない場合は、nullを返す必要があります。 – brakeroo

+1

だから、基本的には 'TryGetValue()'が失敗した場合に 'null'を返そうとするラッパーとして使うだけです。個人的には、単純な古い辞書と 'TryGetValue'を直接使用することをお勧めします。あなたが本当にあなたの 'BetterDictionary'を動作させたいなら、私が持っている最良のアイデアは、私が言及したようにインデクサーから' V'を返すことです。次に、nullの代わりに 'default(V)'を使用するように、見つからないケースを変更します。次に、ヌル可能型を 'V'として明示的に定義した場合、あなたは望みどおりにnullを取得し、制約のない本当に汎用クラスを持ちます。 –

答えて

2

インデクサーがnullをセンチネル値として使用しないようにデザインを修正する必要があります。

1の場合、nullは完全に有効な参照型の値です。たぶんあなたのコードでは、決してそのようには使われませんが、どんな場合でもそのような値をオーバーロードするのは混乱します。通常のIDictionary<TKey, TValue>実装では、nullを指定したキーの値として使用できます。

もう1つは、投稿したコードが間違っているようです。インデクサーに渡されたvaluenullの場合、元のクラスと新しい "結合された"両方の実装のセッターは、すべて戻ります。しかし、キーが辞書に存在する場合は、nullを指定のキーに割り当てることができ、その後、キーの値を取得すると、nullの値が返されます。default(V)として(通常の一般的な辞書で)

set 
    { 
     if (!value.HasValue) 
     { 
      _dictionary.RemoveKey(key); 
      return; 
     } 

     _dictionary[key] = value.Value; 
    } 

一つのコメントが使用することを提案している:非常に少なくとも

、各セッターは、(例として、値型のバージョンを使用して)このような、よりになりべき値の型のパラメータVとして具体的なNullable<T>を使用して、値を返します。これはうまくいく可能性がありますが、参照タイプを使用するときに "見つからない"ためのセンチネルとしてnullを使用することと同じ問題があります。私。これは、実際には、通常、どのキーでも完全に有効な辞書の値です。代わりに、 "見つからない"値を表現するためにそれを使用しないと、インプリメンテーションは他の辞書実装と混同され、一貫性がありません。null値はキーにとって合法ではありません。キーを削除するには、実際にその値を非法的なnullの値に設定する必要があります(問題をもっと混乱させるだけですが、正当な値ではありませんが、インデクサーに割り当てる必要があります)。

値型を扱うときにnullを使用するオプションは非常に制限されています。ボックスにすることができます(そのコストがかかります)、またはNullable<T>(参照タイプと互換性がありません)を使用することができます。

struct NullableEx<T> 
{ 
    private bool _hasValue; 
    private T _value; 

    public NullableEx(T value) 
    { 
     _hasValue = true; 
     _value = value; 
    } 

    public bool HasValue { get { return _hasValue; } } 
    public T Value { get { return _value; } } 

    // You can also e.g. add implicit operators to convert 
    // between T and NullableEx<T>, to implement equality 
    // and hash code operations, etc. 
} 

は、その後、あなたのインデクサがあなたのクラスの種類NullableEx<V>を持たせることができます:あなたは本当にこの操作を行う必要がある場合

は、私が参照型を可能にNullable<T>の独自のバージョンを書いてお勧めします。キーが存在しない場合、new NullableEx<V>()を返すことができます。

発信者はHasValueValueを明示的に確認する必要がありますが、どちらのIMHOもDictionary<TKey, TValue>.TryGetValue()を直接使用するより便利ではありません。しかし、それは動作し、あなたの現在の回避策にはランタイムコストはありません。

あなたの質問に欠けている重要な点の1つは、コードでラッパー実装をどのように使用するのですか?これが広く使われる重要な戦略であるかどうかは分かりません。使用している実装のバージョンに応じて、壊れやすく断片化しています。おそらく、このアプローチに価値があると感じる非常に特殊なシナリオがあります。具体的なシナリオを説明し、通常の辞書ベースの実装があなたにとってうまくいかない理由を説明し、の問題に対処する方法を、アプローチとは異なる方法で助けを求めるあなたは現在取っています。

関連する問題