Covariant return types revisited


As I’ve been using the C# language lately I’ve been noticing a few things missing from C++ that I find very aggravating.   The first is const safety (but I’m dealing with it), the second is covariant return types.  For those who haven’t used them before here’s a little example:

 

interface ITypeVariableBinder {

       //… stuff

}

 

//Represents any type of symbol in C#

interface ISymbol {

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.

       ISymbol Instantiate(ITypeVariableBinder binder);

}

 

//Represents any of the C# type classes.

//i.e. enum, delegate, class or struct

interface IType : ISymbol {

       //Instantiates this type into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList<T> into IList<int>

       IType Instantiate(ITypeVariableBinder binder);

}

 

//Represents a method in C#

interface IMethod : ISymbol {

       //Instantiates this method into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList.Foo<T>(T t) into

       //IList.Foo<string>(string t)

       IMethod Instantiate(ITypeVariableBinder binder);

}

 

As you can see this represent a simple interface hierarchy to represent C# elements in a compiler.  Now with the addition of generics into C# 2.0 we want to have the ability to take any kind of symbol and then instantiate them based on the type variables provided so far.  Of course if you have a type and instantiate it you will get a type back, likewise if you have a method and you instantiate it you will get a method back.  So I wrote the interfaces as you see above.  Now, the return types of the “Instantiate” method in the derived interfaces are not the same as in the base interface.  However, that isn’t a problem because they’re still obeying the contract laid down by the base interface.  ISymbol states that “Instantiate” must return an ISymbol and both IMethod and IType are abiding by that contract since their return types *are* ISymbol’s.

 

What this means is as a code producer I can write code like so:

 

class Symbol : ISymbol {

    public virtual ISymbol Instantiate(ITypeVariableBinder binder) {

        //…

    }

}

 

class Type : Symbol, IType {

    public override IType Instantiate(ITypeVariableBinder binder) {

        //…stuff

    }

}

And a consumer of my libraries can write code nicely like:

 

    void F() {

        IMethod method = //…

        IType type = //…

 

        IMethod instantiatedMethod = method.Instantiate(/*…*/);

        IType instantiatedType = type.Instantiate(/*…*/);

    }

 

However, there’s a problem:

 

       “Warning ‘IType.Instantiate(ITypeVariableBinder)’ hides inherited member ‘ISymbol.Instantiate(ITypeVariableBinder)’. Use the new keyword if hiding was intended.”

 

Seems like I’m not allowed to do this in C#.  Bummer.  I have two choices.  I can make IType.Instantiate and IMethod.Instantiate return ISymbol instead.  However if I do that then the consumer now needs to write:

 

    void F() {

        IMethod method = //…

        IType type = //…

 

        IMethod instantiatedMethod = (IMethod)method.Instantiate(/*…*/);

        IType instantiatedType = (IType)type.Instantiate(/*…*/);

    }

 

Which involves ugly casts, the potential to make a mistake and cause runtime exceptions to happen, runtime cost, *and* it assume that when I wrote my implementations I actually made sure that it returned the right things (which is difficult since the compiler won’t be checking for me).  

 

The alternative is to use hide-by-name in the following manner:

 

//Represents any of the C# type classes.

//i.e. enum, delegate, class or struct

interface IType : ISymbol

{

    //Instantiates this symbol into a generic version with it’s type variables

    //bound.  For example, this could instantiate IList<T> into IList<int>

    new IType Instantiate(ITypeVariableBinder binder);

}

 

//Represent a method in C#

interface IMethod : ISymbol

{

    //Instantiates this symbol into a generic version with it’s type variables

    //bound.  For example, this could instantiate IList.Foo<T>(T t) into

    //IList.Foo<string>(string t)

    new IMethod Instantiate(ITypeVariableBinder binder);

}

 

(Note the use of the “new” keyword).

 

Now the consumer can write code in the original way I specified. Yaay!  However, this new system now incurs a cost on the producer of the API (i.e. me).  Specifically I’ll have to write something like this:

 

class Symbol : ISymbol {

    protected virtual ISymbol InstantiateWorker(ITypeVariableBinder binder) {

        //original implementation

    }

 

    public ISymbol Instantiate(ITypeVariableBinder binder) {

        return InstantiateWorker(binder);

    }

}

 

class Type : Symbol, IType {

    protected override ISymbol InstantiateWorker(ITypeVariableBinder binder) {

        //derived implementation which i manually make sure always returns an IType

    }

 

    public new IType Instantiate(ITypeVariableBinder binder) {

        return (IType)InstantiateWorker(binder);

    }

}

 

Ewwwww.   Look at how much extra cruft there is.  I need to make sure that ISymbol.Instantiate calls a virtual method so that it will always work when I subclass.  I can override that implementation in the subclass but I must manually ensure that it always returns an IType.  *And* on top of all that, when I implement IType.Instantiate I have to make sure to call the virtual method and then cast the result of that back again.  There is plenty of chance for me to screw this up as the library writer.  Worse yet is that the deeper your object hierarchy goes the more complicated and confusing it is to manage and maintain this structure.

 

Seems like a very unfortunate position.  On one hand we can simply create an object hierarchy but then put the onus on the consumer (which might be ourselves) to do the right thing.  On the other hand we can make the API make sense but spend a lot of error prone effort making sure it’s correct.  So why don’t we have covariant return types in C#?  Well, as it turns out in order for the runtime to do override resolution it needs to match the signature of the method in both the base and derived classes to determine which method is being overridden.  However, they require that the return types be the same in order to consider the signature a match  (see the Common Language Infrastructure Annotated Standard: 9.3 Introducing and Overriding Virtual Methods).

 

So I got to thinking: would it be possible to implement this in the C# language without needing support in the runtime.  While lying in bed last night listening to the rain I came up with the following proposal for how we might do it.

 

First, allow covariant return types at the source level.  This means allowing users to write code like:

 

//Represents any type of symbol in C#

interface ISymbol {

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.

       ISymbol Instantiate(ITypeVariableBinder binder);

}

 

//Represents any of the C# type classes.

//i.e. enum, delegate, class or struct

interface IType : ISymbol {

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList<T> into IList<int>

       IType Instantiate(ITypeVariableBinder binder);

}

 

//Represent a method in C#

interface IMethod : ISymbol {

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList.Foo<T>(T t) into

       //IList.Foo<string>(string t)

       IMethod Instantiate(ITypeVariableBinder binder);

}

 

However, when you wrote that we would actually transform it (behind the scenes, and much in the same way that we transform iterators, anonymous delegates, and such) into:

 

namespace System.Runtime.CompilerServices {

       public class CovariantAttribute : Attribute {

              public CovariantAttribute(Type derivedType) { /*…*/ }

       }

}

 

//Represents any type of symbol in C#

interface ISymbol

{

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.

       ISymbol Instantiate(ITypeVariableBinder binder);

}

 

//Represents any of the C# type classes.

//i.e. enum, delegate, class or struct

interface IType : ISymbol

{

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList<T> into IList<int>

       [return: Covariant(typeof(IType))]

       ISymbol Instantiate(ITypeVariableBinder binder);

}

 

//Represent a method in C#

interface IMethod : ISymbol

{

       //Instantiates this symbol into a generic version with it’s type variables

       //bound.  For example, this could instantiate IList.Foo<T>(T t) into

       //IList.Foo<string>(string t)

       [return: Covariant(typeof(IMethod))]

       ISymbol Instantiate(ITypeVariableBinder binder);

}

 

class Symbol : ISymbol {

    public virtual ISymbol Instantiate(ITypeVariableBinder binder) {

        //…

    }

}

 

class Type : Symbol, IType {

    [return: Covariant(typeof(IType))]

    public override ISymbol Instantiate(ITypeVariableBinder binder) {

        //…stuff

    }

}

 

 

In this form the code can be compiled into completely legal metadata since the overridden methods obey the contract that the runtime has that the return types must match.  Then whenever we see any of these methods (from source *or* from metadata) that has this attribute on the return type we can say “ah, it’s safe to treat the return type as an instance of the derived type stored in the Covariant attribute”  So we can then likewise transform:

 

    void F() {

        IMethod method = //…

        IType type = //…

 

        IMethod instantiatedMethod = method.Instantiate(/*…*/);

        IType instantiatedType = type.Instantiate(/*…*/);

    }

 

Into

 

    void F() {

        IMethod method = //…

        IType type = //…

 

        IMethod instantiatedMethod = ((IMethod)method.Instantiate(/*…*/));

        IType instantiatedType = ((IType)type.Instantiate(/*…*/));

    }

 

i.e. wherever we see the return type used that is marked as “Covariant(typeof(T))” we will replace the expression “e” with “((T)e)”.

 

So now we have a situation with a few benefits and a few drawbacks.

Benefits:

a)      Production of covariant code is extremely simple.  The compiler will ensure that if you have a method that says it returns “IType” then it will actually return an instance of “IType” from all code paths.  You don’t need method explosion and a weird hodgepodge of virtuals/non-virtuals and casts scattered everywhere in your code

b)      Consumption of covariant code is extremely simple.  No casting required.

c)      To all other .net languages everything seems hunky-dory.  The interfaces and methods we expose will have the exact same signature that we used to have.  They can then choose to support/ignore the “Covariant” attribute.

d)      If the runtime ever supports covariant return types then you don’t need to change any of your code.  As long as you’re targeting the appropriate runtime we’ll simply remove the attributes and casts from the compiled code and you’ll get the same clear code with no perf impact.

 

Neutral:

a)      You are now incurring a runtime hit of a cast without realizing it.  But you would have had to do the cast anyway so it won’t really affect perf.  It’s just unfortunate since the cast is completely unnecessary

 

Drawbacks:

a)      It’s not actually typesafe.  The runtime will make no guarantees that a method marked with “[return: Covariant(typeof(IType))]” will actually always return an instance of an “IType”.  A malicious compiler could mark a method with that attribute and then return something else causing us to fail at runtime.  This is extremely unfortunate especially as you will get an “InvalidCastException” at runtime in code that seems to have no casts in it!  Ack!  We do have precedent for attributes that can cause runtime exceptions and which depend on the author of the attribute making sure their code to write things correctly (like Fixed Size Buffers), but obviously we would prefer to have the compiler/runtime enforce this so you wouldn’t actually have any chance of this failing at runtime.  We could do some work with FxCop here to ensure that the dlls you run against don’t commit this sin.

Alternatively, we could come up with a scheme where you wrote covariant code and we would generate the following code:

 

class Symbol : ISymbol {

    protected virtual ISymbol __InstantiateImpl(ITypeVariableBinder binder) {

        //original implementation is copied into here

    }

 

    public ISymbol Instantiate(ITypeVariableBinder binder) {

        return InstantiateImpl(binder);

    }

}

 

class Type : Symbol, IType {

    protected override ISymbol __InstantiateImpl(ITypeVariableBinder binder) {

        //derived implementation is copied into here and verified by the compiler to always return an IType

    }

 

    public new IType Instantiate(ITypeVariableBinder binder) {

        return (IType)__InstantiateImpl(binder);

    }

}

 

but i’m not convinced it would work in all cases (especially how you’d understand waht was going in metadata).  Maybe a hybrid approach (use attributes and rewriting) would work the best.

 

I’m feeling the pain heavily with the lack of covariant return types.  Is anyone else out there feeling this?  Would covariant return types be something you’d want to see in the language in the future?  If so, how important would it be that it was deeply supported at the runtime level or just at the language level?  

 

When i was asking what you wanted in the next version of C#, the #6 most resquested feature was “Covariant return types.”  However, what i’d like to hear here is how the lack of this feature has affected you and how much better it would make your life as a developer.  These stories will help everyone here get a feeling for where to go with this.

 

Thanks!


Comments (34)

  1. AT says:

    Benefit c) is not true.

    Let’s try to consume this code in non-covariant C# (1.0/1.1/2.0 😉

    class VbType : IType { }

    class VBMethod : IMethod {

    public override IType Instantiate(ITypeVariableBinder binder) {

    return new VbType(); // One that does not implement IMethod – as covariant Instantiate expect so.

    }

    }

    And now try to pass VBMethod instance to covariant-aware library that will expect IMethod as return type of Instantiate.

    So – non-covariant languages can not ignore this attribute. They need to support it fully or offset this support to developer/runtime :-((

  2. AT: your example needs to be:

    class VBMethod : IMethod {

    ….[return: Covariant(typeof(IMethod))]

    ….public override ISymbol Instantiate(ITypeVariableBinder binder) {

    ……..return new VbType(); // One that does not implement IMethod – as covariant Instantiate expect so.

    ….}

    }

    And yes, this would break the covariant aware code. As i stated in the drawbacks section:

    "It’s not actually typesafe. The runtime will make no guarantees that a method marked with “[return: Covariant(typeof(IType))]” will actually always return an instance of an “IType”. A malicious compiler could mark a method with that attribute and then return something else causing us to fail at runtime. This is extremely unfortunate especially as you will get an “InvalidCastException” at runtime in code that seems to have no casts in it! Ack! "

  3. Jacob Morgan says:

    Classes can provide explicit interface implementation, providing a sort of covariance. This is legal:

    public interface Boo {

    Type1 Farm();

    }

    public class Yah : Boo {

    Type1 Boo.Farm() {

    return Farm();

    }

    public Type1DerivedClass Farm() {

    return new Type1DerivedClass();

    }

    }

    A call to Yah.Farm through the interface will execute the first method. A call to Yah.Farm directly through the class will execute the second method, giving the covariant return type. Not ideal, but it gets the job done!

  4. Jacob: Run with this. Show how you would write a class that derives from Yah that is covariant. on "Farm"

  5. Matthew W. Jackson says:

    Yes! I have on several occasions needed (well, wanted) covariant return types (and to a much lesser, extent) contravarient parameter types.

    For interfaces, the problem is not that bad since explicit-implementation works well, but it’s still a serious pain to write such code. When it comes to concreate base classes, hiding methods is the only alternative, and I really hate to hide methods.

    Maybe I should point you to the framework itself for examples. System.Data would have really benefited from covarient return types (for example, IDbCommand.CreateParameter).

    Although it’s tough to see right now, but I wager that generics are going to make everyone find more uses for covarience. Time will tell.

  6. Matthew W. Jackson says:

    Oh, and while I know that ICloneable is the heart of much debate right now, it would be much nicer if classes could implement it using covarient return types.

    class Foo : ICloneable {

    public virtual Foo Clone() {

    return (Foo)this.MemberwiseClone();

    }

    }

    class Bar : Foo {

    public override Bar Clone() {

    return (Bar)base.Clone();

    }

    }

    Right now, Clone would have to be implemented explicitly, and every derived class would have to hide the base class’s implementation, even though it would result in the same call.

  7. AT says:

    I still insist on opinion in my original message.

    IMethod interface will requere covariant contact even for non-covariant languages ! But this contract will be defined in documentation – not at declaration/compiler level.

    Okey. I would like to simply record my opinion in history:

    I preffer runtime added a complete support for covariant types.

    There are already two different covariant hacks by Eiffel and Component Pascal.NET

    I like the way Microsoft did with generics – they are supported by runtime (unlike Java 5.0). I would like Microsoft keep moving in this way – put as much as possible common functionality to runtime – do not use hacks or workarounds.

    There are a lot of others covariance in addition to return types – arguments and generics. All of thouse will be very-very ugly (or almost impossible) with current hack.

    I do not see any reasons to keep .NET runtime binary-compatible will all prior releases. This is possible to execute several runtimes side-by-side – so I’m willing some real benefits provided in return for disk space consumed.

    But in the same time – there are must be correct/reasonable types for non-covariant aware languages (even if covariance contacts will be inforced only by documentation). Returning/passing System.Object instances is not good.

    I’ve discussed this idea – it’s can be possible to add covariance contract support to all non-covariant languages in .NET using single tool – FxCop.

    Using static analysys at IL level – FxCop must list all methods / interfaces / call & return statements that violate contracts defined by Covariant attributes.

    After this – runtime can set some expectations about types and do more agreesive compilation / typesafety inforcements.

    How this can be done ?

    Let’s assume there are two different classes – one that has "correct" and other "incorrect" covariance support:

    class IncorrectType : IMethod {

    ..public override IType Instantiate(ITypeVariableBinder binder) {

    ….return new SimplyIType(); // Incorrect covariance – IType only

    …}

    }

    and

    class CorrectType : IMethod {

    ..public override IType Instantiate(ITypeVariableBinder binder) {

    ….return new CorrectType(); // Correct – IMethod returned

    …}

    }

    Now than it’s come time to consume types/methods – runtime has to build function tables for each type. In this function table compiler can in reality ignore attribute applied and deduct minimal possible return type from IL and return types of others methods.

    For CorrectType.Instantiate – this return type will be more that simply a IMember – it will be CorrectType. So even (CorrectType) CorrectType.Instantiate() cast will be optimised – not only (IMember) CorrectType.Instantiate() (as interface describe).

    For IncorrectType – this type will not be IMember – but SimplyIType.

    Thus then consuming classes/interface methods – runtime can eliminate useless casts for correct covariant-types. This will be possible to remove casts not only from return types – but also inside covariant-arguments !

    No performance hit will be observed !

    Runtime type validation will be performed only for real non-covariant types (as there are expected exception in this case – IncorrectType ). And this separation of covariant / non-covariant will be performed regarless on correctness of attributes used (even if there is no any !) Existance of thouse attributes (but not a values!) can serve as hints for a methods compiler must check first.

    But all thouse non-covariant types will be spotted to developer via FxCop checker (if there will be design contract attributes applied to interfaces / base classes / method arguments) and it will be developer responsibility to prevent runtime cast errors. If warned of possible damages – assumed no liability for any of them !

    This kind of minimal possible return type checker is not limited to covariance – it can remove a few of others useless cast operators from generated code (conditional and very high-cost statements)- something that will be good for overall performance.

    So. While this hack is possible to implement – runtime must be award on existance of scenarios like this one – as well there must be tool that will check covariant contracts for non-covariant languages.

    Leaving enforsing contract as developer responsibility is not good !!

    P.S> Actualy using FxCop this can be potentialy possible to check if Instantiate will return the same type as owner of this method. Something that impossible to describe using any other way but constructor 😉

  8. AT says:

    For a somethat related functionality check out this:

    Useless cast elimination

    http://jcvm.sourceforge.net/doc/jc.html#Cast%20Checks

    http://lab.msdn.microsoft.com/ProductFeedback/viewfeedback.aspx?feedbackid=FDBK12918 (my suggestions – independly created !)

    Useless heap allow elimination

    http://jcvm.sourceforge.net/doc/jc.html#Stack%20Allocation

    http://lab.msdn.microsoft.com/ProductFeedback/viewfeedback.aspx?feedbackid=FDBK17206

    Or simply method inlining

    http://lab.msdn.microsoft.com/ProductFeedback/viewfeedback.aspx?feedbackid=FDBK17964

    This clearly show that it’s possible to add of cool things to runtime. Even more – we expect this.

  9. Don says:

    The lack of covariant return types is a _huge_ pain for me. It’s far and away my number one language issue. It currently affects nearly every class in my design.

    Why? Serialization.

    I have dozens of serializable classes. Naturally, I want my all my serializable classes to implement a standard IMySerialize interface, such as

    public interface IMySerializable<DataType>

    {

    string AsXmlString();

    void WriteXml(System.IO.FileStream Stream);

    void WriteBinary(System.IO.FileStream Stream);

    DataType FromXml(System.IO.FileStream Stream);

    DataType FromBinary(System.IO.FileStream Stream);

    DataType DeepCopy();

    }

    (Note: the Deserializers should be really static, but I had some problem that I can no longer recall when I tried to implement this scheme with static methods…)

    "Class foo : IMySerializable<foo>" works fine until I want to derive a "Class bar : foo". Anything derived from foo is stuck with foo’s deserialization and deep copy signatures which return foos. (I also need to be careful that the derived methods don’t serialize and deserialize foos instead of bars, but that’s another story).

    To get around the lack of covariant return types, I ended up clogging my derived classes with cruft like this:

    class bar : foo

    {

    public bar FromXml2( System.IO.FileStream Stream )

    {

    return IMySerializable<bar>.FromXmlText( Stream );

    }

    public override foo FromXml( System.IO.FileStream Stream )

    {

    return (foo)(FromXmlText2( Stream ));

    }

    etc.

    }

    As you said, ewwwww! And it took me far too long to get this working. And I keep going back to the code and burning more time trying to improve it because it’s so pervasive and so ugly.

    So please, find a way to give us covariant return types. Until I actually started working in 2.0, I probably would have ranked covariant return types MUCH lower on my priority list (assuming I could even have figured out what an issue like "covariant return types" was even talking about).

    thanks,

    -Don

  10. Dbrowne says:

    Interface enforced covariance such as below would also be of great use to me.

    interface ISymbol

    {

    thistype Instantiate(ITypeVariableBinder binder);

    }

    class Type : ISymbol

    {

    thistype Instantiate(ITypeVariableBinder binder)

    {

    return new Type();

    }

    }

    class Method : ISymbol

    {

    thistype Instantiate(ITypeVariableBinder binder)

    {

    return new Method();

    }

    }

  11. DBrowne: We discussed the "thistype" construct today and we’re actively thinking about it. It shares a lot of overlap with covariant return types (in that the thistype is implicitly covariant), but adds some extra constraints into the mix. I’m seeing if a flexible model can deal with both.

  12. Nick says:

    Several things to mention… First of all… GrantRi’s Weblog yesterday had a post where I asked about <a href="http://blogs.msdn.com/grantri/archive/2004/12/07/277954.aspx">Contravariance of parameters</a>… probably good to take a look at since these are related concepts (read the comments). He mentions that some languages may be allowing this by working around the CLR.

    Second… I feel your pain on const safety. That was on of the <a href="http://schweitn.blogspot.com/2004/02/guarantee-this-wont-change-im-sorry-i.html">first things I ever talked about on my blog</a>. I still don’t understand why they don’t have const reference parameters… and I’ve been using C# for almost 2 years now.

    Third… I don’t understand how the return type can be part of the signature. That is definitely a major change from C++. In fact, the return type CAN’T ALWAYS be considered part of the signature in C#, because this won’t work:

    class MyClass

    {

    public MyClass()

    { }

    private int MyFunction()

    {

    return 1;

    }

    private string MyFunction()

    {

    return "Hello World";

    }

    [STAThread]

    static void Main(string[] args)

    {

    MyClass c = new MyClass();

    c.MyFunction();

    }

    }

    You’re allowed to throw away the return type, so the compiler will never be able to tell which one you want to call. That is why the only things that are considered part of the signature are the method name and the parameter list.

    So this makes C# more confusing. The return type should either always be part of the signature, or never be… not sometimes be depending how you feel.

  13. DrPizza says:

    Actually, I find lack of const much more annoying. But then, I’m not a big fan of inheritance and all that malarky.

  14. AT says:

    Nick, Return type can be a part of method signature.

    It’s alrealy a part of CLR as an exception (CLS Rule 39) for type conversion operators op_Implicit or op_Explicit.

    They are different by their return type _only_ – not a name (read 10.3.3 from CLI)

    So – there is realy nothing exists that can prevent compiler for giving a compile error for your code:

    "c.MyFunction();"

    CS1234: Ambiguous method call. More that one function that satisfy method arguments exists.

    And you can specify exact (or base of it) return type of method you are expected by:

    (int) c.MyFunction();

    or

    (string) c.MyFunction();

    or

    (ValueType) c.MyFunction(); // This will call one that return int 😉

    or

    (Object) c.MyFunction(); // This will call one that return string 😉

    but this:

    (MyUserType) c.MyFunction(); // This will call one that return string 😉

    will be same compile error – not a runtime class cast one – as two methods will be ambiguous.

    As well – this can be possible to add an high preffered to void function to not type (Void) c.MyFunction(); everythere.

    If there will be void MyFunction() – it will be called without any questions. If there is no void – compiler will try to find out best possible return type. In case if there are only one function that satisfy method arguments – it’s selected. If there are more that one – user must define return type either by fictional cast or by assigning value to some variable.

    The tricky issue will be with

    MyAnotherFunction(MyFunction());

    if both int/String are legal.

    But I believe that even this can be addressed in some good way. I can imagine a few of them now.

    So – there are nothing wrong with different return types.

    Biggest problem will be readability decrease if you need to override/hide some specific functions from base class if both covariance and different return types will be supported 😉

    But even for this – this will be possible to do:

    public MyType override (MyBaseType, MyBasicInterface) DoFoo(int howMuch); // This specify that this single method override two different return type from base class.

    or

    public MyType override (MyBaseType(MySuperType), MyBasicInterface(MyAnotherSuperType)) DoFoo(MyAnotherType howMuch); // This will allow to override contravariant arguments 😉

    So. There is not limits for your imaginations 😉

  15. AT says:

    P.S> Using this hacked "override ()" signature this can be possible to override different function names using one body 😉

  16. Ruben says:

    Just another problem for attribute-based covariant return values (benefit c) without runtime support: throw delegates into the mix, and you’ve got an even greater mess. I can’t see how you can solve that unless the runtime enforces CovariantAttribute.

  17. Ruben: Can you give an example of that? In v2.0 the runtime does support covariant/contravariant matching of delegates.

  18. Ruben says:

    Auch, I had hoped I didn’t need to write up code. Anyway:

    C#

    public class Base

    {

    public virtual Base GetIt()

    {

    return new Base();

    }

    }

    public class Derrived : Base

    {

    public override Derrived GetIt()

    {

    return new Derrived();

    }

    }

    VB

    Public Class VBDerrived Inherits Derrived

    Public Overrides Function GetIt() As Base

    Return New Base();

    End Function

    End Class

    C#

    delegate Derrived DerrivedReturner();

    public static void Main()

    {

    Derrived derrived = new VBDerrived();

    DerrivedReturner returner = derrived.GetIt; // not possible, VBDerrived.GetIt is contravariant(?)

    Derrived returned = returner(); // throws

    }

  19. Ruben: Yes, i see. That would in fact be a problem. Another point for my second implementation then 🙂

  20. orangy says:

    What if compiled code conformance to CovariantAttribute (declaration and usage) is made as part of verification process? That’s goes from compiler to runtime in part, though…

  21. Joe Duffy says:

    I understand the problem here, but as presented you are binding to a specific implementation when creating Instantiate(…) anyhow, so you could use explicit interface implementation to accomplish this w/out forcing ugliness on the implementing code or the consumer. E.g. as in:

    interface ITypeVariableBinder {

    //… stuff

    }

    interface ISymbol {

    ISymbol Instantiate(ITypeVariableBinder binder);

    }

    interface IType : ISymbol {

    new IType Instantiate(ITypeVariableBinder binder);

    }

    interface IMethod : ISymbol {

    new IMethod Instantiate(ITypeVariableBinder binder);

    }

    class Symbol : ISymbol {

    ISymbol ISymbol.Instantiate(ITypeVariableBinder binder) {

    Console.WriteLine("Symbol.Instantiate");

    //…

    }

    }

    class Method : Symbol, IMethod {

    IMethod IMethod.Instantiate(ITypeVariableBinder binder) {

    Console.WriteLine("Method.Instantiate");

    //…

    }

    }

    class Type : Symbol, IType {

    IType IType.Instantiate(ITypeVariableBinder binder) {

    Console.WriteLine("Type.Instantiate");

    return null;

    }

    }

    void F() {

    IMethod method = new Method();

    IType type = new Type();

    IMethod instantiatedMethod = method.Instantiate(/*…*/);

    IType instantiatedType = type.Instantiate(/*…*/);

    }

    Although as I noted, this still doesn’t solve the theoretical problem (of supporting covariance in method overrides). Another interesting approch would be to code up an ISymbolBase<T> class or something like this:

    interface ISymbolBase<T> where T : ISymbol

    {

    T Instantiate();

    }

    interface ISymbol : ISymbolBase<ISymbol> {

    }

    interface IType : ISymbolBase<IType>, ISymbol {

    }

    interface IMethod : ISymbolBase<IMethod>, ISymbol {

    }

    class Symbol : ISymbol {

    public ISymbol Instantiate() {

    Console.WriteLine("Symbol.Instantiate");

    //…

    }

    }

    class Method : Symbol, IMethod {

    public new IMethod Instantiate() {

    Console.WriteLine("Method.Instantiate");

    //…

    }

    }

    class Type : Symbol, IType {

    public new IType Instantiate() {

    Console.WriteLine("Type.Instantiate");

    //…

    }

    }

    void F() {

    IMethod method = new Method();

    IType type = new Type();

    IMethod instantiatedMethod = method.Instantiate();

    IType instantiatedType = type.Instantiate();

    }

    But of course, this doesn’t work because there ends up being two ambiguous method calls on the type, both of which only differ by return type. In fact, interestingly you can’t call either unless you do this:

    void F() {

    IMethod method = new Method();

    IType type = new Type();

    IMethod instantiatedMethod = ((ISymbolBase<IMethod>)method).Instantiate();

    IType instantiatedType = ((ISymbolBase<IType>)type).Instantiate();

    }

    Anyhow, very interesting post. 🙂

    <joe/>

  22. joe: both of your suggestions were investigated (and the latter i felt was too connfusing to hoist on people with the first blog post, so i was waiting to write it another time).

    As you mentioned, each has flaws. The first does does not actually allow the producer to do any real overriding. The second is bad because not only is it incredibly confusnig to read but you also can’t use any actual OO genericity.

    I.e. if i have an IMethod and an IType i really want to assign them to an ISymbol. But i can’t. I can only assign the former to an ISymbol<IMethod> and the latter to an ISymbol<IType>. So,in essence, i’m encoding the actual type that i am instead of allowing you to ignore it (which you could do with real covariance).

  23. DrPizza says:

    Why not just change the runtime to directly support covariant return types/contravariant argument types?

    If C# were not constrained by the runtime then I could understand your discussion of it as a language-level feature. But it is constrained by the runtime, and IMO until *that* is fixed there’s no good C# solution.

    C++, for example, could add covariance quite easily because its spec defines both the runtime environment and the language itself; it can just dictate that "covariant return types will work properly" and the rest falls into place.

    C# can’t do that and I don’t think that any language/attribute hack will really resolve that.

  24. Ruben says:

    This discussion looks too C#-centered. Isn’t the following IL legal (or plausible)

    IL

    .method private hidebysig virtual final

    instance ISymbol Instance() cil managed

    {

    .override Symbol::Instance

    callvirt instance IType Type.Instance()

    ret

    }

    .method public hidebysig newslot virtual

    instance IType Instance() cil managed

    {



    }

    C#-ish

    private sealed override ISymbol Instance()

    {

    return (IType) Instance();

    }

    public virtual IType Instance()

    {



    }

    If you’re introducing a new covariant return type into an existing inheritance hierarchy, you’re already breaking (binary) compatibility. So introducing a new method, and hiding the original would solve our problem. Not? Maybe the CLR needs to be more relaxed about hiding publics this way, but I thought C++/CLI already supported covariant return values, so if it fits C++…

  25. DrPizza says:

    "If you’re introducing a new covariant return type into an existing inheritance hierarchy, you’re already breaking (binary) compatibility."

    Why? The covariant method can take the same position in the vtable and will work *exactly* as a drop-in replacement. That’s rather the point of covariant return types, no? At the very worst, old code (built against the non-covariant version) will have an unnecessary cast.

  26. damien morton says:

    Yes to covariant return types please. Ive just been working on this exact issue tonight using pre-generic C#. The solution involves the kind of ugliness youve illustrated.

    The problem wont go away with Generics, so please do push to have this added, if only for that C# sharp version after Whidbey.

  27. Fredrik Tonn says:

    Would the support of covariant and contravariant generic type parameters in Whidbey Beta 1 as in [1] be possible to use for this in any way?

    [1] http://weblog.ikvm.net/CommentView.aspx?guid=21f9ed86-827e-4666-9553-5d4a8d735b7e

  28. As I understand it the .NET bytecode format supports two things that would make it possible to do this in a clean manner:

    1) Methods that are usually invisible (eg property getters and setters)

    2) Methods that explicitly override a method of a different name in the base class.

    This means that you could generate something like this (in a C#-like pseudolanguage with keywords for invisibility and explicit overriding):

    public invisible ISymbol __InstantiateImpl() overrides Symbol.Instantiate() {

    return Instantiate();

    }

    public new virtual IType Instantiate() {

    // real code here

    }

    This means you don’t need anything special from the base class, and that your Instantiate method can be consumed by all languages including today’s C# and VB.

  29. Ron MacNeil says:

    Question:

    Why did the lead C# "engineer" neglect to realize that covariant return types are required for proper object oriented software design?

    Answer:

    The same brain-damange that led him to create Turbo Pascal and Delphi.

    I’ll go back to adding unnecessary casts to my client code and/or additional kludge methods to my libraries now.

    Hate. Hate. Hate.

  30. Ron, what makes you think this was a C# decision?