1

LINQ 式を学習していると、奇妙な動作を発見しました。以下のクラスをご覧ください。あまり便利ではないように見えるかもしれませんが、これは単なる例です。

class IntTestClass
{
    public int Id { get; private set; }

    Func<IntTestClass, bool> check;

    public IntTestClass(int _id)
    {
        Id = _id;

        Expression<Func<IntTestClass, int>> GetId = tc => tc.Id;

        Expression<Func<int, bool>> e1 = i => i.Equals(Id);
        var equals1 = (e1.Body as MethodCallExpression).Method;
        string s1 = equals1.ToString();
        Console.WriteLine(s1);

        var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);
        var exp1 = (Expression<Func<IntTestClass, bool>>)
                             Expression.Lambda(x1, GetId.Parameters.ToArray());
        check = exp1.Compile();
    }

    public bool Check(IntTestClass t)
    {
        var result =  check(t);
        Console.WriteLine(result);
        return result;
    }
}

テストを実行するには、次を使用できます。

var intTestClass= new IntTestClass(0);
intTestClass.Check(new IntTestClass(0)); //true
intTestClass.Check(new IntTestClass(1)); //false

次に、このクラスをジェネリックにしようとしました:

class TestClass<T>
{
    public T Id { get; private set; }

    public TestClass(T _Id)
    {
        Id = _Id;

        Expression<Func<TestClass<T>, T>> GetId = tc => tc.Id;

        Expression<Func<T, bool>> e1 = i => i.Equals(Id);
        var equals1 = (e1.Body as MethodCallExpression).Method;
        string s1 = equals1.ToString();
        Console.WriteLine(s1);

        var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);
        ....
    }
    ....
}

したがって、同様のコードを実行しようとすると:

 var testClass = new TestClass<int>(0);

x1コンストラクターで変数を初期化する行で例外をスローします。

タイプ 'System.Int32' の式は、メソッド 'Boolean Equals(System.Object)' のタイプ 'System.Object' のパラメーターには使用できません

したがって、コンストラクターの(メソッドequals1を含む)はMethodInfoEqualsTestClass<T>Boolean Equals(System.Object)

しかし、IntTestClassコンストラクターequals1ではBoolean Equals(Int32).

どうしてこんなことに?実行時のジェネリック クラスでTは、タイプはSystem.Int32. e1変数の式がクラスEqualsから使用されるのに、からではないのはなぜですか?System.ObjectSystem.Int32

4

1 に答える 1

2

これは、式ツリーを手動で構築するにはトリックの知識が必要なためです。通常のコードを記述するときは、コンパイラがその知識を代わりに行います。

この特定の問題は、次の 2 つの方法で解決できます。

1) int-expression をobject-expression に変換します。

var x1 = Expression.Call(Expression.Constant(Id), equals1, Expression.Convert(GetId.Body, typeof(object)));

これにより、メソッド引数の変換コードが追加Object.Equalsされ、コンパイルされたラムダを呼び出すときに、ボックス化が提供されます。

2) ジェネリックに制約を設定します。

class TestClass<T>
    where T : IEquatable<T>
{
}

次の行を残します。

var x1 = Expression.Call(Expression.Constant(Id), equals1, GetId.Body);

変更なし。

ここでの制約は、IEquatable<T>.Equals(T)の代わりに使用することを伝えるのに役立ちますObject.EqualsIEquatable<T>.Equals(T)それ自体がジェネリックであるため、引数の変換は必要ありません。

于 2013-11-12T05:53:36.883 に答える