3

刚刚注意到这不起作用:

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

4 回答 4

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 年,我们有更多工具可供您使用。如果要返回两个值:

  • 返回一个元组
  • 将代码重构为两个方法,每个方法返回一个值
  • 如果这两个值是值类型和布尔值,则返回一个可为空的值类型。
于 2012-02-18T15:46:34.353 回答
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();
    }
}

如果不存在这样的限制,那么像上面那样违反类型系统就太容易实现了。而且我想您不希望在您的语言中出现这种“可能性”。

于 2012-02-18T14:12:41.907 回答
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!
    }
}
于 2012-02-18T14:16:29.307 回答
1

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

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

于 2012-02-18T14:22:34.757 回答