11

I am receiving the following error:

ClassName.PropertyName cannot implement IClassType.PropertyName because it does not have the matching return type of IBasePropertyType

Now, for the code:

public class ClassName : IClassType
{
    public IChildPropertyType PropertyName { get; set; }
}

public interface IClassType
{
    public IBasePropertyType PropertyName { get; set; }
}

public interface IBasePropertyType
{
    // some methods
}

public interface IChildPropertyType : IBasePropertyType
{
    // some methods
}

Is there a way to do what I am attempting? I know that the issue is with co/contravariance, but I can't seem to figure out how to do this.

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Mark Avenius
  • 13,679
  • 6
  • 42
  • 50
  • @Victor: Is there a way to implement an interface, but have a property of child type to the property type defined in the interface? – Mark Avenius Feb 03 '11 at 17:13

2 Answers2

9

In order to implement the given interface, you must have the same return type. However, there are a couple of potential work-arounds to make life easier:

  1. make your interface generic
  2. implement the interface explicitly.

If you make IClassType generic, like so:

public interface IClassType<T> where T : IBasePropertyType
{
    public T PropertyName { get; set; }
}

... then you can implement this interface using various property types:

public class ClassName : IClassType<IChildPropertyType>
{
    public IChildPropertyType PropertyName { get; set; }
}

Another option would be to leave your interface non-generic, but to have a generic base type that explicitly implements the interface:

public class ClassBase<T> : IClassType
    where T : IChildPropertyType
{
    IBasePropertyType IClassType.PropertyName { 
        get {return PropertyName;}
        set {PropertyName = (IChildPropertyType)value;}
    }
    T PropertyName {get;set;}
}

Note that this last option is not quite ideal because you must dynamically cast the property to the given child type: while you can guarantee that every IChildProperty type is an IBasePropertyType, you cannot guarantee that every IBasePropertyType is an IChildPropertyType. However, if you can eliminate the setter from the original interface, or if you can take other steps to guarantee that the setter will never be called with the wrong type in your code, then this could work.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
  • I have attempted the generic route, but haven't yet gotten all of the pieces together (or else I wouldn't be here). Thanks, and I look forward to seeing code samples that make it work. – Mark Avenius Feb 03 '11 at 17:15
  • @Mark Avenius: There they are. Tell me what you think. – StriplingWarrior Feb 03 '11 at 17:28
  • deleted my answer as yours is much better – Robert Levy Feb 03 '11 at 17:34
  • Thank you very much for this; I went with the first option, as it seems cleaner / more straightforward to me. The reason I had issues was that for my example, I removed some of the complexity, and the methods utilized in the BasePropertyType relied on further relationships between the interfaces. Thanks again. – Mark Avenius Feb 03 '11 at 18:59
  • @Mark Avenius: No problem. Depending on how these classes are consumed, using generics in one interface can sometimes create a chain reaction that forces you to genericize multiple classes, which can be burdensome. One final option that I didn't really mention would be a combination of the two I provided. You could have your base class implement both a generic and a non-generic version of the interface. The non-generic version would only have a getter, to allow covariance. The generic version could provide a getter and setter for the specific type. This could be more flexible in some cases. – StriplingWarrior Feb 03 '11 at 19:39
8

You are correct that this has to do with covariance; specifically it has to do with virtual method return type covariance, which is not a kind of covariance that the C# language supports.


UPDATE: This answer is over ten years old. C# may soon implement return type covariance. Please see https://github.com/dotnet/csharplang/issues/49 for details.


Note that even if it did, the system you describe is not type safe. Suppose we have:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; set; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; set; }
}
...
IHaveAnAnimal x = new C();
x.Animal = new Tiger(); // Uh oh. We just put a Tiger into a property of type IGiraffe.

Even if the covariance were legal at all, this kind of covariance would not be legal; you'd have to have no setter for the covariance to be legal.

Suppose then you did have no setter:

interface IAnimal {}
interface IGiraffe : IAnimal {}
interface ITiger: IAnimal {}
class Tiger : ITiger {}
interface IHaveAnAnimal { IAnimal Animal { get; } }
class C : IHaveAnAnimal
{
    public IGiraffe Animal { get; }
}

Unfortunately this is still not legal. But you can do this:

class C : IHaveAnAnimal
{
    IAnimal IHaveAnAnimal.Animal { get { return this.Animal; } }
    public IGiraffe Animal { get; }
}

Now when C is used as a C, Animal returns a giraffe, and when used an an IHaveAnAnimal, it returns an IAnimal.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067