在可能未初始化的Dictionary元素上执行加号等于操作的简洁方法

Ala*_*ain 4 c# extension-methods dictionary .net-4.0

我正在寻找一种扩展方法或任何其他建议,可以帮助我尽可能简洁地使用此代码.

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

上面的代码有一个相当明确的目的,我用一个复合键将值分成组.但是,此代码将失败,因为字典最初为空,而+ =运算符将不知道在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;
}
Run Code Online (Sandbox Code Playgroud)

但是,当然,即使这样也无法编译,因为没有办法限制V的类型,使得运算符+=存在.

规则

  • 只有一次迭代通过double for循环.在使用0值初始化字典之前不允许循环一次.
  • 可以使用辅助方法或扩展方法,但我希望内循环是一个衬里.
  • 尽可能通用且可重用,这样我就不需要为不同类型(小数,整数等)的类似存储创建一堆相同的函数.

作为参考 - 在类的其他地方,键被定义为一个实际的元组(只有命名参数),这就是为什么它可以用作字典键:

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

感谢Jon Skeet将Lambda函数作为第三个参数传递给我的扩展方法的想法.甚至不需要将其限制为+ =操作.如果值已经存在,则可以传递任何操作来设置新值.

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

例子:

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

无尽的乐趣!

Jon*_*eet 5

首先,我建议不要做任何可能的事情,尽可能缩短可读性的潜在成本.例如,我会在foreach主体周围添加大括号,如果更可读的解决方案最终是两行而不是一行,我会对此感到高兴.

其次,我将假设对于您感兴趣的任何类型,默认值是自然零.

现在,你可以写:

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

然后你可以使用:

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

使用ConcurrentDictionary也会很好用.

另外,如果可以,我会尝试将其作为LINQ查询重新编写.如果混合使用GroupBy,我会不会感到惊讶,SumToDictionary允许你以声明的方式表达整个事物.