【发布时间】:2021-03-23 01:32:54
【问题描述】:
我正在努力实现的目标
我正在尝试实现一个比较两个委托是否相等的方法。
理想情况下,我不想在编译器可以弄清楚时指定可选参数。
所以如果我可以像这里所示那样称呼它会很好:
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;
}
有了这个实现CompareDelegates 将总是返回 false...
我在研究这个时已经学到的东西
- 可以通过
Target和Method属性的组合来唯一标识一个delgate,因此可以直观地假设上面的示例返回true。 - 不这样做的原因是编译器在评估 lambda 时创建了一个匿名类。此类包含一个引用
foo的字段和每个 lambda 的一个方法。这会导致两个代表具有不同的Method属性,从而导致相等性测试失败。
实际问题
所以我的问题是:是否有机会访问 lambda 的“实际”Target 和 Method?
“实际”表示匿名类中装箱的那些。因此能够比较Target 和Method。
我已经拥有的东西
在this 的帮助下,我已经将它与CompareDelegates 一起使用,将lambdas 作为Expression<Func<double>>。
缺点:消费者必须指定我想避免的所有可选参数。
供参考(希望它可以帮助遇到此帖子的类似问题的人):
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);
}
当然你也可以这样做:
private bool CompareDelegates(Func<double, double> d1, Func<double, double> d2)
{
return d1 == d2;
}
// being called like: equal = CompareDelegates(foo.Get, foo.Get);
这看起来不错,也可以与可选参数一起使用。
缺点:您必须为可能存在的所有可选参数编写重载。
任何建议将不胜感激。
【问题讨论】:
-
作为旁注:
() => foo.Get(), () => foo.Get()是两个不同的匿名方法,它们做同样的事情(调用foo.Get())。它们是不同的,所以它们不能相等。您在第一个非工作示例中尝试做的是比较它们,实际上它们是不同的。在第二个示例中,您查看表达式树的“内部”并比较表达式树的“内容”(即代码() => foo.Get()的高级表示)。在第三个示例中,您放弃了匿名方法并直接使用foo.Get的委托。第二个和第三个例子几乎都可以工作。
标签: c# lambda delegates expression