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 的类型以使运算符+=存在。

规则

  • 只有一次迭代通过双 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; } }
}

解决方案

感谢 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;
}

例子:

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正文周围添加大括号,如果一个更具可读性的解决方案最终变成两行而不是一行,我会很高兴的。

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

现在,您可以编写:

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如果混合使用,SumToDictionary允许您以声明方式表达整个事情,我不会感到惊讶。

于 2012-08-10T16:21:58.913 回答
1

.NET 4 有一种新型的字典类,ConcurrentDictionary<TKey, TValue>. 这个类有一个非常有用的AddOrUpdate方法(有一些重载),它展示了你正在寻找的行为。

ConcurrentDictionary 上的 MSDN 文档

于 2012-08-10T16:21:13.733 回答
1

我们可以在这里使用空合并吗?

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 回答
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 回答