Explicit interface implementation limitation
Asked Answered
N

6

6

I have a very simple scenario : a "person" can be a "customer" or an "employee" of a company.

A "person" can be called by phone with the "Call" method.

Depending on which role the "person" plays in the context of the call, e.g. the announcement of a new product or the announcement of a change in organization, we should either use the phone number provided for the "customer" role or the one provided for the "employee" role.

Here is a sum-up of the situation :

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
    }
}

But this code doe not compile and produces the errors :

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()'

Does this scenario has any chance to be implementable in C# in a different way or will I have to find another design ?

If so what alternatives do you propose ?

Thanks in advance for your help.

Necrose answered 7/12, 2010 at 17:47 Comment(2)
What would you want to happen if I write ((IPerson)new Both()).Call()?Distiller
If I don't know which role I'm using then I could apply a default policy by implementing "IPerson.Call" and forwarding to one of the two other methods or calling to both numbers one after the other or ... whatever fits with the business rules.Necrose
D
9

Your objective does not make sense.

Neither ICustomer nor IEmployee define a Call() method; they just inherit the method from the same interface. Your Both class implements the same interface twice.
Any possible Call call will always call IPerson.Call; there are no IL instructions that will specifically call ICustomer.Call or IEmployee.Call.

You may be able to solve this by explicitly redefining Call in both child interfaces, but I highly recommend that you just give them different names.

Distiller answered 7/12, 2010 at 17:51 Comment(6)
It is possible to explicitly implement two interfaces with the same method names, see msdn.microsoft.com/en-us/library/4taxa8t2.aspxJagatai
@Spolto: Yes, but it's a bad idea. Also, there might be conflicts with the base interface.Distiller
@Distiller : thanks for your helpful answers. From a technical point of view the limitations of the implementation of the C# language can indeed justify the errors, but from a design point of view where is there a flaw ?Necrose
@Serious: Yes; it's confusing and unclear. Two methods with the same name should do the same thing. Also, a function working with an IEmployee typed as IPerson will silently get incorrect behavior.Distiller
"Two methods with the same name should do the same thing." : this may be a reason why such a feature has not been implemented in Java.Necrose
@Serious: Explicit interface implementations are meant to be used as workarounds, such as using an internal type, implementing a non-public member, or having two members with different return types (such as int and long, or IDbCommand and SqlCommand).Distiller
E
2

I'm interested on your input with my solution...

I used explicit implementation a lot with compositions when I want a controller to access some properties or methods on my class that should be hidden from a regular usage of the class...

So, to be able to have multiple implementation of IPerson, in this example, I would use generic, to be able to split the IPerson interface from a customer to an employee

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 
    }
} 
Ez answered 21/6, 2012 at 18:29 Comment(1)
Is this an answer or a question?Chassepot
U
1

Aside from the issues SLaks accurately pointed out...

Get rid of IPerson and create IContactable with a method of Contact(), then Create two concrete types called Customer and Employee that implement IContactable. Then whenever you need to contact someone you can call your IContactable.Contact() method as desired since being able to make contact could expand, whereas IPerson is a bit abstract.

Ultraconservative answered 7/12, 2010 at 17:59 Comment(1)
This cannot be used to make a Both class.Distiller
C
1

I ran into this myself.

You can solve the problem by using composition:

interface IPerson
{
    void Call();
}

interface ICustomer : IPerson
{
}

interface IEmployee : IPerson
{
}

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

The above assumes that the Employee in the Both class is a custom implementation of IEmployee, and is constructed based on a Both object.

But it depends on how you were planning to use the Both class.
If you wanted to use the Both class like this:

((IEmployee)both).Call();

Then instead you can use this:

both.Employee.Call();
Clemens answered 8/5, 2012 at 21:19 Comment(0)
J
0

You can't do this because the Call method comes from the IPerson interface in the two cases. So you try to define the Call method two times. I suggest you to change your ICustomer and IEmployee interface into class and to define the Call method in this class :

interface IPerson
{
    void Call();
}

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

class Employee : IPerson
{
    public void Call()
    {
    }
}
Jacobine answered 7/12, 2010 at 17:59 Comment(2)
That doesn't do what he wants. (This cannot be used to make a Both class)Distiller
But IPerson interface can be used instead of Both class with the same effect.Jacobine
S
0

I dont know if this helps or not, but you could get it a shot.

//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 { } 
Sandalwood answered 7/12, 2010 at 18:31 Comment(5)
That's not what he's trying to do.Distiller
It seems to match the stated requirements.Sandalwood
He wants to choose the interface at the call site, not the object creation.Distiller
Can the person be a Customer, an Employee, or both at the same time? If they are both, would you want to "Call()" them twice?Sandalwood
Read the question. The person is both and can be called in different contexts by different pieces of code.Distiller

© 2022 - 2024 — McMap. All rights reserved.