5

这次只是一个快速而简短的。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>>as 参数进行的,如果我没有误解逆变性,应该可以工作,因为.IColoredObjectItem

我得到的是一个转换异常,说的是

无法转换

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 具有接受接口查询并自动选择实现该接口的类型的功能。这本身并不能解决问题,但正如您在下面看到的那样,没有真正的解决方案,因为这里遇到的是预期的行为。

4

4 回答 4

4

Expression<TDelegate> 是一个类,所以它不能对泛型参数有变化。Delegate和之间也有很大的区别Expression<Delegate>。虽然您可以将Itemobject 传递到Func<IColoredObject, bool>并因此可以转换Func<IColoredObject, bool>Func<Item, bool>Expression<Func<Item, bool>>但就像获取Item和返回 bool 的方法的代码一样。您可以分析和更改此代码添加Item特定的方法和属性,显然这对于​​使用IColoredObject.

可以使用 ExpressionVisitor更改into对象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);
    }
}

于 2012-07-12T14:30:05.467 回答
2

C# 仅对接口委托类型具有协变和逆变。例如,这将起作用:

IEnumerable<Func<IColoredObject, bool>> ie1 = XXX;
IEnumerable<Func<Item, bool>> ie2 = ie1;                // works!

请注意,在上面的示例中,ie2 = ie1即使类型不同,分配也会顺利进行。这是因为Func<T, TResult>它的第一个类型参数是逆变的(“in”)T IEnumerable<T>它的T. 这里,Func<,>是一个委托,IEnumerable<>也是一个接口。

不过Expression<TDelegate>是一。C# 不支持类类型的协变/逆变。

因此Expression<Something>永远不会转换为Expression<SomethingAlmostTheSame>.

于 2012-07-12T14:27:27.123 回答
2

这一行:

public Item Get(Expression<Func<Item, bool>> filter) { /* ... */  }

应该是:

public Item Get(Expression<Func<IColoredObject, bool>> filter) { /* ... */  }

在这种情况下,如果要调用Get传递的方法,则必须使用接口Expression<Func<IColoredObject, bool>>

于 2012-07-12T13:19:45.350 回答
1

在尝试计算协变和逆变时,我总是感到困惑。但你的例子对我来说似乎很清楚。您的方法 Get(...) 需要一个 Item 类型的表达式。由于 Item 是从 IColoredObject 派生的,因此不能使用 IColoredObject 类型的表达式,只能使用 Item 类型或从 Item 派生的类型。IColoredObject 是“更抽象的”,因此任何期望类型 Item(或其派生类)的东西都不能使用它。

问问自己:假设 Item 定义了 IColoredObject 没有的属性 X,那么 IColoredObject 类型的表达式将如何定义属性 X?

于 2012-07-12T13:20:06.610 回答