开发者

Concise C# code for gathering several properties with a non-null value into a collection?

A fairly basic problem for a change. Given a class such as this:

public class X
{
    public T A;
    public T B;
    public T C;
    ...

    // (other fields, properties, and methods are not of interest here)
}

I am looking for a concise way to code a method that will return all A, B, C, ... that are not null in an enumerable collection. (Assume that declaring these fields as an array is not an option.)

public IEnumerable<T> GetAllNonNullABCs(this X x)
{
    // ?
}

The obvious implementation of this method would be:

public IEnumerable<T> GetAllNonNullABCs(this X x)
{
    var resultSet = new List<T>();

    if (x.A != null) resultSet.Add(x.A);
    if (x.B != null) resultSet.Add(x.B);
    if (x.C != null) resultSet.Add(x.C);
    ...

    return resultSet;
}

What's bothering me here in particular is that the code looks verbose and repetitive, and that I don't know the initial List capacity in advance.

It's my hope that there is a more clever way, probably something involving the 开发者_如何学C?? operator? Any ideas?


Note about the chosen answer:

I finally went for a mix of both Bryan Watts' and dtb's answers that allows for a clear separation of defining the set of properties A,B,C,... and the filtering of the non-null subset:

(1) Definition of the set of included fields/properties:

IEnumerable<T> AllABCs(this X x)
{
    return new[] { x.A, x.B, x.C, ... };
}

Or alternatively:

IEnumerable<T> AllABCs(this X x)
{
    yield return x.A;
    yield return x.B;
    yield return x.C;
    ...
    yield break;
}

(2) Returning only non-null values:

IEnumerable<T> ThatAreNotNull(this IEnumerable<T> enumerable)
{
    return enumerable.Where(item => item != null);
}

IEnumerable<T> AllNonNullABCs(this X x)
{
    return AllABCs().ThatAreNotNull();
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^
    //     goal reached; it won't get shorter and clearer than this, IMO!
}


public static IEnumerable<T> GetAllNonNullAs(this X x)
{
    return new[] { x.A, x.B, x.C }.Where(t => t != null);
}


You can use the yield keyword to create an enumerable that returns the elements directly instead of populating a list:

public IEnumerable<T> GetAllNonNullAs(this X x)
{
    if (x.A != null) yield return x.A;
    if (x.B != null) yield return x.B;
    if (x.C != null) yield return x.C;
    ...
}

To get rid of the null checks, you could write a method that returns all values, and filter the result:

public IEnumerable<T> GetAllAs(this X x)
{
    yield return x.A;
    yield return x.B;
    yield return x.C;
    ...
}

public IEnumerable<T> GetAllNonNullAs(this X x)
{
    return x.GetAllAs().Where(y => y != null);
}


LINQ to the rescue!

public IEnumerable<T> GetAllNonNullAs(this X x)
{
    return from pi in x.GetType().GetProperties()
           let val = pi.GetValue(x, null)
           where val != null
           select (T) val;
}

You might also want to add a check that the value is of type T, but I'm not sure whether tha's an issue for you.


Perhaps you should have used an array or other collection type when defining your class:

public class X
{
    public T[] ABC = new T[3];
    ...
}

Then it becomes trivial:

return ABC.Where(x => x != null);


You can use an iterator block for this, though it's not much less verbose.

public IEnumerable<T> GetAllNonNullAs(this X x)
{
    if(x.A != null) yield return x.A;
    if(x.B != null) yield return x.B;
    if(x.C != null) yield return x.C;
}


I would suggest a hybrid of some of the other answers. Use yield to return the properties, but then use the .Where LINQ extension method to filter out the nulls.


public IEnumerable<T> GetAllNonNullAs(this X x)
{
    return this.Add(X.A, X.B, X.C);
}

private List<T> Add(params X[] items)
{
   var result = new List<T>();
   foreach(var item in items)
   {
       if(item != null)
       {
           result.Add(item);
       }
   }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜