繁体   English   中英

表达差异<Func<T,bool> &gt;

[英]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>是一个类,所以它不能有泛型参数的变化。 DelegateExpression<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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM