2014-10-17 64 views
1

我有一個基類(抽象)有多種實現,其中一些含有其他實現的集合屬性 - 像這樣:反思,逆變和多態性

class BigThing : BaseThing 
{ 
    /* other properties omitted for brevity */ 
    List<SquareThing> Squares { get; set; } 
    List<LittleThing> SmallThings { get; set;} 
    /* etc. */ 
} 

現在有時我得到一個BigThing,我需要將它映射到另一個BigThing以及它的所有BaseThings集合。但是,如果發生這種情況,我需要能夠判斷源BigThing中的BaseThing是否爲BaseThing,因此應該將其添加到目標BigThing的集合中,或者它是否爲現有 BaseThing應映射到目標集合中已存在的BaseThings之一。 BaseThing的每個實現都有一套不同的匹配標準,對其進行評估以評估新功能。我有以下通用擴展方法來評估此:

static void UpdateOrCreateThing<T>(this T candidate, ICollection<T> destinationEntities) where T : BaseThing 
{ 
    var thingToUpdate = destinationEntites.FirstOrDefault(candidate.ThingMatchingCriteria); 
    if (thingToUpdate == null) /* Create new thing and add to destinationEntities */ 
    else /* Map thing */ 
} 

這工作正常。不過,我認爲我迷戀BigThe的交易方式。我想讓這個方法是通用的,因爲有幾種不同的BigThings,我不想爲每個方法編寫方法,如果我添加集合屬性,我不想改變我的方法。我寫了下面的通用方法是利用反射的,但它不是

void MapThing(T sourceThing, T destinationThing) where T : BaseThing 
{ 
    //Take care of first-level properties 
    Mapper.Map(sourceThing, destinationThing); 

    //Now find all properties which are collections 
    var collectionPropertyInfo = typeof(T).GetProperties().Where(p => typeof(ICollection).IsAssignableFrom(p.PropertyType)); 

    //Get property values for source and destination 
    var sourceProperties = collectionPropertyInfo.Select(p => p.GetValue(sourceThing)); 
    var destinationProperties = collectionPropertyInfo.Select(p => p.GetValue(destinationThing)); 

    //Now loop through collection properties and call extension method on each item 
    for (int i = 0; i < collectionPropertyInfo.Count; i++) 
    { 
     //These casts make me suspicious, although they do work and the values are retained 
     var thisSourcePropertyCollection = sourceProperties[i] as ICollection; 
     var sourcePropertyCollectionAsThings = thisSourcePropertyCollection.Cast<BaseThing>(); 
     //Repeat for destination properties 

     var thisDestinationPropertyCollection = destinationProperties[i] as ICollection; 
     var destinationPropertyCollectionAsThings = thisDestinationPropertyCollection.Cast<BaseThing>(); 

     foreach (BaseThing thing in sourcePropertyCollectionAsThings) 
     { 
      thing.UpdateOrCreateThing(destinationPropertyCollectionAsThings); 
     } 
    } 
} 

這編譯和運行,以及擴展方法成功運行(如預期的匹配和映射),但在destinationThing集合屬性值保持不變。我懷疑我已經失去了所有鑄造的原始destinationThing屬性的參考,並分配給其他變量等。我的方法在這裏存在根本上的缺陷嗎?我錯過了一個更明顯的解決方案嗎?或者是我的代碼中有一些簡單的錯誤導致了不正確的行爲?

+6

您的標題需要「...哦,我的!」在末尾;) – Plutonix 2014-10-17 00:41:01

+0

我認爲它:)但我擔心奧茲/東方模特巫師;) – NWard 2014-10-17 01:10:04

+0

不幸的是,你不顯示所有的代碼。所以沒有人能看到你如何初始化「destinationPropertyCollectionAsThings」對象。當然,這就是這個問題的核心。你真的應該發佈一個簡明但完整的代碼示例,可靠地演示該問題。 – 2014-10-17 01:27:00

回答

2

沒有想太多,我會說你已經陷入遺產濫用的陷阱,現在試圖拯救自己,你可能要考慮你怎麼能解決你的問題,同時放棄現有的設計,導致你首先要做這樣的事情。我知道,這是痛苦的,但它在未來:-)

也就是說投資,

var destinationPropertyCollectionAsThings = 
     thisDestinationPropertyCollection.Cast<BaseThing>(); 

    foreach (BaseThing thing in sourcePropertyCollectionAsThings) 
    { 
     thing.UpdateOrCreateThing(destinationPropertyCollectionAsThings); 
    } 

你正在失去你的ICollection當你使用Linq Cast操作員創建新IEnumerable<BaseThing>。你也不能使用反變換,因爲ICollection不支持它。如果可以的話,你會逃脫as ICollection<BaseThing>這將是很好的。

相反,您必須動態構建泛型方法調用並調用它。最簡單的方法可能是使用dynamic關鍵字,並讓運行時間計算出來,因此:

thing.UpdateOrCreateThing((dynamic)thisDestinationPropertyCollection); 
+0

謝謝克里斯。我最終將我的映射方法添加爲BaseThing上的抽象實例方法 - 並不像反射版本那麼好,但有點更明顯,希望維護人員不會忘記更新Map方法,因爲它在同一個類中:)您的答案是幫助我理解爲什麼我的代碼無法正常工作。再次感謝! – NWard 2014-10-18 22:54:06