5

Just noticed this doesn't work:

var dict = new Dictionary<int, XElement>();
XContainer element;
//...
if (dict.TryGetValue(idx, out element)) { //...

Then I tried this:

class A { }
class B : A { }

class Program {
    static void Main() {
        A a;
        a = Ret();  // no error, ok
        Ref(ref a); // compiler error, ok...
        Out(out a); // compiler error, lolwut?!
    }
    static B Ret() { return null; }
    static void Ref(ref B b) { }
    static void Out(out B b) { b = null; }
}

Why do I get a compiler error in the last call?

Edit: Ok, so as I understand from the answers 'out' is 'ref' in disguise, so it can be shared and changed by other functions or threads. But really, isn't 'out' supposed to be a way to return multiple values from a function? Because if so, it doesn't seem to be good at it. If sharing creates problems, then don't share. Just create a hidden variable at the start of the function, and work with it. I mean:

static void Out(out B b) {
    B bHidden; // compiler generated;
        // all references to b are replaced with bHidden;
    b = bHidden;
}

Is there any reason it can't be done this way? It seems safe to me now...

user1096188
  • 1,809
  • 12
  • 11
  • Looks like you're entering the world of covariance and contravariance. This might help you. http://stackoverflow.com/questions/2662369/covariance-and-contravariance-real-world-example – Jason Evans Feb 18 '12 at 14:10
  • Because `A` is not `B`, quite simply put. :) You can cast `a` to `B` and then it will work, but currently the types don't match. Makes sense, not? – Bazzz Feb 18 '12 at 14:11
  • @Bazzz Not really, after all, a function can return B and assign it to A, isn't that what polymorphism is about? Out parameters are just another way to return stuff from a function, I see no reason why it shouldn't work for them. – user1096188 Feb 18 '12 at 14:14
  • I think you are absolutely correct about sharing. I don't know the reason why compiler does not generate these hidden variables, but once the compiler designers made that decision, the restrictions on `out` inevitably follow. I've been frustrated by this behavior myself more than once :( – Branko Dimitrijevic Feb 18 '12 at 14:47

4 Answers4

3

as I understand from the answers 'out' is 'ref' in disguise, so it can be shared and changed by other functions or threads. But really, isn't 'out' supposed to be a way to return multiple values from a function? Because if so, it doesn't seem to be good at it. If sharing creates problems, then don't share. Just create a hidden variable at the start of the function, and work with it. I mean:

static void Out(out B b) 
{     
    B bHidden; // compiler generated;
               // all references to b are replaced with bHidden;
    b = bHidden; 
} 

Is there any reason it can't be done this way? It seems safe to me now...

Such a system is called a "copy out" system, for obvious reasons. It could be done that way, but doing so creates interesting problems of its own. For example:

void M()
{
    int b = 1;
    try
    { 
        N(out b);
    }
    catch (FooException)
    {
        Console.WriteLine(b);
    }
}

void N(out int c)
{
    c = 123;
    P();
    c = 456;
}

void P()
{
    throw new FooException();
}

What is the output of the program? What should it be?

Next problem: Do you want out's behaviour to be consistent or inconsistent with ref? If you want it to be inconsistent, then congratulations, you just added a highly confusing inconsistency to the language. If you want it to be consistent then you need to make ref use "copy in copy out" semantics, which introduces a number of problems of its own, both in terms of performance and in terms of correctness.

I could go on all day enumerating the differences between reference semantics and copy semantics, but I won't. The system we've got is the system we've got, so learn to work with it.

And besides, if you want to return more than one value from a method, don't use an out parameter. That might have been the smart thing to do in 2001, but it is 2012 now and we have more tools at your disposal. If you want to return two values:

  • return a tuple
  • refactor the code into two methods that each return one value
  • if the two values are a value type and a bool, return a nullable value type.
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 4
    Well, for me, and I believe for many others as well, 'out' is just a means to return several values from the function, it even says: "N pushes something 'out' into b." So to me it is not at all confusingly inconsistent with 'ref', they are two different things used for different purposes, even though their implementation is similar (and I won't know about it until the unlikely event when I try to overload two methods that differ only on ref and out). So, if N is supposed to put something in b, but blows up instead, then b remains unchanged and the output should be 1. I see no problems here. – user1096188 Feb 18 '12 at 17:01
  • And I also don't see how creating extra functions, extra structures, or using already present object that have non-explaining fields like HasValue or Item3 is smarter than simply putting several values into several variables on return. – user1096188 Feb 18 '12 at 17:05
  • @user1096188: Well, since this is a *question and answer site*, if there is something you don't understand then why not *ask another question*? There are a number of good reasons why *returning values as values* is far preferable to *mutating variables*. If you don't understand what those are, why not ask? – Eric Lippert Feb 18 '12 at 17:30
  • 2
    Well, I do. What I don't know, and it is asked in the edit part of my post, is why the mutation of the 'out' variable isn't made to imitate returning values and assigning them to given variables as closely as possible, because even the C# language specification hints that this is their most common use case: `Output parameters are typically used in methods that produce multiple return values.` – user1096188 Feb 18 '12 at 18:47
2

C# specification indicates types must be exact match:

17.5.1.3 Output parameters

When a formal parameter is an output parameter, the corresponding argument in a method invocation shall consist of the keyword out followed by a variable-reference (§ 12.3.3.27) of the same type as the formal parameter.

If it was allowed, you could do this:

class A { }
class B : A { public void BOnlyMethod() { } }
class C : A { }

public class Violations
{
    private A a;

    public void DoIt()
    {
        Violate(out this.a);
    }

    void Violate(out B b)
    {
        b = new B();
        InnocentModification();
        // what we think is B, is now C in fact, yet we still can do this:
        b.BOnlyMethod();
        // which is bound to fail, as BOnlyMethod is not present on type C
    }

    void InnocentModification()
    {
        this.a = new C();
    }
}

If such restriction wasn't present, violations of type system like the one above would be way too easy to achieve. And I suppose you don't want this kind of "possibilities" in your language.

k.m
  • 30,794
  • 10
  • 62
  • 86
2

Eric Lippert has written about this: http://blogs.msdn.com/b/ericlippert/archive/2009/09/21/why-do-ref-and-out-parameters-not-allow-type-variation.aspx

Modifying your example:

class A { }
class B : A { public int x; }

class Program {
    static void Main() {
        A a;
        a = Ret();
        Out(out a, () => a = new A());
    }
    static B Ret() { return null; }
    static void Ref(ref B b) { }
    static void Out(out B b, Action callback) {
        b = new B();
        callback();
        b.x = 3; // cannot possibly work, b is an a again!
    }
}
1

The question is basically: Aren't a = Ret() and Out(out a) supposed to be logically equivalent? If so, why one works and the other doesn't?

If I understand correctly, CLR doesn't actually have out and instead uses ref, which means that behind the scenes Out(out a) is implemented as Out(ref a), which fails for obvious reasons.

Branko Dimitrijevic
  • 50,809
  • 10
  • 93
  • 167