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を使用して行われ、共変性を誤解していない場合は、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

4 に答える 4

4

Expression<TDelegate> はクラスであるため、ジェネリックパラメーターの分散を持つことはできません。との間にも大きな違いがDelegateありExpression<Delegate>ます。Itemオブジェクトをに渡すことができるため、にFunc<IColoredObject, bool>変換できますが、 boolを取得して返すメソッドのコードと同じです。特定のメソッドとプロパティを追加してこのコードを分析および変更できます。明らかに、これはで動作するコードでは不可能です。Func<IColoredObject, bool>Func<Item, bool>Expression<Func<Item, bool>>ItemItemIColoredObject

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

この行:

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 に答える
2

C#には、インターフェイスタイプとデリゲートタイプの共変性と反変性のみがあります。たとえば、これは機能します。

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

上記の例ではie2 = ie1、タイプが同じでなくても割り当てがうまくいくことに注意してください。これFunc<T, TResult>は、最初の型パラメーターが反変( "in")でTあり IEnumerable<T>が共変( "out")であるためTです。ここで、Func<,>はデリゲートでIEnumerable<>あり、インターフェイスです。

しかしExpression<TDelegate>クラスです。C#は、クラスタイプの共変性/反変性をサポートしていません。

したがってExpression<Something>、に変換されることはありませんExpression<SomethingAlmostTheSame>

于 2012-07-12T14:27:27.123 に答える
1

共変性と反変性を解こうとすると、いつも混乱します。しかし、あなたの例は私には明らかなようです。Get(...)メソッドには、Item型の式が必要です。ItemはIColoredObjectから派生しているため、IColoredObjectタイプ、Itemタイプのみ、またはItemから派生したタイプの式を使用することはできません。IColoredObjectは「より抽象的」であるため、Item型(またはその派生物)を期待するものはそれを処理できません。

自問してみてください。しばらくの間、ItemがIColoredObjectにはないプロパティXを定義したと仮定すると、タイプIColoredObjectの式はプロパティXをどのように定義するのでしょうか。

于 2012-07-12T13:20:06.610 に答える