显式接口实现的限制

6
我有一个非常简单的场景:一个“人”可以是公司的“客户”或“员工”。
使用“Call”方法可以通过电话称呼一个“人”。
根据“人”在通话上下文中扮演的角色,例如新产品的宣布或组织变更的公告,我们应该使用为“客户”角色提供的电话号码或为“员工”角色提供的电话号码。
以下是情况总结:
interface IPerson
{
    void Call();
}

interface ICustomer : IPerson
{
}

interface IEmployee : IPerson
{
}

class Both : ICustomer, IEmployee
{
    void ICustomer.Call()
    {
        // Call to external phone number
    }

    void IEmployee.Call()
    {
        // Call to internal phone number
    }
}

但是这段代码无法编译,会产生以下错误:
error CS0539: 'ICustomer.Call' in explicit interface declaration is not a member of interface
error CS0539: 'IEmployee.Call' in explicit interface declaration is not a member of interface
error CS0535: 'Both' does not implement interface member 'IPerson.Call()'

这种情况在C#中是否有其他实现方式,还是我需要寻找其他设计方案?
如果有的话,你有什么替代方案建议?
感谢您的帮助。

1
如果我写 ((IPerson)new Both()).Call(),你希望发生什么? - SLaks
如果我不知道我正在使用哪个角色,那么我可以通过实现“IPerson.Call”并转发到另外两种方法之一或一个接一个地调用这两个号码,或者任何符合业务规则的方法来应用默认策略。 - Pragmateek
6个回答

9

你的目标没有意义。

无论是 ICustomer 还是 IEmployee 都没有定义 Call() 方法;它们只是从同一个接口继承了该方法。你的 Both 类实现了同一个接口两次。
任何可能的 Call 调用都将调用 IPerson.Call;没有 IL 指令可以特别地调用 ICustomer.CallIEmployee.Call

你可以通过在两个子接口中显式重新定义 Call 来解决这个问题,但我强烈建议你给它们不同的名称。


可以显式地实现具有相同方法名称的两个接口,请参见http://msdn.microsoft.com/en-us/library/4taxa8t2.aspx。 - cspolton
@Spolto:是的,但这是个不好的想法。此外,可能会与基础接口发生冲突。 - SLaks
@SLaks:感谢您的有用答案。从技术角度来看,C#语言实现的限制确实可以证明错误的存在,但从设计角度来看,哪里存在缺陷呢? - Pragmateek
1
@Serious:是的,这很令人困惑和不清楚。两个同名方法应该做相同的事情。此外,一个使用IPerson类型作为IEmployee的函数将会默默地得到错误的行为。 - SLaks
具有相同名称的两个方法应该做相同的事情。这可能是为什么Java没有实现这样的特性的原因之一。 - Pragmateek
@Serious:显式接口实现旨在用作解决方法,例如使用internal类型,实现非公共成员,或具有不同返回类型的两个成员(例如 intlong,或IDbCommandSqlCommand)。 - SLaks

2

我希望你能为我的解决方案提供意见...

当我想让控制器访问某些对于一般使用来说应该隐藏的类属性或方法时,我经常会在组合中使用显式实现...

因此,在这个例子中,为了能够拥有多个IPerson实现,我将使用泛型,以便将IPerson接口从客户端分离到员工端。

interface IPerson<T>
{
    void Call();
}

interface ICustomer : IPerson<ICustomer>
{
}

interface IEmployee : IPerson<IEmployee>
{
}

class Both : ICustomer, IEmployee
{
    void IPerson<ICustomer>.Call()
    {
        // Call to external phone number 
    }

    void IPerson<IEmployee>.Call()
    {
        // Call to internal phone number 
    }
} 

这是一个答案还是一个问题? - Austin Henley

1
我自己也遇到了这个问题。
您可以通过使用组合来解决这个问题:
interface IPerson
{
    void Call();
}

interface ICustomer : IPerson
{
}

interface IEmployee : IPerson
{
}

class Both
{
    public ICustomer Customer { get; }
    public IEmployee Employee { get; }
}

以上假设Both类中的Employee是IEmployee的自定义实现,并基于Both对象构建。
但这取决于您计划如何使用Both类。 如果您想像这样使用Both类:
((IEmployee)both).Call();

那么你可以使用这个:

both.Employee.Call();

1

除了SLaks准确指出的问题之外...

摆脱IPerson,创建一个名为IContactable的接口,并添加一个名为Contact()的方法,然后创建两个具体类型,分别称为CustomerEmployee,它们实现IContactable。每当您需要联系某人时,只需调用所需的IContactable.Contact()方法即可,因为能够联系可能会扩展,而IPerson有点抽象。


1
这不能用来创建一个“Both”类。 - SLaks

0

你不能这样做,因为在这两种情况下,Call方法都来自于IPerson接口。所以你试图定义两次Call方法。 我建议你将ICustomer和IEmployee接口改为类,并在这个类中定义Call方法:

interface IPerson
{
    void Call();
}

class Customer : IPerson
{
    public void Call()
    {
    }
}

class Employee : IPerson
{
    public void Call()
    {
    }
}

1
那不能实现他想要的功能。(这无法用来创建一个“Both”类) - SLaks
但是IPerson接口可以代替Both类,具有相同的效果。 - Nicolas

0

我不知道这是否有帮助,但你可以试一试。

//ran in linqpad c# program mode, you'll need to provide an entry point.....
void Main()
{
    IPerson x;
    x = new Both(new Employee());
    x.call(); //outputs "Emplyee"
    x = new Both(new Customer());
    x.call(); //outputs "Customer"
}

class Customer :  ICustomer
{
    public void call() {"Customer".Dump();}
}
class Employee :  IEmployee
{
    public void call() {"Employee".Dump();}
}
class Both : IPerson
{
     private IPerson Person { get; set; }
     public Both(IPerson person)
     {
         this.Person = person;
     }
     public void call()
     {
        Person.call();
     }
} 
interface IPerson { void call(); }  
interface ICustomer : IPerson { } 
interface IEmployee : IPerson { } 

那不是他想要做的。 - SLaks
它似乎符合所述的要求。 - asawyer
1
他想在调用站点选择接口,而不是对象创建。 - SLaks
一个人可以同时是顾客、员工还是两者兼而有之吗?如果是两者兼而有之,您是否希望对他们进行两次“Call()”操作? - asawyer
1
阅读问题。这个人既可以是两者,也可以在不同的上下文中由不同的代码片段调用。 - SLaks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接