采取以下措施:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

为什么会出现上面的编译时错误呢?两者都会发生这种情况 refout 论据。

有帮助吗?

解决方案

=============

更新:我用这个答案作为这篇博客文章的基础:

为什么 ref 和 out 参数不允许类型变化?

有关此问题的更多评论,请参阅博客页面。感谢您提出的好问题。

=============

假设你有课 Animal, Mammal, Reptile, Giraffe, TurtleTiger, ,具有明显的子类关系。

现在假设你有一个方法 void M(ref Mammal m). M 既可以读写 m.


可以传递一个类型的变量吗 AnimalM?

不。该变量可以包含一个 Turtle, , 但 M 将假设它只包含哺乳动物。A Turtle 不是一个 Mammal.

结论1: ref 参数不能变得“更大”。(动物的数量多于哺乳动物,因此变量变得“更大”,因为它可以包含更多的东西。)


可以传递一个类型的变量吗 GiraffeM?

不。 M 可以写信给 m, , 和 M 可能想写一个 Tiger 进入 m. 。现在你已经放了一个 Tiger 到一个实际上是类型的变量中 Giraffe.

结论2: ref 参数不能变得“更小”。


现在考虑 N(out Mammal n).

可以传递一个类型的变量吗 GiraffeN?

不。 N 可以写信给 n, , 和 N 可能想写一个 Tiger.

结论3: out 参数不能变得“更小”。


可以传递一个类型的变量吗 AnimalN?

唔。

嗯,为什么不呢? N 无法读取 n, ,它只能写它,对吧?你写一个 Tiger 到一个类型的变量 Animal 你们都准备好了,对吧?

错误的。规则不是“N 只能写入 n".

简单来说,规则是:

1) N 必须写信给 nN 正常返回。(如果 N 投掷,所有投注均无效。)

2) N 必须写一些东西 n 在它读取某些内容之前 n.

这允许发生以下事件序列:

  • 声明一个字段 x 类型的 Animal.
  • 经过 x 作为 out 参数为 N.
  • N 写了一个 Tiger 进入 n, ,这是一个别名 x.
  • 在另一个线程上,有人写了一个 Turtle 进入 x.
  • N 尝试读取内容 n, ,并发现一个 Turtle 它认为是类型变量 Mammal.

显然我们希望将其定为非法。

结论4: out 参数不能变得“更大”。


定论: 两者都不 ref 也不 out 参数的类型可能有所不同。否则就会破坏可验证的类型安全性。

如果您对基本类型理论中的这些问题感兴趣,请考虑阅读 我的关于 C# 4.0 中协变和逆变如何工作的系列.

其他提示

因为在这两种情况下,您必须能够为ref / out参数赋值。

如果你尝试将b传递给Foo2方法作为参考,并且在Foo2中尝试分配a = new A(),这将无效。
你不能写的原因相同:

B b = new A();

你正在努力解决协方差(和逆变)的经典OOP问题,请参阅维基百科:就像这个事实可能违背直觉期望一样,在数学上不可能允许替换派生类来代替基础类的可变(可赋值)参数(以及其项目可分配的容器,同样的原因)同时仍然尊重 Liskov的原则。为什么会这样,在现有答案中勾勒出来,并在这些维基文章及其链接中进行更深入的探讨。

在保持传统静态类型安全的同时出现这种情况的OOP语言是“作弊”。 (插入隐藏的动态类型检查,或要求检查所有源的编译时检查);根本的选择是:要么放弃这种协方差并接受从业者的困惑(如C#在这里做的那样),要么转向动态类型化方法(作为第一个OOP语言,Smalltalk,确实如此),或者转向不可变(单一 - 赋值)数据,就像函数式语言一样(在不变性的情况下,你可以支持协方差,还可以避免其他相关的谜题,例如你在可变数据世界中不能拥有Square子类Rectangle这一事实)。

考虑:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

它会违反类型安全

虽然其他回复已经简洁地解释了这种行为背后的原因,但我认为值得一提的是,如果你真的需要做这种性质的事情,你可以通过将Foo2变成泛型方法来实现类似的功能,因为:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

因为给 Foo2 一个 ref B 会导致格式错误的对象,因为 Foo2 只知道如何填充 A B 的一部分。

这不是编译器告诉你它希望你明确地转换对象,以便它可以确定你知道你的意图是什么吗?

Foo2(ref (A)b)

从安全角度来看是有道理的,但如果编译器发出警告而不是错误,我会更喜欢它,因为有合法使用的引用传递的多态对象。 e.g。

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

这不会编译,但会起作用吗?

如果您使用类型的实际示例,您会看到它:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

现在你的功能需要祖先 Object ):

void Foo2(ref Object connection) { }

可能出现什么问题?

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

您刚刚设法将 Bitmap 分配给 SqlConnection

这不好。


再试一次:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

你塞了一个 OracleConnection SqlConnection 之上。

在我的情况下,我的函数接受了一个对象,我无法发送任何内容,所以我只是做了

object bla = myVar;
Foo(ref bla);

这有效

My Foo在VB.NET中,它检查内部类型并执行大量逻辑

如果我的答案重复但有些人太长了,我道歉

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top