【问题标题】:Multiple properties assignment多属性分配
【发布时间】:2013-06-06 08:57:58
【问题描述】:

我正在寻找一种在我的代码中实现 DRY 原则的方法。我有一个这样的代码。

private static bool Inconsistent(AdStats adStat)
{
  return
    adStat.Daily.Impressions != adStat.Hourly.Sum(h => h.Value.Impressions) ||
    adStat.Daily.Clicks != adStat.Hourly.Sum(h => h.Value.Clicks) ||
    adStat.Daily.Spent != adStat.Hourly.Sum(h => h.Value.Spent) ||
    adStat.Daily.SocialImpressions != adStat.Hourly.Sum(h => h.Value.SocialImpressions) ||
    adStat.Daily.SocialClicks != adStat.Hourly.Sum(h => h.Value.SocialClicks) ||
    adStat.Daily.SocialSpent != adStat.Hourly.Sum(h => h.Value.SocialSpent) ||
    adStat.Daily.UniqueImpressions != adStat.Hourly.Sum(h => h.Value.UniqueImpressions) ||
    adStat.Daily.UniqueClicks != adStat.Hourly.Sum(h => h.Value.UniqueClicks) ||
    adStat.Daily.SocialUniqueImpressions != adStat.Hourly.Sum(h => h.Value.SocialUniqueImpressions) ||
    adStat.Daily.SocialUniqueClicks != adStat.Hourly.Sum(h => h.Value.SocialUniqueClicks);
}

然后在社区的帮助下,我得到了一个很好的解决方案。

 Func<AdStatsItem, int>[] metricGetters = {
     s => s.Impressions,
     s => s.Clicks,
     s => s.Spent,
     //...
 };

  return metricGetters.Any(getter => getter(adStat.Daily)
                                  != adStat.Hourly.Sum(h => getter(h.Value)));

但我想不出一种方法可以对具有多个分配的代码使用相同的技术。

  hourly.Impressions += delta.Impressions;
  hourly.Clicks += delta.Clicks;
  hourly.Spent += delta.Spent;
  hourly.SocialImpressions += delta.SocialImpressions;
  hourly.SocialClicks += delta.SocialClicks;
  hourly.SocialSpent += delta.SocialSpent;
  hourly.UniqueImpressions += delta.UniqueImpressions;
  hourly.SocialUniqueImpressions += delta.SocialUniqueImpressions;
  hourly.UniqueClicks += delta.UniqueClicks;
  hourly.SocialUniqueClicks += delta.SocialUniqueClicks;

经过一番思考,我想出了这个。

  Expression<Func<AdStatsItem, int>>[] metricGetters = {
     stat => stat.Impressions,
     stat => stat.Clicks,
     //...
  };

  var type = typeof (AdStatsItem);

  foreach (var metric in metricGetters)
  {

    var body = (MemberExpression)metric.Body;
    var propertyName = body.Member.Name;
    var prop = type.GetProperty(propertyName);

    var val = (int)prop.GetValue(hourly, null) + (int)prop.GetValue(delta, null);

    prop.SetValue(hourly, val, null);        
  }

但是反射的使用真的让我很烦恼。有没有更好的方法摆脱上述冗余?

【问题讨论】:

  • 在这种情况下,您不会通过因式分解获得任何收益。如果您对使用的属性(如所有公共数字属性)有一些假设,则可以对其进行增强。否则你最终会得到一堆比你当前的实现可读性差得多的代码。

标签: c# .net reflection lambda dry


【解决方案1】:

您现有的代码非常好。 在 C# 中易于推广的内容存在限制,不幸的是,您遇到了一个不能推广的情况。您可用的解决方案具有一定的“固定成本”(主要是可读性)与之相关;我觉得只有当您拥有 数百个 此类属性和/或聚合器例程时,它们才会有所帮助。

也就是说,如果您真的想要一个通用的“增量器”来处理属性列表,我宁愿使用表达式树而不是纯反射。它更简洁,“可检查”,可修改,并且您只需支付一次组装成本(使用简单 IL 编译的委托的后续成本很便宜)。

static Action<AdStatsItem, AdStatsItem> GetAggregateUpdater
       (IEnumerable<Expression<Func<AdStatsItem, int>>> metricGetters)
{
    var aggregate = Expression.Parameter(typeof(AdStatsItem), "aggregate");
    var delta = Expression.Parameter(typeof(AdStatsItem), "delta");

    var increments = from metricGetter in metricGetters
                     let memberExpression = (MemberExpression)metricGetter.Body
                     let property = (PropertyInfo)memberExpression.Member
                     select Expression.AddAssign
                             (Expression.Property(aggregate, property),
                              Expression.Property(delta, property));

    var lambda = Expression.Lambda<Action<AdStatsItem, AdStatsItem>>
                 (Expression.Block(increments), aggregate, delta);

    return lambda.Compile();
}

生成的 lambda(带有您的示例指标)如下所示:

.Lambda #Lambda1<System.Action`2[AdStatsItem,AdStatsItem]>(
    AdStatsItem $aggregate,
    AdStatsItem $delta) {
    .Block() {
        $aggregate.Impressions += $delta.Impressions;
        $aggregate.Clicks += $delta.Clicks;
        $aggregate.Spent += $delta.Spent
    }
}

...这与“硬编码”解决方案几乎相同

可以这样使用:

Expression<Func<AdStatsItem, int>>[] metricGetters = 
{
    s => s.Impressions,
    s => s.Clicks,
    s => s.Spent,
};

// Cache this; don't create it each time.
var updater = GetAggregateUpdater(metricGetters);

var delta = new AdStatsItem
{
    Impressions = 100,
    Clicks = 4,
    Spent = 33,
};

var hourly = new AdStatsItem
{
    Impressions = 2000,
    Clicks = 140,
    Spent = 400,
};

updater(hourly, delta);

【讨论】:

  • 实际上,只有表达式树的解决方案正是我想要的。据我了解,调用已编译委托的成本与我将其静态实现的成本几乎相同。
  • 是的,几乎相同(如果您忽略委托的调用开销 - 大致相当于虚拟方法调用)。基准测试。
猜你喜欢
  • 1970-01-01
  • 2017-09-12
  • 2016-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-15
  • 1970-01-01
相关资源
最近更新 更多