5

答え:いいえ、これはバグではありません。違いはReflectedTypeにあります。

したがって、ここでの本当の問題は次のとおりです。同じプロパティに対して、異なるタイプから反映された2 つのオブジェクトを比較しPropertyInfoて、それが返される方法はありますtrueか?

元の質問

このコードは、2 つの異なる方法を使用して、まったく同じプロパティPropertyInfoに対して 2 つのオブジェクトを生成します。これらのプロパティ情報は、どういうわけか異なって比較されます。私はこれを理解しようとして時間を失いました。

私は何を間違っていますか?

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace TestReflectionError
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.BufferWidth = 200;
            Console.WindowWidth = 200;

            Expression<Func<object>> expr = () => ((ClassA)null).ValueA;
            PropertyInfo pi1 = (((expr as LambdaExpression)
                .Body as UnaryExpression)
                .Operand as MemberExpression)
                .Member as PropertyInfo;

            PropertyInfo pi2 = typeof(ClassB).GetProperties()
                .Where(x => x.Name == "ValueA").Single();

            Console.WriteLine("{0}, {1}, {2}, {3}, {4}", pi1, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
            Console.WriteLine("{0}, {1}, {2}, {3}, {4}", pi2, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);

            // these two comparisons FAIL
            Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
            Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));

            // this comparison passes
            Console.WriteLine("pi1.DeclaringType == pi2.DeclaringType: {0}", pi1.DeclaringType == pi2.DeclaringType);
            Console.ReadKey();
        }
    }

    class ClassA
    {
        public int ValueA { get; set; }
    }

    class ClassB : ClassA
    {
    }
}

ここでの出力は次のとおりです。

Int32 ValueA, TestReflectionError.ClassA, Property, 385875969, TestReflectionError.exe
Int32 ValueA, TestReflectionError.ClassA, Property, 385875969, TestReflectionError.exe
pi1 == pi2: False
pi1.Equals(pi2): False
pi1.DeclaringType == pi2.DeclaringType: True


犯人:PropertyInfo.ReflectedType

これら 2 つのオブジェクトの違いを発見しました... ReflectedType. ドキュメントには次のように書かれています。

このメンバーを取得するために使用されたクラス オブジェクトを取得します。

4

4 に答える 4

4

MetadataToken と Module を比較してみませんか。

組み合わせが一意に識別するドキュメントによると。

MemberInfo.MetadataToken
Module と組み合わせて、メタデータ要素を一意に識別する値。

static void Main(string[] args)
{
    Console.BufferWidth = 200;
    Console.WindowWidth = 140;

    PropertyInfo pi1 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi2 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi0 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueB").Single();
    PropertyInfo pi3 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueB").Single();
    PropertyInfo pi4 = typeof(ClassC).GetProperties()
        .Where(x => x.Name == "ValueA").Single();
    PropertyInfo pi5 = typeof(ClassC).GetProperties()
        .Where(x => x.Name == "ValueB").Single();


    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi1, pi1.ReflectedType, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi2, pi2.ReflectedType, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi0, pi0.ReflectedType, pi0.DeclaringType, pi0.MemberType, pi0.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi3, pi3.ReflectedType, pi3.DeclaringType, pi3.MemberType, pi3.MetadataToken, pi3.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi4, pi4.ReflectedType, pi4.DeclaringType, pi4.MemberType, pi4.MetadataToken, pi4.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi5, pi5.ReflectedType, pi5.DeclaringType, pi5.MemberType, pi5.MetadataToken, pi5.Module);

    // these two comparisons FAIL
    Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
    Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));

    // this comparison passes
    Console.WriteLine("pi1.DeclaringType == pi2.DeclaringType: {0}", pi1.DeclaringType == pi2.DeclaringType);


    pi1 = typeof(ClassA).GetProperties()
        .Where(x => x.Name == "ValueB").Single();

    pi2 = typeof(ClassB).GetProperties()
        .Where(x => x.Name == "ValueB").Single();

    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi1, pi1.ReflectedType, pi1.DeclaringType, pi1.MemberType, pi1.MetadataToken, pi1.Module);
    Console.WriteLine("{0}, {1}, {2}, {3}, {4}, {5}", pi2, pi2.ReflectedType, pi2.DeclaringType, pi2.MemberType, pi2.MetadataToken, pi2.Module);

    // these two comparisons FAIL
    Console.WriteLine("pi1 == pi2: {0}", pi1 == pi2);
    Console.WriteLine("pi1.Equals(pi2): {0}", pi1.Equals(pi2));


    Console.ReadKey();
}
class ClassA
{
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}
class ClassB : ClassA
{
    public new int ValueB { get; set; } 
}
class ClassC
{
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}
于 2012-10-07T19:23:37.870 に答える
3

自分が何をしているのかを実際に理解していて、問題を徹底的にテストしていない限り、ライブラリにバグがあると思い込まないでください

PropertyInfoオブジェクトには平等の概念がありません。確かにそれらは同じ結果を表すかもしれませ==が、演算子をオーバーロードしないので、そうすべきだとは思いません。そうではないので、単に参照比較を行って何を推測しているだけなのか、2 つの別々のオブジェクトを参照しているため、!=.

一方、Typeオブジェクトも演算子をオーバーロードしません==が、2 つのインスタンスを演算子で比較すると機能するよう==です。なんで?型インスタンスは実際にはシングルトンとして実装されているため、これは実装の詳細です。したがって、同じ型への 2 つの参照が与えられた場合、実際には同じインスタンスへの参照を比較しているため、それらは期待どおりに比較されます。

フレームワーク メソッドを呼び出すときに取得するすべてのオブジェクトが同じように機能するとは思わないでください。シングルトンを使用するフレームワークはあまりありません。そうする前に、すべての関連ドキュメントとその他のソースを確認してください。


これを再検討すると、.NET 4 の時点で、Equals()メソッドと==演算子が型に実装されていることが通知されました。残念ながら、ドキュメントではそれらの動作についてあまり説明されていませんが、.NET Reflector などのツールを使用すると、いくつかの興味深い情報が明らかになります。

Reflector によると、mscorlib アセンブリのメソッドの実装は次のとおりです。

[__DynamicallyInvokable]
public override bool Equals(object obj)
{
    return base.Equals(obj);
}

[__DynamicallyInvokable]
public static bool operator ==(PropertyInfo left, PropertyInfo right)
{
    return (object.ReferenceEquals(left, right)
        || ((((left != null) && (right != null)) &&
             (!(left is RuntimePropertyInfo) && !(right is RuntimePropertyInfo)))
        && left.Equals(right)));
}

継承チェーン ( RuntimePropertyInfo-> PropertyInfo-> MemberInfo-> Object) を上ったり下ったりするEquals()と、基本実装が最後まで呼び出されるObjectため、実際にはオブジェクト参照の等価比較が行われます。

オペレーターは、どちらのオブジェクトもオブジェクトでは==ないことを明確に確認します。そして、私が知る限り、(ここに示されているユースケースで) リフレクションを使用して取得するすべてのオブジェクトは.PropertyInfoRuntimePropertyInfoPropertyInfoRuntimePropertyInfo

これに基づいて、フレームワークの設計者は、同じプロパティを表している場合でも、 (ランタイム)オブジェクトを比較できないように良心的に作成したようです。プロパティが同じインスタンスPropertyInfoを参照しているかどうかのみを確認できます。PropertyInfoなぜ彼らがこの決定を下したのかはわかりません (私には私の理論があります)。彼らから聞く必要があります。

于 2012-10-07T04:28:39.723 に答える
2

と比較DeclaringTypeNameます。これは、2 つの異なるジェネリック型の「同じ」プロパティが異なることを報告します (たとえば、List<int>.CountList<string>.Count)。と を比較するMetadataTokenModule、これら 2 つのプロパティが同じであることが報告されます。

于 2012-12-21T16:37:11.993 に答える