9
votes

Related Post: C# interface method ambiguity

Code from the same source:

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

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

    private interface IAllYourBase : IBase1, IBase2
    {
    }

    private class AllYourBase : IAllYourBase
    {
        private int _percentage;

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

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

(But does not answer my question -- "WHY the contracts become ambiguous? " )

Given:

Interface is a contract that the implementing class MUST abide with.

If two (or more) interfaces ask for the same contract and a interface passes them 'forward' and then class implements both of them and ACCEPTS that the common contracts should serve as just one contract for the implementing classes (by not providing an explicit implementation). Then,

  1. Why does compiler shows 'ambiguity' warning over the common contracts?

  2. Why the compiler fails to compile on trying to access the ambiguous contract through interface( iayb.Percentage) ?

I would like to know what benefit compiler is serving with this restriction?

Edit: Providing a real world use case where I would like to use contracts across interfaces as one contract.

public interface IIndexPriceTable{
      int TradeId{get;}
      int IndexId{get;}
      double Price{get;}
}

public interface ILegPositionTable{
      int TradeId {get;}
      int LegId {get;}
      int Position {get;}
}

public interface ITradeTable {
      int TradeId{get;}
      int IndexId{get;}
      int LegId{get;}
      //others
}

public interface IJoinedTableRecord : IIndexPriceTable, ILegPositionTable, ITradeTable {
     //Just to put all contracts under one interface and use it as one concrete record, having all information across different tables.
}
  • Why would I like to have 3-TradeId, 2-LegId, 2-IndexId in my joined table record?
6
Other than the obvious, that the compiler can't resolve which type owns Percentage, I can't help. But I would like to add that it's interesting AllYourBase compiles without the explicit implementations.rfmodulator
@rfmodulator: I guess the class isn't interested in the underlying interface members since it implements the common interface, but the common interface inherits from two other interfaces with identical members, and I think that's where the ambiguity lies.BoltClock
@BoltClock I haven't tried it, but I guess two explicit implementations would be an error as well, since they would both have the same name.rfmodulator
In your real world case you can separate the duplicate properties into other, finer grained, interfaces to avoid the problem.Andrew Kennan

6 Answers

19
votes

The solution is to define a property Percentage again with new keyword like this:

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

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

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

private class AllYourBase : IAllYourBase
{
    private int _percentage;

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

private void Foo()
{
    IAllYourBase iayb = new AllYourBase();
    int percentage = iayb.Percentage; //OK
} 

Notice:

C# approach to interfaces is very different to approach plan by Bjarne StrouStrup in C++14. In C# you have to claim, that the class implement interface by modifying class itself while in C++14 it only needs to have methods which correspond to interface definition. Thus the code in C# have more dependencies that code in C++14.

5
votes

Because the interface IAllYourBase does not declare the Percentage property itself.

When you assign an instance of AllYourBase to a variable of IAllYourBase the compiler needs to output a call to either IBase1.Percentage or IBase2.Percentage:

callvirt   instance int32 IBase1::get_Percentage()

or

callvirt   instance int32 IBase2::get_Percentage()

These are different members on different types and just because they have the same signature doesn't mean they are interchangeable.

In your real world situation you might need finer grained interfaces that define the common properties.

3
votes

Because the compiler can't figure out which base interface implementation (IBase1.Percentage or IBase2.Percentage) you're trying to access, because your IAllYourBase interface takes after both of them and both of them each have their own Percentage property.

Put it this way: just because two interfaces have a property with the same name and type doesn't mean that the property is intended to work the same way in both interfaces. Even if a common interface inherits from two interfaces with identical members, the compiler can't just combine two seemingly identical properties into one, because they are members of two different contracts.

1
votes

The line int percentage = iayb.Percentage; has no idea it's dealing with an AllYourBase class, just that whatever it is, it implements the IAllYourBase interface.

So suppose I tried to execute the same statement using my DoubleBase class:

private class DoubleBase : IAllYourBase
{
    int IBase1.Percentage { get; set; } = 10;

    int IBase2.Percentage { get; set; } = 20;
}

To what value does int percentage get set?

0
votes

I see your point. I guess the main benefit from this compiler restriction is that it's better to have one, then to not. I.e. there is more harm then your unintentional interface clush will be ignored, then benefit (if there is any) from this strange case there you want such behaviour.

BTW any real-world scenario there desired behaviour will be so much useful?

0
votes

If an interface inherits two other interfaces that are going to have like-named members, then one of two conditions has to apply:

  1. Both interfaces inherit the same member from some other interface. The other interface will have to be public, but one can document that it exists purely to be inherited, and that consumers are not expected to declare variables or parameters of its type.
  2. The interface which inherits the other interfaces declares as `new` its own member of that same name. This is a good approach when one interface declares a read-only property and another declares a write-only property of the same name; the interface that combines those two interfaces can declare a read-write property whose implementation would be expected to use the read-only property's "getter" and the write-only property's "setter". I'm not sure that it would be good in many other situations, though.

If one does not do one of those things, it's probably best that the compiler not try to guess. Imagine that one has interfaces IListOfDigits, whose Add method appends an integer 0-9 to the list, and IBigNumber, whose Add method adds a number arithmetically. One also has an interface IListOfDigitsRepresentingBigNumber which inherits both. Given an IListOfDigitsRepresentingBigNumber called myThing, holding the digits "5,4,3,2", what should be the effect of myThing.Add(1)? Should it change myThing to hold "5,4,3,2,1" (the effect of IListOfDigits.Add) or "5,4,3,3" (the effect of IBigNumber.Add)? If one does either of the above things, the compiler will have no difficulty figuring out which Add method to use. Otherwise, if both methods can accept an int it won't have a clue.

Incidentally, generics and overloading pose an interesting case. If a IFoo<T,U> has members void Bar(T param) and void Bar(U param), one cannot declare a class as implementing IFoo<int,int>. On the other hand, one can declare a class Foo<T,U> as implementing IFoo<T,U>, and then declare some other class as inheriting from Foo<int,int>, because even if T and U refer to the same type, the compiler would still resolve overloads using T and U.