【问题标题】:Casting derived class to parent class within expression将派生类转换为表达式中的父类
【发布时间】:2017-03-22 21:39:06
【问题描述】:

我有一个基类 (BankAccount),然后它有一个派生类型的 UkBankAccount。

我正在获取子类的字段列表作为表达式:

var properties = new Expression<Func<UkBankAccount, object>>[] {
    x => x.Status,
    x => x.Name,
    x => x.SortCode,
    x => x.AccountNumber
};

然后想将其转换为类型BankAccount,这样我就可以通用地使用它。我试过了:

updatedProperties = properties.ToList()
   .Cast<Expression<Func<BankAccount, object>>>()
   .ToArray();

但这会引发无效的强制转换异常。我应该能够在表达式中进行转换吗?

谢谢

【问题讨论】:

  • 为什么你认为这应该有效?您也不能将List&lt;UkBankAccount&gt; 分配给List&lt;BankAccount&gt;
  • 如果这些字段仅在子类型上,尝试在超类型对象上调用这些属性有什么意义?

标签: c#


【解决方案1】:

UkBankAccountBankAccount

但是Func&lt;UkBankAccount, object&gt; 不能分配给Func&lt;BankAccount, object&gt;,实际上是相反的:Func&lt;BankAccount, object&gt; 可以分配给Func&lt;UkBankAccount, object&gt;。想一想:当你有一个能够接受各种账户的功能时,它肯定可以接受英国账户。但反过来就不对了。

但这并不意味着Expression&lt;Func&lt;BankAccount, object&gt;&gt; 应该可以分配给Expression&lt;Func&lt;UkBankAccount, object&gt;&gt;。它不是。泛型逆变不适用于类。

即便如此,你似乎想要的恰恰相反。

我应该能够在表达式中进行转换吗?

不,类型系统不允许将 Expression&lt;Func&lt;UkBankAccount, object&gt;&gt; 强制转换、转换或以其他方式分配给 Expression&lt;Func&lt;BankAccount, object&gt;&gt;

你需要做的是产生相当于

的东西
var properties = new Expression<Func<BankAccount, object>>[] {
    x => ((UkBankAccount)x).Status,
    x => ((UkBankAccount)x).Name,
    x => ((UkBankAccount)x).SortCode,
    x => ((UkBankAccount)x).AccountNumber
};

如果 BankAccount 上也存在该属性,则可以跳过转换。

请注意,这些转换可能会失败,这就是为什么 Func&lt;UkBankAccount, object&gt; 不能分配给 Func&lt;BankAccount, object&gt;

【讨论】:

  • 所以基本上,你是说,OP 处于死胡同?除了我们大多数人都知道的规范事实之外,我不确定这个答案如何有助于实现所需的解决方案。公平地说,也许 OP 正在 朝着错误的方向前进。只是想对 OP 的方法进行澄清说明。
  • @code4life 我毫不怀疑您知道规范事实,但显然 OP 不是。
  • @xanatos 这正是我所说的,不是吗?
  • @KrisVandermotten 跳过了一句:-)
  • @xanatos 确实!我应该多睡 :-) 会解决的。
【解决方案2】:

您必须重写表达式...使用我在另一个问题中发布的SimpleParameterReplacer

var updatedProperties = Array.ConvertAll(properties, exp =>
{
    var parameter = Expression.Parameter(typeof(BankAccount), exp.Parameters[0].Name);
    var body = new SimpleParameterReplacer(exp.Parameters[0], parameter).Visit(exp.Body);
    var exp2 =  Expression.Lambda<Func<BankAccount, object>>(body, parameter);
    return exp2;
});

你用SimpleParameterReplacer()重写表达式的“主体”(用BankAccount类型的参数替换使用UkBankAccount类型参数的实例)然后你重建lambda表达式(改变委托类型)。

重要提示:如果x =&gt; x.SomePropertySomeProperty基类 的一部分而不是派生类...在您的示例中,x.Statusx.Namex.SortCodex.AccountNumber 必须BankAccount 的一部分(或它的基类之一(如果存在)。

【讨论】:

  • 如果 BankAccount 上不存在这些属性之一怎么办?
  • @KrisVandermotten Kabooom :-) 我读到这个问题是假设基类中存在这些字段。如果不是,那么 OP 应该说明当这个前提条件不满足时应该发生什么。
  • 虽然您走在正确的轨道上,但是当属性仅存在于派生类型中时,您还应该重写表达式的主体以包含强制转换。看我的回答。
猜你喜欢
  • 1970-01-01
  • 2022-10-14
  • 1970-01-01
  • 2017-07-18
  • 1970-01-01
  • 2017-01-08
  • 2013-11-17
相关资源
最近更新 更多