Can anyone explain to me why the following code throws System.Reflection.AmbiguousMatchException?
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.