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);
}
}
}
精彩评论