C# interface method ambiguity

2019-01-23 11:57发布

问题:

Consider the following example:

interface IBase1
{
   int Percentage { get; set; }
}

interface IBase2
{
   int Percentage { get; set; }
}

interface IAllYourBase : IBase1, IBase2
{
}

class AllYourBase : IAllYourBase
{
   int percentage;

   int Percentage {
      get { return percentage; }
      set { percentage = value; }
   }
}

void Foo()
{
   IAllYourBase iayb = new AllYourBase();
   int percentage = iayb.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}

In the example above, there is ambiguity between which Percentage property to call. Assuming the IBase1 and IBase2 interfaces may not be changed, how would I go about resolving this ambiguity in the cleanest, most preferred way?

Update

Based on the responses I was getting for using explicit interface implementation, I want to mention that while this does solve the problem it does not solve it in an ideal way for me because I use my AllYourBase object as an IAllYourBase most of the time, never as an IBase1 or IBase2. This is mostly because IAllYourBase also has interface methods (I failed to detail those in my code snippet above because I thought they were irrelevant) that are implemented by AllYourBase and I want to access those too. Casting back and forth all the time will get very tedious and result in messy code.

I did try one solution that involved defining the Percentage property in IAllYourBase and not using explicit interface implementation, which seemed to get rid of the compiler error at least:

class IAllYourBase : IBase1, IBase2
{
   int Percentage { get; set; }
}

Is this a valid solution?

回答1:

Implement explicitly:

public class AllYourBase : IBase1, IBase2
{
    int IBase1.Percentage { get{ return 12; } }
    int IBase2.Percentage { get{ return 34; } }
}

If you do this, you can of course treat your non-ambigous properties just like normal.

IAllYourBase ab = new AllYourBase();
ab.SomeValue = 1234;

However, if you want to access the percentage prop this will not work (Suppose it did, which value would be expected in return?)

int percent = ab.Percentage; // Will not work.

you need to specify which percentage to return. And this is done by casting to the correct interface:

int b1Percent = ((IBase1)ab).Percentage;

As you say, you can redefine the properties in the interface:

interface IAllYourBase : IBase1, IBase2
{
    int B1Percentage{ get; }
    int B2Percentage{ get; }
}

class AllYourBase : IAllYourBase 
{
   public int B1Percentage{ get{ return 12; } }
   public int B2Percentage{ get{ return 34; } }
   IBase1.Percentage { get { return B1Percentage; } }
   IBase2.Percentage { get { return B2Percentage; } }
}

Now you have resolved the ambiguity by distinct names instead.



回答2:

Implement the interfaces explicitly.

An explicit interface member implementation is a method, property, event, or indexer declaration that references a fully qualified interface member name

See this MSDN page for a detailed tutorial.

interface AllYourBase : IBase1, IBase2
{
   int IBase1.Percentage { get; set; }
   int IBase2.Percentage { get; set; }
}


回答3:

Assuming that you want both properties to access the percent member variable you can accomplish this using explicit interface implementation and extending the IAllYouBase interface:

interface IAllYourBase : IBase1, IBase2
{
    new int Percentage { get; set; }
}

class AllYourBase : IAllYourBase
{
   int percentage;

   public int Percentage {
      get { return percentage; }
      set { percentage = value; }
    }

    int IBase1.Percentage {
      get { return percentage; }
      set { percentage = value; }
    }

    int IBase2.Percentage {
      get { return percentage; }
      set { percentage = value; }
   }
}

It's not pretty, but it will give you the behavior I think you're after.



回答4:

Explicitly implement and access it:

interface IAllYourBase : IBase1, IBase2 { } 
public class AllYourBase : IAllYourBase
{     
      int IBase1.Percentage { get{ return 12; } }     
      int IBase2.Percentage { get{ return 34; } } 
} 

IAllYourBase base = new AllYourBase();    
int percentageBase1 = (base as IBase1).Percentage;
int percentageBase2 = (base as IBase2).Percentage;


回答5:

If you don't actually need to return different values for the Percentage property, you can eliminate the compiler error by "deriving" from each interface separately, rather than the "master" interface:

public class AllYourBase : IBase1, IBase2
{
    // No need to explicitly implement if the value can be the same
    public double Percentage { get { return 12d; } }
}

Of course if you do need separate values, then you'll have to explicitly implement the interfaces, and access the property via an appropriately typed reference:

public class AllYourBase : IBase1, IBase2
{
    // No need to explicitly implement if the value can be the same
    public double IBase1.Percentage { get { return 12d; } }
    public double IBase2.Percentage { get { return 34d; } }

}

And the code:

public void SomeMethod()
{
    AllYourBase ayb = new AllYourBase();
    IBase1 b1 = ayb
    double p1 = b1.Percentage;

    IBase2 b2 = ayb;
    double p2 = b2.Percentage;
}

One important consideration when implementing the interfaces explicitly is that AllYourBase itself no longer has a Percentage property. It can only be accessed when the object is accessed via a reference typed as one of the interfaces:

public void SomeMethod()
{
    AllYourBase ayb = new AllYourBase();
    double d = ayb.Percentage;   // This is not legal
}

UPDATE: Looking at your edit, your solution is fine, assuming you don't need different behaviour for IBase1 and IBase2. Your solution hides those properties, so they will only be accessible by casting the object to one of those two interfaces.



回答6:

Though you have already accepted the answer. I would argue that explicit implementation asks too much of redundant work (Implementing the same property twice, in your case) !

How about further segregation of interface (Inteface Segregation Principle) ?

  internal interface IPercentage
    {
        int Percentage { get; set; }
    }

    internal interface IBase1 : IPercentage
    {
    }

    internal interface IBase2 : IPercentage
    {
    }

    internal interface IAllYourBase : IBase1, IBase2
    {
    }

    internal class AllYourBase : IAllYourBase
    {
        private int percentage;

        public int Percentage
        {
            get { return percentage; }
            set { percentage = value; }
        }

        void Foo()
        {
            IAllYourBase iayb = new AllYourBase();
            int percentage = iayb.Percentage; // Compiles now!!!
        }
    }


回答7:

While explicit implementation the guys have mentioned is obviously correct, please consider following scenarions just for the sake of sanity (or do it just for a person reading your code in the future):

If these percentages have different meanings depending on the interface, why not to give them some meaningful names?

interface IBase1 
{ 
    int PercentageBase1 { get; set; } 
}

interface IBase2
{
    int PercentageBase2 { get; set; } 
}

And, on the other hand, if they have the same meaning, why not to have just one Percentage in one of the interfaces and just derive one from another?

interface IBase1 
{ 
    int Percentage { get; set; } 
}

interface IBase2 : IBase1
{
}

However, if the last operation is not possible for whatever the reason is, please consider creating a base interface containing that property for both of them:

interface IBase0
{
    int Percentage { get; set; } 
}

interface IBase1 : IBase0
{ 
}

interface IBase2 : IBase0
{
}


回答8:

void Foo()
{
   IAllYourBase base = new AllYourBase();
   int percentage = base.Percentage; // Fails to compile. Ambiguity between 'Percentage' property.
}

In this example you use the base keyword for an object name and that may be causing the problem!!!



回答9:

You can define the property in the IAllYourBase interface.

Something like this :

interface IAllYourBase : IBase1, IBase2
{
   new int Percentage { get; set; }
}

This will resolve the problems with the ambiguity between the properties. And you can keep the structure of the interfaces.