![](/img/trans.png)
[英]Convert Expression<Func<T,T,bool>> to Expression<Func<T,bool>>
[英]Variance in Expression<Func<T,bool>>
这次只是一个快速而简短的。 Func<T,TResult>
是逆变的(编辑:类型参数 T 是)。 现在,我不使用Func<T,TResult>
,而是使用Expression<Func<T,TResult>>
,似乎已经走到了死胡同。 更新 - 完整代码示例:
public interface IColoredObject
{
string Color { get; }
}
public class Item : IColoredObject
{
public string Color { get; set; }
public double Price { get; set; }
}
public partial class MainWindow : Window
{
private IList<Item> _items;
public IList<Item> Items
{
get
{
if (_items == null)
{
_items = new List<Item>();
_items.Add(new Item() { Color = "black" });
_items.Add(new Item() { Color = "blue" });
_items.Add(new Item() { Color = "red" });
}
return _items;
}
}
public MainWindow()
{
InitializeComponent();
Expression<Func<IColoredObject, bool>> filter = x => x.Color == "black";
Item i = Get(filter);
}
public Item Get(Expression<Func<Item, bool>> filter)
{
return Items.AsQueryable().Where(filter).FirstOrDefault();
}
}
调用是使用Expression<Func<IColoredObject, bool>>
作为参数进行的,如果我没有误解逆变,应该可以工作,因为IColoredObject
派生的较少Item
。
我得到的是一个转换异常,说的是
无法转换
System.Linq.Expressions.Expression`1[System.Func`2[MyNs.IColoredObject,System.Boolean]]
到
System.Linq.Expressions.Expression`1[System.Func`2[MyNs.Item,System.Boolean]]
有没有办法解决这个问题并让它工作?
编辑:
由于我所说的有些不准确,这里有更多背景信息。 代码示例已更新。 此外,我检查了 MSDN 所说的Func<T, TRes>
:
public Item GetFunc(Func<Item, bool> filter)
{
return Items.AsQueryable().Where(filter).FirstOrDefault();
}
正如 MS 所指出的,这可以与逆变类型参数一起使用,如下所列:
Func<IColoredObject, bool> filterFunc = x => x.Color == "black";
GetFunc(filterFunc);
这再次让我想知道为什么这适用于Func<T, TRes>
而不适用于Expression<Func<T, TRes>>
...
最后...
选中的答案是因为这是我最终做的。 正如我在下面的评论中所说, Get
-Method 利用 NHibernate 来获取数据。 但显然 NHibernate 有一个特性,即接受接口上的查询并自动选择实现接口的类型。 这并不能解决问题本身,但正如您在下面阅读的那样,没有真正的解决方案,因为这里遇到的是预期的行为。
Expression<TDelegate>
是一个类,所以它不能有泛型参数的变化。 Delegate
和Expression<Delegate>
之间也有很大的区别。 虽然您可以将Item
对象传递给Func<IColoredObject, bool>
从而可以将Func<IColoredObject, bool>
转换为Func<Item, bool>
, Expression<Func<Item, bool>>
就像采用Item
的方法的代码并返回布尔值。 您可以分析和更改此代码,添加Item
特定的方法和属性,显然这对于使用IColoredObject
代码是不可能的。
可以使用 ExpressionVisitor 将Expression<Func<IColoredObject, bool>>
中IColoredObject
参数的所有条目更改为Item
对象。 下面是在简单情况下(即没有显式接口实现)执行此类转换的访问者。 也许,您的问题有更简单的解决方案,但如果不了解更多细节,就很难找到它。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
interface IGizmo
{
bool Frobnicate();
}
class Gizmo : IGizmo
{
public bool Frobnicate()
{
Console.WriteLine("Gizmo was frobnicated!");
return true;
}
}
public sealed class DelegateConversionVisitor : ExpressionVisitor
{
IDictionary<ParameterExpression, ParameterExpression> parametersMap;
public static Expression<Func<T2, TResult>> Convert<T1, T2, TResult>(Expression<Func<T1, TResult>> expr)
{
var parametersMap = expr.Parameters
.Where(pe => pe.Type == typeof(T1))
.ToDictionary(pe => pe, pe => Expression.Parameter(typeof(T2)));
var visitor = new DelegateConversionVisitor(parametersMap);
var newBody = visitor.Visit(expr.Body);
var parameters = expr.Parameters.Select(visitor.MapParameter);
return Expression.Lambda<Func<T2, TResult>>(newBody, parameters);
}
public DelegateConversionVisitor(IDictionary<ParameterExpression, ParameterExpression> parametersMap)
{
this.parametersMap = parametersMap;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return base.VisitParameter(this.MapParameter(node));
}
private ParameterExpression MapParameter(ParameterExpression source)
{
var target = source;
this.parametersMap.TryGetValue(source, out target);
return target;
}
}
class Program
{
static void Main()
{
Expression<Func<IGizmo, bool>> expr = g => g.Frobnicate();
var e2 = DelegateConversionVisitor.Convert<IGizmo, Gizmo, bool>(expr);
var gizmo = new Gizmo();
e2.Compile()(gizmo);
}
}
这一行:
public Item Get(Expression<Func<Item, bool>> filter) { /* ... */ }
应该是:
public Item Get(Expression<Func<IColoredObject, bool>> filter) { /* ... */ }
在这种情况下,如果要调用传递Expression<Func<IColoredObject, bool>>
的Get
方法,则必须使用接口。
C# 只有接口和委托类型的协变和逆变。 例如,这将起作用:
IEnumerable<Func<IColoredObject, bool>> ie1 = XXX;
IEnumerable<Func<Item, bool>> ie2 = ie1; // works!
请注意,在上面的示例中,即使类型不同,赋值ie2 = ie1
也能顺利进行。 这是因为Func<T, TResult>
在其第一个类型参数T
是逆变的(“in”),而IEnumerable<T>
在其T
是协变的(“out”)。 这里, Func<,>
是一个委托, IEnumerable<>
是一个接口。
但是Expression<TDelegate>
是一个类。 C# 不支持类类型的协变/逆变。
因此Expression<Something>
永远不会转换为Expression<SomethingAlmostTheSame>
。
在尝试计算协方差和逆变时,我总是感到困惑。 但你的例子对我来说似乎很清楚。 您的方法 Get(...) 需要一个类型为 Item 的表达式。 由于 Item 派生自 IColoredObject,您不能使用 IColoredObject 类型的表达式,只能使用 Item 类型或 Item 派生的类型。 IColoredObject 是“更抽象的”,因此任何需要类型 Item(或其派生类)的东西都不能使用它。
问问自己:假设 Item 定义了一个 IColoredObject 没有的属性 X,那么 IColoredObject 类型的表达式将如何定义属性 X?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.