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));
}
}
}
我得到了这个:
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
版本9.0.30729.1 SP
Microsoft .NET Framework
版本3.5 SP1
修改
奇怪的是,看起来其他人无法重现它。虽然它每次都会在我的机器上崩溃。我发现这段代码:
Console.WriteLine(typeof(EntityBase).GetProperty("Id", BindingFlags.Instance | BindingFlags.Public, null, typeof(object), Type.EmptyTypes, null));
工作正常,但应该是一样的。
答案 0 :(得分:18)
为了回答你的问题,我会让你熟悉“方法表”一词。这是.NET框架中类型的内部表示的一部分,其中每个.NET类型都有自己的方法表。您可以将其想象为包含该类型的所有方法和属性的哈希映射(或字典)。关键是方法/属性签名(方法名称和参数类型,没有返回类型),值是匹配方法/属性的集合,以及一些反射元数据信息,例如哪个类型已声明方法/属性。
当类A
派生自基类 - B
或实现接口C
时,方法表中B
和C
的项目在A
的方法表中直接可用。如果A
的方法表已经包含具有特定签名的项目,则该项目将添加到集合中以用于相同的签名,因此现在A
将具有签名所指向的2个方法/属性。区分这些重复条目的唯一方法是比较描述声明签名类型的元数据。
让我们使用界面IObjectWithId<TId>
,它定义了一个属性TId ID { get; set; }
。 EntityBase
类实现IObjectWithId<TId>
,因此在其方法表中接收TId ID { get; set; }
属性。同时,该类实现IEntityBase
接口,为其提供Object ID { get; set; }
属性。然后EntityBase
类在相同的签名下接收两个属性(因为返回类型不参与签名),而它仍然会暴露2个不同的属性。以下声明将导致编译错误:
public class EntityBase : IEntityBase, IObjectWithId<int>
{
public int ID { get; set; }
}
因为IEntityBase
未实现。同样,后续也将失败:
public class EntityBase : IEntityBase, IObjectWithId<int>
{
public object ID { get; set; }
}
因为此时IObjectWithId<int>
不满意。您可以尝试这样做:
public class EntityBase : IEntityBase, IObjectWithId<int>
{
public object ID { get; set; }
public int ID { get; set; }
}
只是为了获得另外两个具有相同签名的属性的编译错误。
解决这个问题的方法是明确地实现至少一个冲突的签名:
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; } }
}
现在,回到您的代码 - 您使用object
而不是TId
创建了一个罕见但有趣的案例 - 两个ID
属性统一,因为他们相同的签名。所以这堂课:
public class EntityBase : IEntityBase, IObjectWithId<object>
{
public object ID { get; set; }
}
将编译,因为ID
属性满足两个接口。但是,EntityBase
类在其方法表中仍然具有两个 ID
属性(每个接口都有一个属性)。这两个属性由编译器自动分配给EntityBase
类中的相同实现(该过程称为统一)。
以下代码:
typeof(EntityBase).GetProperty(
"ID", BindingFlags.Instance | BindingFlags.Public);
将查看EntityBase
类的方法表,并将看到该签名的两个属性条目,并且不知道要选择哪一个。
这是因为您可能已经实现了类:
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; }
}
}
请参阅 - 这两个属性可以有不同的实现,此时运行时无法知道它们的实现是否统一(现在反射发生在运行时,而不是编译统一的时间)。您收到的AmbiguousMatchException
是.NET框架阻止您执行可能未知/意外行为的代码的方法。
如果没有为每个接口提供不同的实现(如您的情况),那么您所拥有的唯一实现将由该签名的方法表中的两个条目调用,但仍然有两个条目指向同一财产。为了防止框架混淆,您应该在继承层次结构中使用类型足够,这样它的方法表中只有一个条目可供您想要反映的成员使用。在我们的示例中,如果我们在反映Id
属性时使用接口类型,我们将解决我们的情况,因为每个接口在其方法表中只有一个用于请求的条目签名。
然后您可以使用
Console.WriteLine(
typeof(IEntityBase).GetProperty(
"Id", BindingFlags.Instance | BindingFlags.Public));
或
Console.WriteLine(
typeof(BusinessObject<object>).GetProperty(
"Id", BindingFlags.Instance | BindingFlags.Public));
取决于您要检索的实现。在我最新的示例中,每个接口都有不同的实现,您可以通过选择正确的接口来调用任何实现的反射。在您的问题的示例中,您可以使用您想要的任何接口,因为它们都有一个实现。
答案 1 :(得分:0)
当一个接口方法有两种实现方式时,出现此错误。