Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I compare two PropertyInfos or methods reliably?

Tags:

c#

reflection

Same for methods too:

I am given two instances of PropertyInfo or methods which have been extracted from the class they sit on via GetProperty() or GetMember() etc, (or from a MemberExpression maybe).

I want to determine if they are in fact referring to the same Property or the same Method so

(propertyOne == propertyTwo)

or

(methodOne == methodTwo)

Clearly that isn't going to actually work, you might be looking at the same property, but it might have been extracted from different levels of the class hierarchy (in which case generally, propertyOne != propertyTwo)

Of course, I could look at DeclaringType, and re-request the property, but this starts getting a bit confusing when you start thinking about

  • Properties/Methods declared on interfaces and implemented on classes
  • Properties/Methods declared on a base class (virtually) and overridden on derived classes
  • Properties/Methods declared on a base class, overridden with 'new' (in IL world this is nothing special iirc)

At the end of the day, I just want to be able to do an intelligent equality check between two properties or two methods, I'm 80% sure that the above bullet points don't cover all of the edge cases, and while I could just sit down, write a bunch of tests and start playing about, I'm well aware that my low level knowledge of how these concepts are actually implemented is not excellent, and I'm hoping this is an already answered topic and I just suck at searching.

The best answer would give me a couple of methods that achieve the above, explaining what edge cases have been taken care of and why :-)


Clarification:

Literally, I want to make sure they are the same property, here are some examples

public interface IFoo
{
     string Bar { get; set; }
}

public class Foo : IFoo
{
     string Bar { get; set; }
}

typeof(IFoo).GetProperty("Bar")

and

typeof(Foo).GetProperty("Bar")

Will return two property infos, which are not equal:

public class BaseClass
{
     public string SomeProperty { get; set ; }
}

public class DerivedClass : BaseClass { }


typeof(BaseClass).GetMethod("SomeProperty")

and

typeof(DerivedClass).GetProperty("SomeProperty")

I can't actually remember if these two return equal objects now, but in my world they are equal.

Similarly:

public class BaseClass
{
    public virtual SomeMethod() { }
}

public class DerivedClass
{
    public override SomeMethod() { }
}

typeof(BaseClass).GetMethod("SomeMethod")

and

typeof(DerivedClass).GetProperty("SomeMethod")

Again, these won't match - but I want them to (I know they're not specifically equal, but in my domain they are because they refer to the same original property)

I could do it structurally, but that would be 'wrong'.

Further Notes:

How do you even request the property that's hiding another property? Seems one of my earlier suppositions was invalid, that the default implementation of GetProperty("name") would refer to the current level by default.

BindingFlags.DeclaringType appears just to end up returning null!

like image 657
Rob Ashton Avatar asked Jan 09 '11 17:01

Rob Ashton


2 Answers

Taking a look at the PropertyInfo objects from your IFoo/Foo example, we can reach these conclusions:

  1. There's no direct way to see what class/interface the property was declared on initially.
  2. Therefore, to check if the property was in fact declared on an ancestor class we need to iterate over the ancestors and see if the property exists on them as well.
  3. Same goes for interfaces, we need to call Type.GetInterfaces and work from there. Don't forget that interfaces can implement other interfaces, so this has to be recursive.

So let's have a crack at it. First, to cover inherited properties:

PropertyInfo GetRootProperty(PropertyInfo pi)
{
    var type = pi.DeclaringType;

    while (true) {
        type = type.BaseType;

        if (type == null) {
            return pi;
        }

        var flags = BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance |
                    BindingFlags.Public | BindingFlags.Static;
        var inheritedProperty = type.GetProperty(pi.Name, flags);

        if (inheritedProperty == null) {
            return pi;
        }

        pi = inheritedProperty;
    }
}

Now, to cover properties declared in interfaces (search with DFS):

PropertyInfo GetImplementedProperty(PropertyInfo pi)
{
    var type = pi.DeclaringType;
    var interfaces = type.GetInterfaces();

    if (interfaces.Length == 0) {
        return pi;
    }

    var flags = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public;
    var query = from iface in interfaces
                let implementedProperty = iface.GetProperty(pi.Name, flags)
                where implementedProperty != pi
                select implementedProperty;

    return query.DefaultIfEmpty(pi).First();
}

Tying these together:

PropertyInfo GetSourceProperty(PropertyInfo pi)
{
    var inherited = this.GetRootProperty(pi);
    if (inherited != pi) {
        return inherited;
    }

    var implemented = this.GetImplementedProperty(pi);
    if (implemented != pi) {
        return implemented;
    }

    return pi;
}

This should work. It doesn't take into account indexed properties with the same name but different types and/or numbers of indexing parameters, so that's left as the proverbial excercise for the reader.

Disclaimer: I didn't even compile this (no time to run tests right now). It is intended as a starting point for "the" answer, since there is none so far.

like image 136
Jon Avatar answered Oct 19 '22 20:10

Jon


I'm not exactly sure what you need this for but I guess that your definition of equality in this case is "do the two method infos invoke the same method if invoked"? If so then you really need to specify a type in the comparison too, for example consider this:

public interface IFoo
{
  void AMethod();
}

public interface IBar
{
  void AMethod();
}

public class FooBar : IFoo, IBar
{
  void AMethod();
}

Clearly typeof(IFoo).GetMethod("AMethod") and typeof(IBar).GetMethod("AMethod") are not equal, they are not even related, BUT they do invoke the same method on an instance of FooBar. So what you might like is a comparison method that takes three arguments:

bool WillInvokeSameMethodOnType(MethodInfo method1, MethodInfo method2, Type type)

Check out the class MethodInfoManager in FakeItEasy, it might be what you want: http://code.google.com/p/fakeiteasy/source/browse/Source/FakeItEasy/Core/MethodInfoManager.cs?r=8888fefbc508fb02d5435a3e33774500bec498b3

like image 37
Patrik Hägne Avatar answered Oct 19 '22 19:10

Patrik Hägne