反思、反方差和多态性

本文关键字:多态性 方差 反思 | 更新日期: 2023-09-27 18:26:46

我有一个包含多个实现的基类(抽象),其中一些包含其他实现的集合属性,比如:

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,因此应该添加()-ed到目标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 */
}

这很好用。然而,我认为我正在迷失在处理BigThings的方法中。我想让这个方法通用,因为有几种不同类型的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属性的引用,包括所有的强制转换和对其他变量的赋值等等。我的方法是否存在根本缺陷?我是不是错过了一个更明显的解决方案?或者我的代码中是否存在导致错误行为的简单错误?

反思、反方差和多态性

不用想太多,我想说你已经陷入了继承滥用陷阱,现在为了拯救自己,你可能需要考虑如何解决你的问题,同时放弃最初导致你做这些事情的现有设计。我知道,这很痛苦,但这是对未来的投资:-)

也就是说,

   var destinationPropertyCollectionAsThings = 
       thisDestinationPropertyCollection.Cast<BaseThing>();
    foreach (BaseThing thing in sourcePropertyCollectionAsThings)
    {
        thing.UpdateOrCreateThing(destinationPropertyCollectionAsThings);
    }

当您使用创建新IEnumerable<BaseThing>的Linq Cast运算符时,您将丢失ICollection。你也不能使用逆变,因为ICollection不支持它。如果可以的话,你可以使用as ICollection<BaseThing>,这会很好。

相反,你必须动态地构建通用方法调用,并调用它。最简单的方法可能是使用dynamic关键字,让运行时计算出来,例如:

thing.UpdateOrCreateThing((dynamic)thisDestinationPropertyCollection);