【问题标题】:Contravariance in Expressions表达式中的逆变
【发布时间】:2012-04-11 07:42:21
【问题描述】:

我正在尝试创建一个通用操作委托

  delegate void ActionPredicate<in T1, in T2>(T1 t1, T2 t2);

public static ActionPredicate<T,string> GetSetterAction<T>(string fieldName) 
    {

        ParameterExpression targetExpr = Expression.Parameter(typeof(T), "Target");
        MemberExpression fieldExpr = Expression.Property(targetExpr, fieldName);
        ParameterExpression valueExpr = Expression.Parameter(typeof(string), "value");

        MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(fieldExpr.Type));

        UnaryExpression valueCast = Expression.Convert(convertExpr, fieldExpr.Type);
        BinaryExpression assignExpr = Expression.Assign(fieldExpr, valueCast);
        var result = Expression.Lambda<ActionPredicate<T, string>>(assignExpr, targetExpr, valueExpr);
        return result.Compile();
    }

这是我的来电者

 ActionPredicate<busBase, string> act = DelegateGenerator.GetSetterAction<busPerson>("FirstName");

这是业务对象

 public abstract class busBase 
{

}
public class busPerson : busBase
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public string GetFullName()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

这是我在编译过程中遇到的错误

Cannot implicitly convert type 'BusinessObjects.ActionPredicate<BusinessObjects.busPerson,string>' to 'BusinessObjects.ActionPredicate<BusinessObjects.busBase,string>'. An explicit conversion exists (are you missing a cast?)    

我的 GetSetterAction 正在返回 ActionPerdicate,因为这里的 T 是 busPerson,我正试图将它存储在 ActionPredicate 中,记住逆变。但它失败了。我不知道如何进一步进行。请帮忙..!

【问题讨论】:

  • 你试过显式转换吗?

标签: c# expression expression-trees covariance contravariance


【解决方案1】:

通用逆变允许您将委托D&lt;TDerived&gt;分配给委托D&lt;TBase&gt;,原因如下(在此处使用Action&lt;T1&gt;):

Action<string> m1 = MyMethod; //some method to call
Action<object> m2 = m1; //compiler error - but pretend it's not.
object obj = new object();

m2(obj);  //runtime error - not type safe

如您所见,如果我们被允许执行此分配,那么我们将破坏类型安全,因为我们可以尝试通过传递 object 的实例来调用委托 m1 而不是string。然而,换一种方式,即将委托引用复制到参数类型比源更派生的类型是可以的。 MSDN has a more complete example of generic co/contra variance

因此,您需要将act 的声明更改为ActionPredicate&lt;busPerson, string&gt; act,或者更有可能考虑编写GetSetterAction 方法以始终返回ActionPredicate&lt;busBase, string&gt;。如果你这样做,你还应该添加类型约束

where T1 : busBase

对于方法,您还需要更改表达式的构建方式,将前两行替换为如下:

ParameterExpression targetExpr = Expression.Parameter(typeof(busBase), "Target");
//generate a strongly-typed downcast to the derived type from busBase and
//use that as the type on which the property is to be written
MemberExpression fieldExpr = Expression.Property(
  Expression.Convert(targetExpr, typeof(T1)), fieldName);

添加通用约束是一个不错的选择,可确保此向下转换对任何T1 始终有效。

稍有不同 - Action&lt;T1, T2&gt; 代表有什么问题?它似乎和你做的一模一样? :)

【讨论】:

  • 正如您所建议的,我尝试将 GeetSetterAction 更改为返回 Action 但我最终遇到此错误“'BusinessObjects.busPerson' 类型的 ParameterExpression 不能用于 ' 类型的委托参数BusinessObjects.busBase'”。我在这一行收到此错误。 "var 结果 = Expression.Lambda>(assignExpr, targetExpr, valueExpr);"
  • @kans - 是的 - 你必须稍微不同地构建表达式树:生成 fieldExpr 时从 busBase 向下转换为 T1。已更新我的答案。
  • 太棒了。非常感谢。效果很好。我正在创建一个基于表达式的规则引擎。您能对此发表一些想法吗..
  • 表达式非常强大且非常有用;和迄今为止做动态代码最简单的方法。尤其是因为您可以在代码中理解它们,方法是与访客一起漫步。不过,一句忠告,不要指望你的同事能理解他们。如果您必须想要学习,而根据我的经验,大多数人都不会!
  • 感谢您的 cmets。如果你有任何关于规则 xml/c# 代码等的信息,你能不能抛出一些代码,如果你的女仆有什么东西,或者有什么东西可以帮助我快速启动以及关于访客的事情。
猜你喜欢
  • 1970-01-01
  • 2014-03-11
  • 2012-08-27
  • 2021-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多