这次只是一个快速而短暂的。 Func<T,TResult>
is contravariant( 编辑 :类型参数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具有接受接口查询并自动选择实现接口的类型的功能。这并没有解决问题本身,但正如你可以在下面看到的那样,没有真正的解决方案,因为这里遇到的是预期的行为。
答案 0 :(得分:4)
Expression<TDelegate>
是一个类,因此它不能具有泛型参数的方差。 Delegate
和Expression<Delegate>
之间也存在很大差异。虽然您可以将Item
对象转换为Func<IColoredObject, bool>
,因此可以将Func<IColoredObject, bool>
转换为Func<Item, bool>
,Expression<Func<Item, bool>>
就像采用Item
的方法的代码一样并返回布尔。您可以分析和更改此代码,添加Item
特定方法和属性,显然这对于使用IColoredObject
的代码是不可能的。
可以使用ExpressionVisitor将IColoredObject
中Expression<Func<IColoredObject, bool>>
参数的所有条目更改为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);
}
}
答案 1 :(得分:2)
这一行:
public Item Get(Expression<Func<Item, bool>> filter) { /* ... */ }
应该是:
public Item Get(Expression<Func<IColoredObject, bool>> filter) { /* ... */ }
如果您要调用Get
传递Expression<Func<IColoredObject, bool>>
方法,则必须使用界面。
答案 2 :(得分:2)
C#仅对界面和委托类型具有协方差和逆变。例如,这将起作用:
IEnumerable<Func<IColoredObject, bool>> ie1 = XXX;
IEnumerable<Func<Item, bool>> ie2 = ie1; // works!
请注意,在上面的示例中,即使类型不同,分配ie2 = ie1
仍然顺利。这是因为Func<T, TResult>
在其第一个类型参数T
中是逆变的(“in”),和 IEnumerable<T>
在其{{1}中是协变的(“out”) }}。在这里,T
是委托,Func<,>
是接口。
但IEnumerable<>
是类。 C#不支持类类型的协方差/逆变。
因此Expression<TDelegate>
永远不会转换为Expression<Something>
。
答案 3 :(得分:1)
在尝试制定协方差和逆变时,我总是感到困惑。但你的例子对我来说似乎很清楚。您的方法Get(...)需要一个类型为Item的表达式。由于Item是从IColoredObject派生的,因此不能使用IColoredObject类型的表达式,只能使用类型Item或派生Item的类型。 IColoredObject是“更抽象的”,因此任何期望类型项(或其衍生物)的东西都无法使用它。
问问自己:假设Item已经定义了IColoredObject没有的属性X,那么IColoredObject类型的表达式如何定义属性X?