11

In C#, the out keyword can be used in two different ways.

  1. As a parameter modifier in which an argument is passed by reference

    class OutExample
    {
        static void Method(out int i)
        {
            i = 44;
        }
        static void Main()
        {
            int value;
            Method(out value);
            // value is now 44
        }
    }
    
  2. As a type parameter modifier to specify covariance.

    // Covariant interface. 
    interface ICovariant<out R> { }
    
    // Extending covariant interface. 
    interface IExtCovariant<out R> : ICovariant<R> { }
    
    // Implementing covariant interface. 
    class Sample<R> : ICovariant<R> { }
    
    class Program
    {
        static void Test()
        {
            ICovariant<Object> iobj = new Sample<Object>();
            ICovariant<String> istr = new Sample<String>();
    
            // You can assign istr to iobj because 
            // the ICovariant interface is covariant.
            iobj = istr;
        }
    }
    

My question is: why?

To a beginner, the connection between the two doesn't seem intuitive. The use with generics doesn't seem to have anything to do with passing by reference.

I first learned what out was in relation to passing arguments by reference, and this hindered my understanding of the use of defining covariance with generics.

Is there a connection between these uses that I'm missing?

  • 5
    The connection is slightly more understandable if you look at the covariance and contravariance usage in System.Func<in T, out TResult> delegate. – rwong Feb 10 '15 at 4:30
  • 4
    Also, most language designers try to minimize the number of keywords, and adding a new keyword in some existing language with a large codebase is painful (possible conflict with some existing code using that word as a name) – Basile Starynkevitch Feb 10 '15 at 8:56
20

There is a connection, however it is a little loose. In C# the keywords ´in´ and ´out´ as their name suggest stand for input and output. This is very clear in the case of output parameters, but less clean what it have to do with template parameters.

Lets take a look at the Liskov substitution principle:

...

Liskov's principle imposes some standard requirements on signatures which have been adopted in newer object-oriented programming languages (usually at the level of classes rather than types; see nominal vs. structural subtyping for the distinction):

  • Contravariance of method arguments in the subtype.
  • Covariance of return types in the subtype.

...

See how contravariance is associated with input and covariance is associated with output? In C# if you flag a template variable with out to make it covariant, but please note you can only do this if the mentioned type parameter only appears as output (function return type). So the following is invalid:

interface I<out T>
{
  void func(T t); //Invalid variance: The type parameter 'T' must be
                  //contravariantly valid on 'I<T>.func(T)'.
                  //'T' is covariant.

}

Similary if you flag a type parameter with in, that means you can only use it as input (function parameter). So the following is invalid:

interface I<in T>
{
  T func(); //Invalid variance: The type parameter 'T' must
            //be covariantly valid on 'I<T>.func()'. 
            //'T' is contravariant.

}

So to summarize, the connection with the out keyword is that with function parameters it means that it is an output parameter, and for type parameters it means that the type is only used in output context.

System.Func is also a good example what rwong mentioned in his comment. In System.Func all input parameters ar flaged with in, and the output parameter is flaged with out. The reason is exactly what I described.

  • 2
    Nice answer! Saved me some … wait for it … typing! By the way: the part of the LSP that you quoted was actually known long before Liskov. It's just the standard subtyping rules for function types. (Parameter types are contravariant, return types are covariant). The novelty of Liskov's approach was a) phrasing the rules not in terms of co-/contravariance but in terms of behavorial substitutability (as defined by pre-/postconditions) and b) the history rule, which makes it possible to apply all this reasoning to mutable datatypes, which wasn't previously possible. – Jörg W Mittag Feb 10 '15 at 10:43
10

@Gábor has already explained the connection (contravariance for all that goes "in", covariance for all that goes "out"), but why re-use keywords at all?

Well, keywords are very expensive. You cannot use them as identifiers in your programs. But there is only so many words in the English language. So, sometimes you run into conflicts, and you have to awkwardly rename your variables, methods, fields, properties, classes, interfaces, or structs to avoid clashing with a keyword. For example, if you are modeling a school, what do you call a class? You can't call it a class, because class is a keyword!

Adding a keyword to a language is even more expensive. It basically makes all code that uses this keyword as an identifier illegal, breaking backwards-compatibility all over the place.

The in and out keywords already existed, so they could just be reused.

They could have added contextual keywords which are only keywords within the context of a type parameter list, but what keywords would they have chosen? covariant and contravariant? + and - (like Scala did, for example)? super and extends like Java did? Could you remember off the top of your head which parameters are covariant and contravariant?

With the current solution, there's a nice mnemonic: output type parameters get the out keyword, input type parameters get the in keyword. Note the nice symmetry with method parameters: output parameters get the out keyword, input parameters get the in keyword (well, actually, no keyword at all, since input is the default, but you get the idea).

[Note: if you look at the edit history, you will see that I actually originally switched the two around in my introductory sentence. And I even got an upvote during that time! This just goes to show you how important that mnemonic really is.]

  • The way to remember co- versus contra-variance is to consider what happens if a function in an interface takes a parameter of of an generic interface type. If one has an interface Accepter<in T> { void Accept(T it);};, an Accepter<Foo<T>> will accept T as an input parameter if Foo<T> accepts it as an output parameter, and vice versa. Thus, contra-variance. By contrast, interface ISupplier<out T> { T get();}; a Supplier<Foo<T>> will have whatever sort of variance Foo has--thus co-variance. – supercat May 20 '15 at 17:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.