反思、反方差和多态性
本文关键字:多态性 方差 反思 | 更新日期: 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);