表达式中的方差<func <t,bool>&gt; </func <t,bool>

时间:2012-07-12 13:12:36

标签: c# covariance contravariance variance

这次只是一个快速而短暂的。 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具有接受接口查询并自动选择实现接口的类型的功能。这并没有解决问题本身,但正如你可以在下面看到的那样,没有真正的解决方案,因为这里遇到的是预期的行为。

4 个答案:

答案 0 :(得分:4)

Expression<TDelegate>是一个类,因此它不能具有泛型参数的方差。 DelegateExpression<Delegate>之间也存在很大差异。虽然您可以将Item对象转换为Func<IColoredObject, bool>,因此可以将Func<IColoredObject, bool>转换为Func<Item, bool>Expression<Func<Item, bool>>就像采用Item的方法的代码一样并返回布尔。您可以分析和更改此代码,添加Item特定方法和属性,显然这对于​​使用IColoredObject的代码是不可能的。

可以使用ExpressionVisitor将IColoredObjectExpression<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?