Linq Select Where

32,638

For the type of query you're performing, you can't really get around it. You will want to have a place to put that attribute somewhere. Whether you hide it in a separate method or operate on your result object, it has to be done. It would be counter productive to worry about it. But there are ways you can make it more readable.

If you rewrote your query in the query syntax, you can hide the fact that it is being done

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

For this however, I would probably package it up in a generator. It doesn't need to be written in terms of LINQ, you'd be seriously limiting yourself if you try to.

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);
    }
}

Your query becomes this:

var fields = _type.GetProperties()
    .SelectWhere(
        p => p.GetCustomAttribute<ColumnAttribute>(),
        (p, a) => a != null,
        (p, a) => new { Prop = p, Attrib = a }
    )
    .ToList();
Share:
32,638
Gene
Author by

Gene

Updated on July 19, 2022

Comments

  • Gene
    Gene almost 2 years

    I often find myself writing something like this:

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

    Where I'm bothered is that I'm unnecessarily creating objects in the cases where the where clause fails. Granted the overhead is small, but I'd still prefer to save the allocation, as I would if I were simply looping over it or did the more painful:

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

    Is there a better pattern/extension method I'm missing? Or is it possible that LINQ could make that optimization under the covers?

    Much appreciated!

    Update:

    I guess something like this is what I mean, but I'm expecting something equivalent already exists and I'm just searching poorly:

    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);
    
  • Mister Epic
    Mister Epic almost 10 years
    Custom LINQ operators are powerful and unfortunately far too rare. This is a very elegant example.
  • Gene
    Gene almost 10 years
    When you say hide the fact that it's being done, you mean under the covers it allocates for every element, even if attr != null fails?
  • Gene
    Gene almost 10 years
    Either way, that's definitely more elegant - much appreciate it sir!
  • Jeff Mercado
    Jeff Mercado almost 10 years
    @Gene: Right. That new variable has to be placed somewhere so that all variables can be in scope. Whether it's within a closure or a result object, it has to be done. So you can choose to do it explicitly, or do it implicitly by letting the compiler take care of it.
  • Gene
    Gene almost 10 years
    @Jeff: Gotcha, that makes sense. I really should spend more time in the decompiler/sources to fully understand the details. Appreciate your experience again sir!