12

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!

abatishchev
  • 98,240
  • 88
  • 296
  • 433
metdos
  • 13,411
  • 17
  • 77
  • 120
  • 1
    "Properties/Methods declared on a base class, overridden with 'new' " - That's actually called *hiding*, and they are *certainly* different members. It doesn't make sense to view them as "equal" at all. – Ani Jan 09 '11 at 17:53
  • Well obviously, I just phrased that badly - it would be very hard to write comparison code to return true in that circumstance ;-) - it's still an unhappy path that needs testing, as I can think of structural comparison code that would return true. – metdos Jan 09 '11 at 17:55
  • 1
    Can you clarify the question better? Do you want `string.GetHashCode == int.GetHashCode` (because their base definitions are on the same type - `object`). How about `List.Count` vs. `HashSet.Count` (both implement `ICollection.Count`)? – Ani Jan 09 '11 at 18:01
  • Oh, if you're asking that then I have phrased the question even more badly than I thought. – metdos Jan 09 '11 at 18:03
  • @Rob Ashton: Don't worry about it, maybe it's phrased fine but it's just not clear to *me*. It's much better that I clarify than give you an unhelpful answer. :) In any case, this sort of equality of members is a naturally "vague" concept anyway, unless it has been precisely defined. – Ani Jan 09 '11 at 18:09
  • Clarified, I guess it depends how you define 'equal', I think I've given a rough outline of what I mean now – metdos Jan 09 '11 at 18:10
  • If this is not clear, I have actually started writing the tests for what *I* consider equality now, so I'll paste them up when I'm done - I'll be writing the code anyway - just want feedback so I don't do_it_wrong – metdos Jan 09 '11 at 18:23
  • 1
    To be honest, what you ask is *painful* to solve in the general case, especially for properties. Some of the differences between the CLI and C# type-system really get in the way here. – Ani Jan 09 '11 at 18:53
  • 1
    Yeah, having some real fun trying to get my tests to pass, might just have to go for the 90% that do work and accept that in some really strange cases I'll get bug reports and allow special casing through API overrides – metdos Jan 09 '11 at 18:55
  • 1
    This may be off-topic, but I would really be interested to know the ultimate purpose here, and why you have *this particular* definition of equality between members. This is clearly not a toy reflection test-bed, since you have so many test cases. :) – Ani Jan 09 '11 at 18:58
  • Oh, I have a shoddy little test generation framework (autopoco.codeplex.com), there are three different configuration sources where a MemberInfo can come from, and rules for base types have to cascade into derived types - but only if no other rule has been defined for that derived type, and rules for interfaces have to cascade into their defined types - and so and and so forth :-) – metdos Jan 09 '11 at 19:13

4 Answers4

3

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.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • Still an excellent start - I have a suite of tests and a pile of my (as yet unworking but similar) code to test it with - I shall read your post and see where the differences lie and get back to you :-) – metdos Jan 09 '11 at 19:31
  • Bah, so far so good but there is a difference between Mono and MS.NET so I can't verify fully (not able to run tests on my Windows VM for some reason) - I'll get it working and accept your answer once I've verified the edge cases – metdos Jan 09 '11 at 20:16
  • Okay, so nearly there - FYI It's not good enough to do a GetProperty, we have to match the property using the InterfaceMap - other than that seems kosher so far – metdos Jan 09 '11 at 21:37
  • Right, yours comes closest - I'll do a round-up with my comment – metdos Jan 10 '11 at 09:56
2

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

Patrik Hägne
  • 16,751
  • 5
  • 52
  • 60
  • Making the type available would be quite difficult, but DeclaringType etc would surely be enough? I think MethodInfoManager comes close - but how do you manage new vs virtual? – metdos Jan 10 '11 at 09:38
  • Not sure exactly what you're asking, there's no need to special case new vs virtual in my case since all I'm interested in is wether the to method infos would dispatch the same method. – Patrik Hägne Jan 10 '11 at 09:55
2

So, this was a tough cookie and before I go into boring details I'll say this, I ended up opting to just do a structural comparison, as the only place that will fall down, is when a member hides another member with the 'new' keyword in C# - decided that was a small issue in this system compared to the multitude of other hurts that a proper solution for this problem ends up causing if it goes wrong (and it does go wrong, believe me).

I accepted an answer above, because it came close to solving the problem - the only problem it has is that it is still structural in nature (and I can achieve that by type/name/argument check)

There are some differences though, chiefly that it is necessary to look at the interface map instead of calling GetProperty - this is quite an important detail - here is my revised method that will fall over in certain circumstances.

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

        for(int interfaceIndex = 0; interfaceIndex < interfaces.Length; interfaceIndex++)
        {
            var iface = interfaces[interfaceIndex];
            var interfaceMethods = type.GetInterfaceMap(iface).TargetMethods;

            MethodInfo matchingMethod = null;
            for (int x = 0; x < interfaceMethods.Length; x++)
            {
                if (pi.GetGetMethod().LooseCompare(interfaceMethods[x]) || pi.GetSetMethod().LooseCompare(interfaceMethods[x]))
                {
                    matchingMethod = type.GetInterfaceMap(iface).InterfaceMethods[x];
                    break; 
                }
            }
            if (matchingMethod == null) continue;

            var interfacePi = from i in interfaces
                              from property in i.GetProperties()
                              where property.GetGetMethod().LooseCompare(matchingMethod) || property.GetSetMethod().LooseCompare(matchingMethod)
                              select property;

            return interfacePi.First();
        }

        return pi;
    } 

I ended up giving up on checking whether a member was hiding another member, and went for the following hack:

private PropertyInfo GetRootProperty(PropertyInfo pi)
    {
        if ((pi.GetGetMethod().Attributes & MethodAttributes.Virtual) != MethodAttributes.Virtual) { return 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;
        }
    }

I make an assumption here that a property/method using the 'new' keyword will not be using the virtual keyword also, seeing as 'new' is a bit of an edge case anyway, this is quite unlikely.

This is as far as I got before deciding that it made my tests pass and I was happy with that. (And before I decided to just opt for a structural check...) I hope this is of use to anybody who stumbles this way in the future.

metdos
  • 13,411
  • 17
  • 77
  • 120
1

It seems it would be simpler to check the declaring types of the two MemberInfos you want to compare. In the case of a base/subclass relationship, they should represent the same declaration if the declaring types are the same. In the case of interfaces, they should be the same if the declaring interface type is in the list of the other's interfaces:

Type type1 = methodInfo1.DeclaringType;
Type type2 = methodInfo2.DeclaringType;

bool same = type1 == type2 || 
    type1.IsInterface && type2.GetInterfaces.Contains(type1) ||
    type2.IsInterface && type1.GetInterfaces.Contains(type2);

One thing to be aware of for interfaces is the 'interface mapping' - Type.GetInterfaceMap which means that methods declared in an interface may not have the same name in the implementing class which your current approach does not seem to take account of.

Lee
  • 142,018
  • 20
  • 234
  • 287
  • This will still return false if one of the members is overriding a base member (in my definition they are the same), and of course will return true if the two types are completely different but have the same types - admittedly this is just a typo because you've just dumped this into an answer :-) - Point taken though, this is very simple indeed – metdos Jan 09 '11 at 19:54
  • 1
    Seems to me that the longer solution is the best - find the root of BOTH members, and compare those, relying on comparing memberOne/memberTwo directly at all seems error prone at best – metdos Jan 09 '11 at 20:01