9

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?
Community
  • 1
  • 1
Manish Basantani
  • 16,931
  • 22
  • 71
  • 103
  • 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 Mar 12 '12 at 04:06
  • @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 Mar 12 '12 at 04:17
  • @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 Mar 12 '12 at 04:23
  • In your real world case you can separate the duplicate properties into other, finer grained, interfaces to avoid the problem. – Andrew Kennan Mar 12 '12 at 05:36

6 Answers6

22

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.

Tomas Kubes
  • 23,880
  • 18
  • 111
  • 148
  • 2
    Since it isn't shown, for anyone curious about whether the new keyword overrides Percentage. You can also cast the instance to IBase1 and IBase2 and access Percentage. – Luke Aug 03 '18 at 05:59
5

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.

Liam
  • 27,717
  • 28
  • 128
  • 190
Andrew Kennan
  • 13,947
  • 3
  • 24
  • 33
3

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.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
  • But as long as I am using a "common handle" (that is, an interface which implements multiple interfaces with same contracts), the compiler need not be concerned about the different interfaces! – Manish Basantani Mar 12 '12 at 04:03
  • @BoltCock: Because (in my understanding), interface as no 'real' existence it is just to enforce constraint on some real life entity (entity which would be interested in implementing the interface). Contraint that the concrete entity must have the specified attributes and behaviors - Which the entity is abiding with. – Manish Basantani Mar 12 '12 at 05:06
  • 1
    @BoltCock: About 'the compiler can't just combine two seemingly identical properties into one, because they are members of two different contracts' - There is already a way to explicitly request compiler to keep the conflicting contracts separate. That is by doing an explicit implementation. – Manish Basantani Mar 12 '12 at 05:07
  • @Amby: Perhaps I phrased that poorly, but as you mentioned in your question explicit interface implementation isn't relevant. – BoltClock Mar 12 '12 at 05:08
  • @BoltClock "same name and type doesn't mean that the property is intended to work the same way in both interfaces." - Doesn't List implement Count property once even though it comes from ICollection.Count, ICollection.Count, IReadOnlyCollection.Count? – gbackmania Mar 30 '15 at 01:12
1

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?

Philip C
  • 1,819
  • 28
  • 50
0

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?

Petr Abdulin
  • 33,883
  • 9
  • 62
  • 96
0

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.

supercat
  • 77,689
  • 9
  • 166
  • 211