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

问题描述

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

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,返回类型为{{ 1}}。

我还需要使用不同的对象类型,因此在另一种方法中,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?)

有什么想法吗?我不确定反射,因为根据集合大小和唯一条目的数量,此方法可以轻松调用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属性访问器(也可能很慢)。

要使用反射,建议您缓存已编译的函数,这样您就不必在每次调用该方法时都不断地重新编译:

// [Expression] => [Func]
Dictionary<LambdaExpression,Delegate> propFnCache = new Dictionary<LambdaExpression,Delegate>();

private TProp CheckConsistency<TClass,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;
    }
}