Can't pass variables of base types as out parameters?

て烟熏妆下的殇ゞ 提交于 2019-12-06 08:14:01

问题


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...


回答1:


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.



回答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!
    }
}



回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/9341887/cant-pass-variables-of-base-types-as-out-parameters

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!