考虑以下类和接口:
public interface A { string Property { get; set; } }
public interface B { string Property { get; set; } }
public interface C : A, B { }
public class MyClass : C
{
public string Property { get; set; }
}
看起来很简单吧?现在考虑以下程序:
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.Property = "Test";
A aTest = myClass;
B bTest = myClass;
C cTest = myClass;
aTest.Property = "aTest";
System.Console.WriteLine(aTest.Property);
bTest.Property = "bTest";
System.Console.WriteLine(bTest.Property);
cTest.Property = "cTest";
System.Console.WriteLine(cTest.Property);
System.Console.ReadKey();
}
看起来没问题,但不会编译。它给了我一个歧义异常:
为什么C#不能解决这个问题?从架构的角度来看,我在疯狂吗?我正在努力理解为什么(我知道它可以通过演员来解决)。
修改
当我介绍接口C
时出现了问题。当我使用MyClass : A, B
时,我根本没有任何问题。
FINAL
刚刚完成了关于这个主题的博客:Interface Ambiguity and Implicit Implementation。
答案 0 :(得分:7)
简而言之,因为它确实含糊不清。
现在更详细的故事。正如您已经看到的那样,有明确的接口实现,因此您可以为A.Property和B.Property提供两种不同的实现,当您只有C时,您无法判断实现是否相同。由于C#“哲学”不是猜测你的意思,而是让你在必要时更清楚地说明,编译器既不选择A.Property也不选择B.Property,但报告错误。
答案 1 :(得分:4)
您需要explicit interface implementation:
public interface A { string Property { get; set; } }
public interface B { string Property { get; set; } }
public interface C : A, B { }
public class MyClass : C
{
string B.Property { get; set; }
string A.Property { get; set; }
}
到了打电话给他们的时候,你将不得不这样做:
MyClass c = new MyClass();
Console.WriteLine("Property A is ": ((A)c).Property);
你为什么不这样做:
public class MyClass : C
{
string B.Property { get; set; }
string A.Property { get; set; }
string B { get { return B.Property; } set { B.Property=value; } }
string A { get { return A.Property; } set { A.Property=value; } }
}
应该注意这是一个糟糕的设计,如果你要公开一个接口C,请确保找到一种更好的方式来公开A / B.Property。
答案 2 :(得分:3)
要弄清楚什么? cTest的类型为“C”,它从两个不同的类继承“Property”;编译器不知道你想要哪一个。这种行为继承自C ++;这是“为什么多重继承是潘多拉盒子的经典例子”。
其他面向对象语言 - Java是一个值得注意的例子 - 根据定义避免这个问题:like-named / like-signatured方法融合在一个共同的后代中。
答案 3 :(得分:3)
当您从单个接口继承时,编译器可以确定在添加新方法时您想要实现哪种方法。
然而,当多个接口具有相同的方法时,基础(和正确)假设是每个接口都需要该方法的DIFFERENT实现,因为这些方法或属性是在不同的接口上定义的。
因此,编译器会告诉您这些不同的接口需要为每个属性实现显式实现。
两个接口共享属性或方法的相同NAME这一事实是任意的 - 没有理由认为它们共享除名称之外的任何东西,因此编译器可以保护您不会错误地将其隐藏在同样的方式。
答案 4 :(得分:2)
这不简单,看起来也不简单。如果两个接口之间发生名称冲突,.NET需要询问您尝试实现哪个接口。问你的方法是通过歧义错误。
如果你没有这种错误,你最终会偶然实现接口。
答案 5 :(得分:2)
您需要explicity implement来自每个界面的两个属性:
public class MyClass : C
{
string A.Property { get; set; }
string B.Property { get; set; }
}
答案 6 :(得分:0)
因为你在做什么是不对的。 A和B发生冲突并且属性名称相同......您需要使用显式接口的实现。
参考here。
答案 7 :(得分:0)
有很多答案,而且所有答案都是正确的,因为明确的界面实现是您问题的答案。
我将尝试用一个有些复杂的例子来澄清这种设计背后的动机:
假设我有一个运行人员的界面(可能有LongDistanceRunner
,Jogger
,MarathonMan
等实现
public interface IRunner
{
void Run();
}
以及可以打开和运行的设备界面(可能的实施BathTub
,Application
,Dishwasher
等)
public interface IRunnable
{
void Run();
}
现在我想为IMusicallJogger
创建和界面(JoggerWithIpod
,BoomBoxJogger
等实现)
public interface IMusicalJogger : IRunner, IRunnable {}
public class BoomBoxJogger : IMusicalJogger
{
// code here
}
BoomBoxJogger bbJogger = new BoomBoxJogger();
现在,当我说bbJogger.Run()
我的对象应该做什么?它应该开始穿过公园,还是应该打开音箱,或两者,或其他完全?如果我同时实现类和调用点,很明显 I 希望我的慢跑者同时做这两件事,但是如果我只控制调用点呢?如果接口的其他实现做了其他什么呢?如果我的慢跑者开始穿过公园,当它被用在一个被认为是装置的环境中时(通过铸造)会怎么样。
这就是显式接口实现发挥作用的地方。
我必须像这样定义我的课程:
public class BoomBoxJogger : IMusicalJogger
{
void IRunner.Run() //implementation of the runner aspect
{
Console.WriteLine("Running through the park");
}
void IRunnable.Run() //implementation of the runnable aspect
{
Console.WriteLine("Blasting out Megadeth on my boombox");
}
public void Run() //a new method defined in the class itself
{
Console.WriteLine("Running while listening to music");
}
}
然后,当我打电话时,我必须指定我想要使用的慢跑者的方面:
BoomBoxJogger bbJogger = new BoomBoxJogger();
((IRunner).bbJogger).Run(); // start running
((IRunnable).bbJogger).Run(); // blast the boombox
//and of course you can now do
bbJogger.Run //running while listening
((IMusicalJogger)jogger).Run(); //compiler error here, as there is no way to resolve this.
希望我帮助澄清这个概念。