6

このコードをできるだけ簡潔にするのに役立つ拡張メソッドまたはその他の提案を探しています。

foreach( Layer lyr in this.ProgramLayers )
  foreach( UWBCEvent evt in this.BcEvents.IncludedEvents )
    EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] += 
       GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions);

上記のコードにはかなり明確な目的があります。複合キーを使用して値をグループにまとめています。ただし、辞書は最初は空であり、+= 演算子はバケットを 0 から開始することを認識しないため、このコードは失敗します。

私が思いつくことができる最高のものはこれです:

public V AddOrSet<K, V>(this Dictionary<K, V> dict, K key, V value)
{
    if( dict.ContainsKey(key) )
        dict[key] += value;
    else
        dict[key] = value;
}

しかしもちろん、演算子が存在するように V の型を制限する方法がないため、それでもコンパイルされません+=

ルール

  • double for ループを 1 回だけ反復します。ディクショナリを 0 値で初期化する前に 1 回ループすることはできません。
  • ヘルパーメソッドや拡張メソッドも使えるが、内側のループはワンライナーにしたい。
  • 可能な限り汎用的かつ再利用可能にして、異なる型 (10 進数、整数など) で同様のバケットを作成するために同一の関数を多数作成する必要がないようにします。

参考までに - クラスの他の場所では、キーは実際のタプル (名前付きパラメーターのみ) として定義されているため、辞書キーとして使用できます。

private Dictionary<EventGroupIDLayerTuple, Decimal> _EventGroupLayerLosses;
public class EventGroupIDLayerTuple : Tuple<Int32, Layer>
{
    public EventGroupIDLayerTuple(Int32 EventGroupID, Layer Layer) : base(EventGroupID, Layer) { }
    public Int32 EventGroupID { get { return this.Item1; } }
    public Layer Layer { get { return this.Item2; } }
}

解決

Lambda 関数を 3 番目のパラメータとして拡張メソッドに渡すというアイデアをくれた Jon Skeet に感謝します。+= 操作に制限する必要さえなくなりました。値が既に存在する場合、新しい値を設定するために任意の操作を渡すことができるのは十分に一般的です。

//Sets dictionary value using the provided value. If a value already exists, 
//uses the lambda function provided to compute the new value.
public static void UpdateOrSet<K, V>(this Dictionary<K, V> dict, K key, V value, Func<V, V, V> operation)
{
    V currentValue;
    if( dict.TryGetValue(key, out currentValue) )
        dict[key] = operation(currentValue, value);
    else
        dict[key] = value;
}

例:

mySums.UpdateOrSet("Bucket1", 12, (x, y) => x + y);
myStrs.UpdateOrSet("Animals", "Dog", (x, y) => x + ", " + y);
myLists.UpdateOrSet("Animals", (List<T>) Dogs, (x, y) => x.AddRange(y));

無限の楽しみ!

4

4 に答える 4

8

まず、可読性を犠牲にしてでも、できる限り短くしようとはしないことをお勧めします。たとえば、foreach本文の周りに中かっこを追加します。より読みやすい解決策が 1 行ではなく 2 行になった場合は、それで満足です。

第 2 に、関心のある型のデフォルト値は自然なゼロであると仮定します。

今、あなたは書くことができます:

public static void AddOrSet<K, V>(this Dictionary<K, V> dict,
                                  K key, V value, Func<V, V, V> addition)
{
    V existing;
    dict.TryGetValue(key, out existing);
    dict[key] = addition(existing, value);
}

次に、次を使用できます。

EventGroupLayerLosses.AddOrSet(new EventGroupIDLayerTuple(evt.EventGroupID, lyr),
    GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions),
    (x, y) => x + y);

を使用しConcurrentDictionaryてもうまくいきます。

さらに、可能であれば、これを LINQ クエリとして作り直そうと思います。GroupBy、 、Sumおよびの混合により、ToDictionaryすべてを宣言的に表現できるようになったとしても、私は驚かないでしょう。

于 2012-08-10T16:21:58.913 に答える
1

ここで null 合体を使用できますか?

foreach( Layer lyr in this.ProgramLayers )
   foreach( UWBCEvent evt in this.BcEvents.IncludedEvents )
    EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] = (EventGroupLayerLosses[new EventGroupIDLayerTuple(evt.EventGroupID, lyr)] ?? 0) + 
   GetEL(evt.AsIfs, lyr.LimitInMillions, lyr.AttachmentInMillions);
于 2012-08-10T16:23:14.507 に答える
1

.NET 4 には、新しいタイプのディクショナリ クラスConcurrentDictionary<TKey, TValue>. このクラスには、AddOrUpdate探している動作を示す非常に役立つメソッド (いくつかのオーバーロードを含む) があります。

ConcurrentDictionary に関する MSDN ドキュメント

于 2012-08-10T16:21:13.733 に答える
0

あなたはこのようなことを試すことができます。

private void AddOrUpdate<K, V>(this Dictionary<K, V> dict, K key, Func<V> newValue, Func<V, V> updateValue) 
{ 
    V value;

    if( !dict.TryGetValue(key, out value) ) 
        value = newValue();
    else 
        value = updateValue(value); 

    dict[key] = value; 
}
于 2012-08-10T16:33:56.583 に答える