Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can anyone explain to me why the following code throws System.Reflection.AmbiguousMatchException?

using System;
using System.Reflection;

namespace A
{
  interface IObjectWithId<TId>
  {
    TId Id { get; }
  }
  interface IEntityBase : IObjectWithId<object>
  {
    new object Id { get; }
  }
  abstract class BusinessObject<TId> : IObjectWithId<TId>
  {
    public abstract TId Id { get; }
  }
  class EntityBase : BusinessObject<object>, IEntityBase
  {
    public override object Id { get { return null; } }
  }

  public static class Program
  {
    public static void Main()
    {
      Console.WriteLine(typeof(EntityBase).GetProperty("Id", BindingFlags.Instance | BindingFlags.Public));
    }
  }
}

I am getting this:

System.Reflection.AmbiguousMatchException was unhandled
  Message="Ambiguous match found."
  Source="mscorlib"
  StackTrace:
       at System.RuntimeType.GetPropertyImpl(String name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
       at System.Type.GetProperty(String name, BindingFlags bindingAttr)
       at A.Program.Main() in C:\Home\work\A\Program.cs:line 26
  InnerException: 

Microsoft Visual Studio 2008
Version 9.0.30729.1 SP
Microsoft .NET Framework
Version 3.5 SP1

EDIT:

Strangely enough, looks like other folks fail to reproduce it. Though it does crash on my machine every time. I found out that this code:

Console.WriteLine(typeof(EntityBase).GetProperty("Id", BindingFlags.Instance | BindingFlags.Public, null, typeof(object), Type.EmptyTypes, null));

Does work OK, although it should be the same.

like image 597
mark Avatar asked Apr 30 '10 15:04

mark


1 Answers

To get your question answered, I'll get you familiar with the term 'Method Table'. This is part of the internal representation of types in the .NET framework, where every .NET type has its own method table. You can imagine it as a hash map (or a dictionary) containing all the methods and properties of the type. The key is the method/property signature (method name and parameter types, without the return type) and the value is a collection of matching methods/properties, alongside some reflection metadata information such as which type has declared the method/property.

When class A derives from a base class - B, or implements an interface C, the items in the method table of B and C become directly available in the method table of A. If the method table of A already contains an item with certain signature, that item is added to the collection for the same signature, so now A will have 2 methods/properties that the signature points to. The only way to distinguish these duplicate entries is by comparing the metadata describing the type on which behalf the signature is declared.

Lets take the interface IObjectWithId<TId>, which defines a property TId ID { get; set; }. The EntityBase class implements IObjectWithId<TId> so receives an TId ID { get; set; } property to its method table. At the same time this class implements the IEntityBase interface, which gives it the Object ID { get; set; } property. The EntityBase class then receives two properties under the same signature (because the return type does not participate in the signature), while it will still expose 2 different properties. The following declaration will result in a compile error:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public int ID { get; set; }
   }

because the IEntityBase is not implemented. Similarly, the follwing will also fail:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
   }

because this time IObjectWithId<int> is not satisfied. You may try to do this:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        public object ID { get; set; }
        public int ID { get; set; }
   }

just to receive another compilation error for having 2 properties with the same signature.

The way to work this around, is to implement at least one of the conflicting signatures explicitly:

   public class EntityBase : IEntityBase, IObjectWithId<int>
   {
        private object objID;
        private int intID;

        object IEntityBase.ID { get { return objID; } set { objID = value; } }     
        int IObjectWithId<int>.ID { get { return intID; } set { intID = value; } }
   }

Now, back to your code - you used object instead of TId which creates a rare but interesting case - the two ID properties unify because of their identical signature. So this class:

   public class EntityBase : IEntityBase, IObjectWithId<object>
   {
        public object ID { get; set; }
   }

will compile, because the ID property satisfies both interfaces. However, the EntityBase class still has two ID properties in its method table (one coming form each interface). The two properties are automatically assigned to the same implementation in the EntityBase class by the compiler (the process is called unification).

The following code:

typeof(EntityBase).GetProperty(
    "ID", BindingFlags.Instance | BindingFlags.Public);

will look into the method table of the EntityBase class and will see two property entries for that signature and will not know which one to pick.

This is because you might have implemented your class like that:

   public class EntityBase : IEntityBase, IObjectWithId<object>
   {
        private object objID1;
        private int objID2;

        object IEntityBase.ID 
        { 
            get { return objID1; } 
            set { objID1 = value; } 
        }

        object IObjectWithId<object>.ID 
        {
            get { return objID2; } 
            set { objID2 = value; } 
        }
   }

See - the two properties can have different implementations, and at that point the runtime cannot know whether their implementations are unified (reflection happens at runtime now, not at compile time when unification was performed). The AmbiguousMatchException you received is the .NET framework's way to prevent you from executing code with possibly unknown/unintended behavior.

When no different implementation is provided for each interface (as in your case), the only implementation you have is called by both entries in the method table for that signature, but still there are two entries pointing to the same property. To prevent the framework from confusion, you should use a type high enough in the inheritance hierarchy, so that it has only one entry in its method table for the member you want to reflect. In our example, if we use the interface types instead when reflecting the Id property, we will solve our case, as each of the interfaces has only one entry in its method table for the requested signature.

You can then use

Console.WriteLine(
    typeof(IEntityBase).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));

or

Console.WriteLine(
    typeof(BusinessObject<object>).GetProperty(
        "Id", BindingFlags.Instance | BindingFlags.Public));

depending which implementation you want to retrieve. In case of my latest example, where each interface has different implementation, you have the ability to invoke reflective any of the implementations, by choosing the correct interface. In the example from your question, you may use whichever interface you want, as both have one implementation.

like image 100
Ivaylo Slavov Avatar answered Sep 19 '22 13:09

Ivaylo Slavov