0

Can someone explain why this doesn't compile? More specifically, you get an error stating that class A doesn't implement IA, even though B "is a" (implements) IB, which should satisfy IA? Or am I doing something completely wrong?

(I want to keep A/B as specific as possible, while still implementing IA/IB in order to pass it to a method that takes in IA)

public interface IA {
    public string Field { get; set; }
    public IB B { get; set; }
}

public interface IB {
    public string OtherField { get; set; }
}

public class A : IA {
    public string Field { get; set; }
    public B B { get; set; }
}

public class B : IB {
    public string OtherField { get; set; }
}
  • 2
    Imagine this: `class B2 : IB { ... } A a = new A(); IA ia = a; ia.B = new B2(); B b = a.B`. The interface `IA` says that the `B` property can hold *any* type which implements `IB`, but the `A` class restricts that to only instances of `B`. This means that you can use the `IA` interface to set any type which implements `IB`, but the type `A` promises that its `B` field only returns instances of `B` – canton7 Apr 13 '21 at 08:14
  • 3
    Because the interface signature must be followed exactly. The signature requires `IB` not `B` – pinkfloydx33 Apr 13 '21 at 08:15
  • You may do this in Java, but not in .NET. – acelent Apr 13 '21 at 08:37
  • Does this answer your question? [Why does C#/CLR not support method override co/contra-variance?](https://stackoverflow.com/questions/837134/why-does-c-clr-not-support-method-override-co-contra-variance) – Charlieface Apr 13 '21 at 09:24
  • @Charlieface - Partly it probably does, but it's a significant leap from my specific issue, so I prefer canton7's explanation of the issues that arises from this type of pattern. – carl-johan.blomqvist Apr 13 '21 at 09:42
  • The class needs to implement the interface method exactly, even though there is no reason why a covariant derived type shouldn't work on a getter. But yes @canton7 is right, but only because the property also has a setter. In other words, even with covariant returns, which may come in the next version of C#, it won't help because of the setter – Charlieface Apr 13 '21 at 09:55
  • @Charlieface There is a good reason, actually. See the "This is technically a breaking change" section [here](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/covariant-returns#implicit-interface-implementations). That part of the proposal was never implemented with covariant return types, as I understand it because of that issue – canton7 Apr 13 '21 at 09:57
  • 1
    Note that if `IA.B` is getter-only, you can use explicit interface implementations to do this: `class A : IA { public B B { get; set; } IB IA.B => B; }` – canton7 Apr 13 '21 at 09:58
  • @canton7 I guess a better way of wording that would have been "even though there is no reason why a covariant derived type shouldn't have worked on a getter if it was implemented in C# from the start" :-) I didn't know you could do it with explicit impl, that's interesting. VB doesn't allow it AFAIK – Charlieface Apr 13 '21 at 10:59

3 Answers3

2

You're changing the signature that the interface enforced. Even though B implements IB, there might be another class that implements IB, and for IA interface, it is a valid type to set as property B. For instance:

    public class A1
    {
        public string Field { get; set; }
        public IB B { get; set; }

        public A1()
        {
            B = new B(); // completely valid
            B = new Bv2(); // completely valid as well, as Bv2 : IB
        }
    }

    public class A2
    {
        public string Field { get; set; }
        public B B { get; set; }

        public A2()
        {
            B = new B();   // completely valid
            B = new Bv2(); // CTE: Cannot implicitly convert type 'Bv2' to 'B'
        }
    }

So, in your example, you only need to adjust your class A slightly for it to work:

    public class A : IA
    {
        public string Field { get; set; }
        public IB B { get; set; }
    }

Or make IA specifically accept property B as an instance of a class B (1st option is generally better, allows more flexible design):

    public interface IA
    {
        string Field { get; set; }
        B B { get; set; }
    }
Andrii
  • 133
  • 1
  • 10
1

As pinkfloydx33 pointed out in their comment, the interface signature must be followed exactly, so A must have a property of type IB, and not of any type that implements IB. Plus, it's just better that A doesn't depend on a concrete implementation of IB. So your A class must (and should) look like this:

public class A
{
    public string Field {get; set;}

    public IB B {get; set;}
}

Note that

var b = new B();
var a = new A();

a.B = b;

Works and you don't need a cast, as b is implicitly converted to an IB

MindSwipe
  • 7,193
  • 24
  • 47
  • I understand pinkfloydx33 's comment, in particular in light of canton7 's comment. I do not agree with that it's better to not depend on a concrete implementation. Sometimes you want to depend on a specific implementation, to enforce the structure. But then again, as canton7 points out, this leads to issues, so the solution is to go with Klamsi 's answer, which solves the issue. – carl-johan.blomqvist Apr 13 '21 at 09:39
1

What should work is:

    public interface IA<out TIB> where TIB : IB // EDIT: Changed to "out" TIB
    {
        public string Field { get; set; }
        public TIB B { get; } // Edit: removed setter. Not possible with "out" Type
    }

    public interface IB
    {
        public string OtherField { get; set; }
    }


    public class A : IA<B>
    {
        public string Field { get; set; }
        public B B { get; set; }
    }

    public class B : IB
    {
        public string OtherField { get; set; }
    }

Klamsi
  • 846
  • 5
  • 16