views:

107

answers:

4

Why doesnt this work?

 public class ClassOptions {}

 public interface Inode {
    ClassOptions Options {get;}
 }            

public class MyClass : Inode {
  public ClassOptions Options { get; set; }
}           

public class ClassDerivedOptions : ClassOptions {
}

public class MyDerivedClass : Inode {
    public ClassDerivedOptions Options { get; set; } << does not implement INode...
}

[ the compiler message tells me why it breaks but i'd like to know the reasoning behind why the compiler doesnt let this through - also if there are any work arounds? - thanks]

+3  A: 

It doesn't work because an interface defines a contract and when you implement this contract method signatures must match exactly. A possible workaround is to use a generic interface:

public class ClassOptions 
{ }

public class ClassDerivedOptions : ClassOptions 
{ }

public interface INode<T> where T : ClassOptions
{
    T Options { get; }
}            

public class MyClass : INode<ClassOptions> 
{
    public ClassOptions Options { get; set; }
}           

public class MyDerivedClass : INode<ClassDerivedOptions>
{
    public ClassDerivedOptions Options { get; set; }
}
Darin Dimitrov
thank you for the work around, it looks more elegant than the others suggested.
GreyCloud
+1  A: 

The standard way to deal with this situation is to implement the interface explicitly:

public class MyDerivedClass : Inode {

    // New, more specific version:
    public ClassDerivedOptions Options { get; set; }

    // Explicit implementation of old, less specific version:
    ClassOptions Inode.Options
    {
        get { return Options; }
    }
}

This is how most old IList implementations worked before generics, for example: specifying a more specific T this[int index] property and then explicitly implementing object IList.this[int index], throwing an exception when the set got called with an object of the wrong type.

In the example code you posted, you don't even need an explicit set, as that is not a member of your Inode interface.

Dan Tao
+7  A: 

It doesn't work because the INode interface explicitly calls for an Options property of type ClassOptions. C# doesn't support return type covariance (which is what you're asking for in this case).

For what it's worth, there's also a language feature request on Microsoft Connect specifically for return type covariance:

Need covariant return types in C# / all .NET languages

If you look at the page, they also mention that the common work-around is to use Explicit Interface Implementation:

public class MyDerivedClass : INode
{
    public ClassDerivedOptions Options { get; set; }
    public ClassOptions INode.Options { get { return Options; } }
}
Justin Niessner
Cheers, mate. This is good info. +1
Sidharth Panwar
GreyCloud
+4  A: 

As Justin notes, the feature you want is called "return type covariance" and it is not supported in C#, or, for that matter, in the CLR type system.

Though it is frequently requested, it is extremely unlikely (*) that this feature will be implemented any time soon. Since it is not supported in the CLR, in order to implement it we would simply have to generate all the helper methods that do the call forwarding for you. Since you can already do that "manually" with a small amount of code, there is little value added by the compiler doing it for you. (And as another question today notes, people sometimes get confused or irritated when the compiler generates a method to do interface forwarding on your behalf.)

Don't get me wrong; I can see how it comes in handy, and I've used this feature in C++. But every time it has come up in a C# program, I've found I can work around its absence pretty easily.

(*) Of course five years ago I would have said exactly the same thing about named and optional parameters, and now they're in C# 4. It is possible for an unlikely feature to be implemented, but the demand has to be pretty darn high.

Eric Lippert