【问题标题】:c# How To Use .Contains on Generic IEnumerable<T>c# 如何在通用 IEnumerable<T> 上使用 .Contains
【发布时间】:2017-11-21 13:02:30
【问题描述】:

我正在尝试创建一个通用方法,我想使用它来混合基于属性的对象列表。或者,您可以提供列表对象列表以将某些组合并在一起。这可以作为非泛型方法以及当我手动对属性进行编码等时正常工作。

方法头:

public static IEnumerable<T> MixObjectsByProperty<T>(
    IEnumerable<T> objects,
    string propertyName,
    IEnumerable<IEnumerable<T>> groupsToMergeByProperty = null)

方法主体的片段:

groups =
    (from item in objects
    let propertyInfo = item.GetType().GetProperty(propertyName)
    where propertyInfo != null
    group item by groupsToMergeByProperty
        .FirstOrDefault(ids => ids.Contains(propertyInfo.GetValue(item, null)))
        //.FirstOrDefault(ids => ids.Any(propertyInfo.GetValue(item, null)))
        ?.First()
        ?? propertyInfo.GetValue(item, null)
    into itemGroup
    select itemGroup.ToArray())
    .ToList();

如您所见,我在使用 IEnumerable&lt;T&gt; 上的 contains 方法时遇到问题,因为 propertyInfo.GetValue(item, null) 正在返回一个对象。我尝试了各种铸造尝试并改用.Any(),但我碰上了砖墙:(

任何帮助都会很棒,谢谢!

另外,如果您需要更多信息,请告诉我,但我想,本质上,我只需要知道如何使用 .Contains()&lt;T&gt;

将它们设置为相同的类型?自定义IEqualityComparer?

【问题讨论】:

  • 为什么不直接将GetValue 的结果转换为T
  • 如果您可以将 T 限制为基本类型或接口,这将大有帮助。
  • 除非您将T 转换为其他已知类型,否则您不能这样做
  • 为什么要传递属性名?为什么不像所有 LINQ 方法一样传递 表达式,例如使用 Func&lt;T,T&gt; propGetter 并传递 it =&gt;it.PropName 作为参数值。还是将整个条件作为表达式传递?

标签: c# linq generics ienumerable contains


【解决方案1】:

没有看到整个方法(因为您的类型签名清楚地表明它不是整个方法),这是一个示例实现:

public class Ext
{
    public static List<T[]> MixObjectsByProperty<T, TProp, U>(
        IEnumerable<T> source,
        Expression<Func<T, TProp>> property,
        IEnumerable<IEnumerable<U>> groupsToMix = null)
        where T : class
        where U : TProp
    {
        var prop = (PropertyInfo)(property.Body as MemberExpression)?.Member;
        if (prop == null) throw new ArgumentException("Couldn't determine property");

        var accessor = property.Compile();

        var groups = 
            from item in source
            let value = (U)accessor(item)
            group item by
                groupsToMix.FirstOrDefault((ids => ids.Contains(value)))
            into itemGroup
            select itemGroup.ToArray();
        return groups.ToList();
    }
}

看在上帝的份上停止传递属性名称和使用反射,Linq 的其余部分使用华丽的表达系统,你也应该这样做!

【讨论】:

  • 好的,哇,冷静点:P 感谢您的意见,它看起来不错 - 我没有意识到这一点。但是,你能让你的 linq 返回 List 吗?然后我可以充分测试这个潜在的解决方案 - 谢谢:)
  • @user2439970 确定,你去吧!
  • 如果你只打算编译它,为什么还要传递一个表达式。如果你只想要一个代表,就接受一个代表。
  • @Servy true,尽管如果 OP 想要获取附加信息(例如用于调试目的的属性名称等),该表达式很有用。
  • 此时您又回到了仅使用反射代码,而不是实际使用静态类型系统。
【解决方案2】:

我尝试过各种投射尝试

你试过这个吗?

.FirstOrDefault(ids => ids.Contains((T)propertyInfo.GetValue(item, null)))

由于ids 属于IGrouping&lt;TKey, TElement&gt; 类型,而TElement 在您的情况下属于T 类型,因此将属性值转换为T 将允许进行比较。

【讨论】:

  • 我试过了,但有趣的是,我早些时候又试了一次,再做一些调整,我就成功了! :) 我现在将发布答案,以便其他人可以看到我做了什么,等等。
【解决方案3】:

好的,所以我最终破解了它。我需要在我的通用方法标头/签名中添加更多细节。

    public static IEnumerable<T> MixObjectsByProperty<T, U>(
        IEnumerable<T> objects, string propertyName, IEnumerable<IEnumerable<U>> groupsToMergeByProperty = null)
        where T : class
        where U : class
    {
        ...

注意,我添加了允许我使用可为空运算符等的类约束。

并且正文变成了(在添加了一个转换为正确类型的 propertyValue 变量之后!):

            groups =
                (from item in objects
                 let propertyInfo = item.GetType().GetProperty(propertyName)
                 where propertyInfo != null
                 let propertyValue = (U)propertyInfo.GetValue(item, null)
                 group item by
                     groupsToMergeByProperty
                         .FirstOrDefault(ids => ids.Contains(propertyValue))
                         ?.First()
                     ?? propertyValue
                 into itemGroup
                 select itemGroup.ToArray())
                .ToList();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-20
    • 1970-01-01
    • 2011-04-02
    • 2023-03-14
    • 1970-01-01
    • 2015-03-12
    • 1970-01-01
    • 2012-09-17
    相关资源
    最近更新 更多