谓词中具有动态选择参数的泛型方法



我有很多不同类型的对象,我需要以相同的方式从每个对象中检查一些不同的属性。我想在对象初始化器中使用这个方法,如下所示:

collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType 
{
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = MY_METHOD_HERE(group),
Name = MY_METHOD_HERE(group)
})

其中一个属性的强类型示例:

private ItemType CheckItemTypeConsistency(IGrouping<string, Item> group)
{
if (group.Any(x => x.ItemType != group.First().ItemType))
{
throw new ArgumentException($"Item with number {group.Key} is inconsistent", nameof(Item.ItemType));
}
else
{
return group.First().ItemType;
}
}

但我在Item中也有其他属性需要以与diffrent类型相同的方式进行检查,所以我有类似的方法,但.ItemType到处都改为.Name,返回类型为string

我也有不同的对象类型,需要使用它,所以在另一种方法中,Item改为Vehicle

如何创建这样的泛型方法?我试过这样的东西:

private TElement CheckConsistency<TKey, TElement>(IGrouping<TKey, TElement> group, (maybe something here?))
{
if (group.Any(x => x.(what here?) != group.First().(what here?)))
{
throw new ArgumentException($"Element with number {group.Key} is inconsistent");
}
else
{
return group.First();
}
}

我通过返回整个项解决了返回值的问题,所以在调用此方法时只能使用CheckConsistency().Property。但是我不知道该怎么处理(what here?)

我想也许我可以在(maybe something here?)中放一些东西,以某种方式代替(what here?)

有什么想法吗?我不确定反射,因为根据集合大小和唯一条目的数量,这个方法可以轻松调用1000多次。

@编辑:我们说它类似于合并两个文件中的数据。例如,两个项目的数据集,其中具有相同标识符等的项目的数量被添加在一起,但一些属性应该是相同的,比如名称,如果它们不同,那么就有问题,我需要抛出一个错误。有不同的数据集具有完全不同的属性,如车辆,但规则是相似的,有些字段只是添加在一起等,有些字段必须相同。

使用访问器函数并对属性类型和对象类型进行泛型,可以获得:

private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Func<TClass, TProp> propFn) {
var firstPropValue = propFn(group.First());
if (group.Any(x => firstPropValue == null ? propFn(x) == null : !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent");
}
else {
return firstPropValue;
}
}

你可以像这样使用:

var ans = collection.GroupBy(x => x.Identifier)
.Select(group => new SomeType {
Identifier = group.Key,
Quantity = group.Sum(i => i.Quantity),
Type = CheckConsistency(group, x => x.ItemType),
Name = CheckConsistency(group, x => x.Name)
});

如果在异常中包含正确的参数名称很重要,可以将其传入,接受Expression<Func<>>并提取名称,然后将参数编译为要使用的lambda(可能很慢(,或者使用反射而不是lambda属性访问器(也可能很慢。

要使用Reflection,我建议缓存编译后的函数,这样就不会每次调用该方法时都不断地重新编译:

// [Expression] => [Func]
Dictionary<LambdaExpression, Delegate> propFnCache = new Dictionary<LambdaExpression, Delegate>();
private TProp CheckConsistency<TClass, TProp>(IGrouping<string, TClass> group, Expression<Func<TClass, TProp>> propExpr) {
Func<TClass,TProp> propFn;
if (propFnCache.TryGetValue(propExpr, out var propDel))
propFn = (Func<TClass, TProp>)propDel;
else {
propFn = propExpr.Compile();
propFnCache.Add(propExpr, propFn);
}
var firstPropValue = propFn(group.First());
if (group.Any(x => !propFn(x).Equals(firstPropValue))) {
throw new ArgumentException($"Item with number {group.Key} is inconsistent", ((MemberExpression)propExpr.Body).Member.Name);
}
else {
return firstPropValue;
}
}

最新更新