不能将基类型的变量作为out参数传递?

时间:2012-02-18 14:07:34

标签: c# out

注意到这不起作用:

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

然后我尝试了这个:

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; }
}

为什么我在上次调用中遇到编译器错误?

编辑:好的,所以我从答案中了解'out'是伪装的'ref',所以它可以被其他函数或线程共享和更改。但实际上,不是'out'应该是从函数返回多个值的方法吗?因为如果是这样的话,它似乎并不擅长。 如果共享产生问题,则不要共享。只需在函数的开头创建一个隐藏变量,然后使用它。我的意思是:

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

有什么理由不能这样做吗?现在对我来说似乎很安全......

4 个答案:

答案 0 :(得分:3)

  

正如我从答案中所理解的那样'out'是伪装的'ref',所以它可以被其他函数或线程共享和更改。但实际上,不是'out'应该是从函数返回多个值的方法吗?因为如果是这样的话,它似乎并不擅长。如果共享产生问题,则不要共享。只需在函数的开头创建一个隐藏变量,然后使用它。我的意思是:

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

有什么理由不能这样做吗?现在对我来说似乎很安全......

由于显而易见的原因,这种系统被称为“复制”系统。它可以这样做,但这样做会产生自己的有趣问题。例如:

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();
}

该计划的输出是什么?它应该是什么?

下一个问题:你是否希望out的行为与ref一致或不一致?如果你想让它不一致,那么恭喜你,你刚刚给语言添加了一个非常令人困惑的不一致。如果你想让它保持一致,那么你需要让ref使用“copy in copy out”语义,这在性能和正确性方面都会引入许多问题。

我可以一整天都在列举引用语义和复制语义之间的差异,但我不会。我们得到的系统就是我们所拥有的系统,所以要学会使用它。

此外,如果您想从方法中返回多个值,不要使用out参数。这可能是2001年的明智之举,但现在是2012年,我们有更多工具可供您使用。如果要返回两个值:

  • 返回一个元组
  • 将代码重构为两个方法,每个方法都返回一个值
  • 如果两个值是值类型和bool,则返回可为空的值类型。

答案 1 :(得分:2)

C#规范表明类型必须完全匹配

  

17.5.1.3输出参数

     

当形式参数是输出参数时,方法调用中的相应参数应包含关键字out,后跟与形式参数相同类型的变量引用(第12.3.3.27节)。

如果允许,你可以这样做:

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();
    }
}

如果不存在此类限制,违反上述类型系统的行为将太容易实现。而且我想你不希望用你的语言来表达这种“可能性”。

答案 2 :(得分:2)

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

修改您的示例:

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 :(得分:1)

问题基本上是:a = Ret()Out(out a)不应该在逻辑上等同吗?如果是这样,为什么一个有效,另一个无效?

如果我理解正确,CLR实际上没有out,而是使用ref,这意味着幕后Out(out a)实现为Out(ref a),但失败了原因很明显。