2013-06-06 115 views
1

我正在尋找一種方法在我的代碼中實現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);   
    } 

但反射的用法真的讓我感到困擾。有沒有更好的辦法擺脫上述冗餘?

+0

在這種情況下,你不會獲得通過保理什麼。如果您對所使用的屬性有一些假設,如所有公共數字屬性,則可以對其進行增強。否則,你最終會得到一堆比你當前的實現更不可讀的代碼。 – Pragmateek

回答

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 #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); 
+0

實際上,只有表達式樹的解決方案正是我所期待的。據我瞭解,調用編譯委託的成本幾乎相同,如果我會靜態實現它。 – Causarium

+1

是的,幾乎相同(如果您忽略代理的調用開銷 - 與虛擬方法調用大致相當)。基準測試。 – Ani