Why can't I convert from 'out BaseClass' to 'out DerivedClass'?
Asked Answered
R

3

6

I just learned that having a generic argument as the type of an out parameter forces that generic type to be invariant. This is surprising to me. I thought out parameters are treated the same as return types (i.e. if the generic parameter is covariant, then it can be used in as an out out parameter), since they are both "outputs" of a method.

After a bit of investigation, I realised that you can't do this:

public class Program {
    public static void Main() {
        // cannot convert from 'out object' to 'out string'
        F(out object s); // passing an out object
    }
    
    public static void F(out string o) {
        o = null;
    }
}

This explains why out parameters must be invariant. However, I still don't understand why you can't do this. As is commonly known, out parameters are just another way of returning a value. F could be rewritten with a return value, and it will work:

// This is the semantically equivalent version of the above, just without "out"
public class Program {
    public static void Main() {
        object s = F();
    }
    
    public static string F() {
        return null;
    }
}

So why doesn't the first code snippet compile? Does using out allow F to do something that can't be done with return values, that will break type-safety if an out object s were passed to it?

I found this question, which is about converting the other way - from a derived class to a base class, which clearly isn't possible. You can't assign the return value of a method that returns a object to a variable of type string, can you?

What I'm asking is, since you can assign the return value of a method that returns string to a variable of type object, why can't you do the same with out parameters? That is, why can't you pass an out object to a out string parameter?

I also read the docs and the spec, but they never mentioned anything about the fact that you have to pass the exact same type into a out parameter, let alone explain why you have to do it.

Rattling answered 12/9, 2020 at 8:51 Comment(4)
Don't know the answer, but notice that out is still in c# mainly for compatibility reasons, and that calls to external/unmanaged libraries tend to rely on out, and while what you describe is valid in c#, it isn't necessarily valid in other languages (AFAIK)Hazem
@CamiloTerevinto was it just for compatibility? Before tuples, it was the primary way for a method to return multiple values.Rattling
I remember reading that some years ago in some MS post/blog. You could always return multiple values through an object thoughHazem
It's used in specific cases, for example bool TryGetValue(TKey key, out TValue value) in DictionaryNarbada
S
5

With out parameters the argument is passed by reference just like ref, the difference is that the value must be assigned by the end of the method and the reference does not need to be initialized before calling. But it can be initialized before and the method can read the initial value.

enter image description here

From the docs: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier

The out keyword causes arguments to be passed by reference

It is like the ref keyword, except that ref requires that the variable be initialized before it is passed

As the method can read the variable, the reference must be of type string to work. The reading blocks covariance and the output blocks contravariance, thus the argument must be invariant.

Supportive answered 12/9, 2020 at 9:15 Comment(1)
"just like ref" This is what made me click! In addition to the fact that you can't have two methods with the same name that differs only on ref and out, I understand why this is now.Rattling
K
1

As is commonly known, out parameters are just another way of returning a value

Not true: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out

As a parameter modifier, which lets you pass an argument to a method by reference rather than by value.

That means that you are passing on a reference to a specific object.

I think your answer though is here: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref

Passing a reference type by reference enables the called method to replace the object to which the reference parameter refers in the caller.

So when you are passing an object to the function you are effectively doing an assignment of type

derived <- base

and when you are assigning from inside the function

base <- derived

Kerenkeresan answered 12/9, 2020 at 9:18 Comment(2)
“By reference” means you’re passing on a reference to a variable, not an object.Cheviot
Indeed, and this variable is of a specific type.Kerenkeresan
P
0

Think about that you can do something like this in c#:

public static void Out(out string s)
{
    Thread.Sleep(50);
    s = "World";
}

public static void Ref(ref string s)
{
    Console.WriteLine(s); // Hello
    Thread.Sleep(100);
    Console.WriteLine(s); // World
}

string str = "Hello";
new Thread(() => Out(out str)).Start();
new Thread(() => Ref(ref str)).Start();

If it is legal to change string str to object str, now str can be any type, how to keep the reference between Out and Ref method?

Pompon answered 12/9, 2020 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.