多个属性分配
本文关键字:分配 属性 | 更新日期: 2023-09-27 18:17:06
我正在寻找一种在我的代码中实现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#中容易泛化的东西是有限制的,不幸的是,你遇到了一个不容易泛化的情况。你现有的解决方案都有一定的"固定成本"(主要是可读性);我觉得只有当你有数百个这样的属性和/或聚合器例程时,它们才会有帮助。
也就是说,如果您真的想在属性列表上使用一个通用的"递增器",我宁愿使用表达式树而不是纯粹的反射。它更干净,它是"可检查的",它是可修改的,而且你只需要支付一次组装成本(使用直接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);