【问题标题】:Generic method with dynamically selected parameter in predicate在谓词中动态选择参数的通用方法
【发布时间】:2020-08-11 16:14:41
【问题描述】:

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

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 并且返回类型是 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 次。

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

【问题讨论】:

  • 你能让你所有的类都继承自一个父类吗?如果是这样,那么您可以让父母有一个方法来检查一致性,孩子们会随心所欲地实施它。
  • 不,它们之间有一些不常见的属性,对于同一对象的其他属性使用此方法仍然会有问题。
  • 你可以传入一个Func 来获取所需的属性吗?
  • 您的意思是传递x =&gt; x.ItemType != group.First().ItemType 作为参数?我可以但仍然会一次又一次地重复编写整个条件,并且存在错误的风险,例如== 而不是!=传递不同的谓词
  • 不,我的意思是只传递x =&gt; x.ItemType

标签: c# .net linq generics predicate


【解决方案1】:

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

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&lt;Func&lt;&gt;&gt; 并提取名称,然后将参数编译为 lambda 以使用(可能很慢),或使用反射而不是 lambda 属性访问器(也可能很慢)。

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

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

【讨论】:

  • 我已经做过类似的事情了,我忘了把它发到这里。实际上,当propFn(x) 为空时,您应该为此创建自己的方法而不是等于,以避免空引用异常,因为如果 this 和 firstPropValue 都为空,它应该工作并返回 true,如果只有一个为空,它应该返回 false。我以 typeof(TProp).Name 异常返回属性名称
  • @kkamil4sz 我添加了空处理。请注意,typeof(TProp).Name 将类似于 System.String,这不是很有帮助。
  • 哦,对了,当你有自己的课程时这很好,但如果它像字符串这样的东西,它就不是很有帮助。如何从 propFn 获取属性名称?
  • @kkamil4sz 我添加了一个Expression&lt;&gt; 版本,它缓存编译结果,因此它在调用时不必不断编译,并从Expression&lt;Func&lt;&gt;&gt; 中提取成员名称。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-06
  • 1970-01-01
  • 2022-07-29
  • 1970-01-01
  • 2020-07-02
  • 1970-01-01
相关资源
最近更新 更多