Lambda property value selector as parameter
Solution 1
private string MyMethod(int testParameter, Func<MyObject, string> selector)
{
return selector(_myObject);
}
When using Func
delegates, the last parameter is the return type and the first N-1 are the argument types. In this case, there is a single MyObject
argument to selector
and it returns a string
.
You can invoke it like:
string name = _myClassInstance.MyMethod(1, x => x.Name);
string result = _myClassInstance.MyMethod(1, x => x.Code);
Since the return type of MyMethod
matches the return type of your selector
delegate, you could make it generic:
private T MyMethod<T>(int testParameter, Func<MyObject, T> selector)
{
MyObject obj = //
return selector(obj);
}
I don't know VB.Net but it looks like it would be:
Public Function MyMethod(testParameter as Integer, selector as Func(Of MyObject, String))
Return selector(_myObject)
End Function
and the generic version would be:
Public Function MyMethod(Of T)(testParameter as Integer, selector Func(Of MyObject, T))
Return selector(_myObject)
End Function
Solution 2
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 ...)
.
Solution 3
in C#
The parameter type you are looking for Func
private string MyMethod(int testParameter, Func<MyClass,string> selector){
return selector(_myObject);
}
in VB you still want Func the syntax is a little different.
Function MyMethod(ByVal testParameter As Integer, ByVal selector as Func(Of MyClass,string) as string
return selector(_myObject)
End Function
XN16
Senior Software Developer in the MIS and Fabrication EPoS Energy Supplier EPoS Apprentice industry.
Updated on September 10, 2020Comments
-
XN16 over 3 years
I have a requirement to modify a method so that it has an extra parameter that will take a lambda expression that will be used on an internal object to return the value of the given property. Forgive my probable incorrect use of terminology as this is my first foray into LINQ expressions!
I have tried searching for an answer, but as I mentioned, my terminology seems to be off and the examples I can find are far too complex or deal with expressions for collection functions such as
.Where()
, which I am familiar with.What I have so far (cut down version):
class MyClass { private MyObject _myObject = new MyObject() { Name = "Test", Code = "T" }; private string MyMethod(int testParameter, ??? selector) { //return _myObject.Name; //return _myObject.Code; return ???; } }
I would like to call it something like this:
string result = _myClassInstance.MyMethod(1, (x => x.Name));
or:
string result = _myClassInstance.MyMethod(1, (x => x.Code));
Obviously the parts which I am missing is the
selector
parameter inMyMethod
, how to apply it to the local variable and how to pass the required property into the method when I am invoking it.Any help would be appreciated, also extra bonus points for a VB.NET solutions as well as unfortunately the final implementation needs to be in our lone VB project!
-
XN16 almost 11 yearsIt's really that easy?! I feel a little stupid about now! What is the significance of the
string
in theFunc
declaration? -
Servy almost 11 years@XN16 It specifies the return type of the delegate.
-
XN16 almost 11 years@Servy I assume this would have to be modified if I tried to access another property of a different type?
-
Lee almost 11 years@XN16 - I've added a brief explanation of the
Func
type arguments. -
Servy almost 11 years@XN16 Well,
MyMethod
returns astring
, so if the delegate didn't match it wouldn't compile. To be able to allow an arbitrary selector you'd need to makeMyMethod
generic and use the generic argument for both the selector and the return type. -
Mr.Mindor almost 11 years@XN16 yes if you are accessing properties of different types you would need multiple overrides for MyMethod. Unless you always want them returned as a string regardless of their type, then you can just change the delegate x => x.NotAString.ToString()
-
XN16 almost 11 yearsThanks for the answer, I have now implemented it and it works perfectly!
-
kofifus almost 3 yearsit's good to mention that
Func<MyObject, T> selector
does not guarantee thatT
is a member ofMyObject