【问题标题】:Linq Select WhereLinq 选择位置
【发布时间】:2014-05-31 22:28:03
【问题描述】:

我经常发现自己在写这样的东西:

var fields = _type.GetProperties()
            .Select(prop => new { Prop = prop, Attrib = prop.GetCustomAttribute<ColumnAttribute>() })
            .Where(t => t.Attrib != null)
            .ToList();

让我烦恼的是,在 where 子句失败的情况下,我不必要地创建了对象。尽管开销很小,但我仍然更愿意保存分配,就像我只是简单地循环它或者做更痛苦的事情一样:

var fields = _type.GetProperties()
        .Select(prop =>
        {
            var attrib = prop.GetCustomAttribute<ColumnAttribute>();

            return attrib == null ? null : new {Prop = prop, Attrib = attrib};
        })
        .Where(t => t != null);

我是否缺少更好的模式/扩展方法?或者 LINQ 是否有可能在幕后进行优化?

非常感谢!

更新:

我想这就是我的意思,但我希望已经存在类似的东西,我只是搜索不佳:

public static IEnumerable<TResult> SelectWhereNotNull<TSource, TValue, TResult>(this IEnumerable<TSource> source, Func<TSource, TValue> valueSelector, Func<TSource, TValue, TResult> selector)
    where TValue:class
    where TResult:class
{
    return source
        .Select(s =>
        {
            var val = valueSelector(s);

            if (val == null)
            {
                return null;
            }

            return selector(s, val);
        })
        .Where(r => r != null);
}

var fields = _type.GetProperties()
     .SelectWhereNotNull(prop => prop.GetCustomAttribute<ColumnAttribute>(), Tuple.Create);

【问题讨论】:

  • 为什么要先投影再过滤?如果您担心投影中不必要的分配,您不能在使用Select() 进行投影之前先使用Where.(prop =&gt; prop.GetCustomAttribute&lt;ColumnAttribute&gt;() != null) 进行过滤吗?
  • @ChrisHardie 这也是我的第一个想法。然而,调用prop.GetCustomAttribute 两次可能比分配两次更糟糕。 (直觉,这里没有真实数据)

标签: c# linq linq-to-objects


【解决方案1】:

对于您正在执行的查询类型,您无法真正绕过它。你想要有一个地方把那个属性放在某个地方。无论您将其隐藏在单独的方法中还是对结果对象进行操作,都必须这样做。担心它会适得其反。但是有一些方法可以使它更具可读性。

如果你用查询语法重写了你的查询,你可以隐藏它正在完成的事实

var fields =
    from prop in _type.GetProperties()
    let attr = prop.GetCustomAttribute<ColumnAttribute>()
    where attr != null
    select new
    {
        Prop = prop,
        Attrib = attr,
    };

但是,为此,我可能会将其打包在生成器中。它不需要用 LINQ 编写,如果你尝试这样做,你会严重限制自己。

public static IEnumerable<TResult> SelectWhere<TSource, TValue, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TValue> valueSelector,
        Func<TSource, TValue, bool> predicate,
        Func<TSource, TValue, TResult> resultSelector)
{
    foreach (var item in source)
    {
        var value = valueSelector(item);
        if (predicate(item, value))
            yield return resultSelector(item, value);
    }
}

你的查询变成这样:

var fields = _type.GetProperties()
    .SelectWhere(
        p => p.GetCustomAttribute<ColumnAttribute>(),
        (p, a) => a != null,
        (p, a) => new { Prop = p, Attrib = a }
    )
    .ToList();

【讨论】:

  • 自定义 LINQ 运算符功能强大,但遗憾的是太少见了。这是一个非常优雅的例子。
  • 当你说隐藏它正在完成的事实时,你的意思是在幕后它为每个元素分配,即使 attr != null 失败?
  • 无论如何,这绝对是更优雅的 - 非常感谢先生!
  • @Gene:对。该新变量必须放在某个地方,以便所有变量都可以在范围内。无论是在闭包还是结果对象中,都必须完成。因此,您可以选择显式执行它,或者通过让编译器处理它来隐式执行它。
  • @Jeff:明白了,这是有道理的。我真的应该花更多时间在反编译器/源代码上以充分了解细节。再次感谢您的经验,先生!
【解决方案2】:

您可以使用Selectmany:如果条件为真,则返回一个单项数组,否则返回Array.Empty

var headers = httpHeaders.SelectMany(pair => allowedHeaders.Contains(pair.Key)
    ? new[] { pair }
    : Array.Empty<KeyValuePair<string, string>>());

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-30
    • 1970-01-01
    • 1970-01-01
    • 2012-08-22
    • 2021-07-12
    • 2017-05-19
    相关资源
    最近更新 更多