What I'm trying to achieve
I'm trying to implement a method that compares two delegates for equality.
Ideally I dont want to have to specify the optional parameters while the compiler could figure them out.
So it would be nice if I could call it like shown here:
public class Foo { public double Get(double d = 0.0) { return d; } }
[TestMethod]
public void TestMethod()
{
var foo = new Foo();
bool equal = CompareDelegates(() => foo.Get(), () => foo.Get());
// equal will be false here
}
private bool CompareDelegates(Func<double> d1, Func<double> d2)
{
return d1 == d2;
}
With this implementation CompareDelegates will always return false...
What I already learned while researching into this
- A delgate can be uniquely identified by its combination of
TargetandMethodproperties, so intuitively one would assume that the above example returns true. - The reason it doesn't is that the compiler creates an anonymous class when evaluating the lambdas. This class holds a field with a reference to
fooand one method for each lambda. That leads to the two delegates having differentMethodproperties which causes the equality test to fail.
The actual question
So my question is: Is there any chance to access the "actual" Target and Method of the lambdas?
With "actual" meaning the ones boxed inside the anonymous class. Thus being able to compare Target and Method.
What I already have
With the help of this I already made it work with the CompareDelegates taking the lambdas as Expression<Func<double>>.
Downside: The consumer would have to specify all the optional parameters, which I would like to avoid.
For reference (hoping it helps people with a similar problem that come across this post):
private bool CompareDelegates(Expression<Func<double>> d1, Expression<Func<double>> d2)
{
var actual1 = GetActualTargetAndMethod(d1);
var actual2 = GetActualTargetAndMethod(d2);
return actual1.method == actual2.method && actual1.target == actual2.target;
// return actual1 == actual2; // as of C#7.3
}
private (object target, MethodInfo method) GetActualTargetAndMethod(Expression<Func<double>> expression)
{
// The expression is a lambda expression with a method call body.
var lambda = (LambdaExpression)expression;
var methodCall = (MethodCallExpression)lambda.Body;
// The method is called on a member of some instance.
var member = (MemberExpression)methodCall.Object;
// The member expression contains an instance of the anonymous class that
// defines the member...
var constant = (ConstantExpression)member.Expression;
var anonymousClassInstance = constant.Value;
// ...and the member itself.
var calledClassField = (FieldInfo)member.Member;
// With an instance of the anonymous class and the field, we can get its value.
return (calledClassField.GetValue(anonymousClassInstance), methodCall.Method);
}
And of course you could just do:
private bool CompareDelegates(Func<double, double> d1, Func<double, double> d2)
{
return d1 == d2;
}
// being called like: equal = CompareDelegates(foo.Get, foo.Get);
Which looks nice and works with the optional parameters as well.
Downside: You'll have to write an overload for all optional parameters there may be.
Any suggestions would be appreciated.