谓词中具有动态选择参数的通用方法

Generic method with dynamically selected parameter in predicate

我有许多不同类型的对象,我需要以相同的方式检查每个对象的几个不同属性。我想像这样在对象初始值设定项中使用此方法:

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 中还有其他 属性 需要以相同的方式检查不同的类型,所以我有类似的方法但是 .ItemType 到处都变成了 .Name return 类型是 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();
        }
    }

我通过 returning 整个项目解决了 returning 值的问题,因此在调用此方法时我可以 CheckConsistency().Property。 但我不知道如何处理 (what here?).

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

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

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

使用访问器函数并泛化 属性 类型和对象类型,您有:

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 属性 访问器(也可能很慢)。

要使用反射,我建议缓存已编译的函数,这样您就不会在每次调用方法时都re-compile:

// [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;
    }
}