I will show you a different approach that is very flexible (see DotNetFiddle at the bottom): You can easily write your own LINQ functions to extend existing functions or write your own functions and benefit from the power of LINQ queries.
In this example, I am improving Linq's Distinct
function in a way so you can specify a field, which is used for grouping.
Usage (Example):
var myQuery=(from x in Customers select x).MyDistinct(d => d.CustomerID);
In this example the query is being grouped by CustomerID
and the first element of each group is returned.
Declaration of MyDistinct
:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f)
{
return query.GroupBy(f).Select(x=>x.First());
}
}
You can see that f
, the 2nd parameter, is declared as Func<T, V>
, so it can be used by the .GroupBy
statement.
Coming back to the code in your question, if you have declared
class MyObject
{
public string Name;
public string Code;
}
private MyObject[] _myObject = {
new MyObject() { Name = "Test1", Code = "T"},
new MyObject() { Name = "Test2", Code = "Q"},
new MyObject() { Name = "Test2", Code = "T"},
new MyObject() { Name = "Test5", Code = "Q"}
};
you could use that with the newly defined function MyDistinct
as follows:
var myQuery = (from x in _myObject select x).MyDistinct(d => d.Code);
which will return
Name Code
Test1 T
Test2 Q
or you can use .MyDistinct(d => d.Name)
in the query, which returns:
Name Code
Test1 T
Test2 Q
Test5 Q
Notice that because MyDistinct
is declared with the generics T
and V
, it recognizes and uses the right object types automatically and returns MyObject
elements.
Advanced usage
Notice that MyDistinct
always takes the first element of each group. What if you need a condition defining which element you need?
Here's how you can do it:
public static class Extensions
{
public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<IGrouping<V,T>,T> h=null)
{
if (h==null) h=(x => x.First());
return query.GroupBy(f).Select(h);
}
}
This modification either allows you to use it exactly as before, i.e. by specifying one parameter like .MyDistinct(d => d.Name)
, but it also allows you to specify a having condition such as x => x.FirstOrDefault(y => y.Name.Contains("1")||y.Name.Contains("2"))
as a second parameter like so:
var myQuery2 = (from x in _myObject select x).MyDistinct(d => d.Name,
x=>x.FirstOrDefault(y=>y.Name.Contains("1")||y.Name.Contains("2"))
);
If you run this query, the result is:
Name Code
Test1 T
Test2 Q
null
because Test5
does not meet the condition (it does not contain 1 or 2), you're getting null in the 3rd row.
Note: If you want to expose just the condition, you can have it even simpler by implementing it as:
public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
Func<T, V> f,
Func<T,bool> h=null
)
{
if (h == null) h = (y => true);
return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}
In this case, the query would just look like:
var myQuery3 = (from x in _myObject select x).MyDistinct2(d => d.Name,
y => y.Name.Contains("1") || y.Name.Contains("2")
);
so you don't need to write x=>x.FirstOrDefault(... condition ...)
.
Try it in DotNetFiddle