为什么派生类的引用不能用于需要基类引用的方法?

11

我遇到了以下编译器错误。我不知道为什么我不能获取派生类的引用并将其传递给需要基类引用的方法。请注意,foo()和bar()方法不一定具有相同的语义,因此它们应该具有不同的名称,这些方法不是问题所在。

public class X { public int _x; }  
public class Y : X { public int _y; }  

public class A {  
  public void foo( ref X x ) {  
    x._x = 1;  
  }  
}  

public class B : A {  
  public void bar( ref Y y ) {  
    foo( ref y ); // generates compiler error
    foo( ref (X)y); // wont work either
    y._y = 2;  
  }  
}

我找到的唯一解决方案是:

public class B : A {  
  public void bar( ref Y y ) {  
    X x = y;
    foo( ref x ); // works
    y._y = 2;  
  }  
}

我知道在bar()中"y"从未被初始化,但由于它被声明为引用,因此必须在方法外部初始化,所以这不可能是问题的原因。如果您能对此事提供任何启示,将是有帮助的。我相信这只是我对C#理解不够,如果使用转换操作符,这在C++中可以工作。


3
为什么你一开始要将参数定义成引用类型?根据你的示例,似乎并不需要使用引用,可以通过按值传递参考来实现。请问你这样做的原因是什么? - Brian Rasmussen
1
我认为这只是一个非常简化的示例,展示了问题,而不是它所使用的完整代码。 - Joey
没错 Johannes,这不是生产代码。 :) - Timo
6个回答

20

因为无法确保您不会将引用替换为与最初传入的类型完全不同的实例。

给定:

class Base
{}

class Hamster : Base
{}

class ADentist : Base
{}


void ohWait(ref Base obj)
{
    obj = new ADentist();
}

当以这种方式调用它时:

var foo = new Hamster();
ohWait(ref foo);

这样写的话代码会变得难以维护。Eric Lippert 在他的博客中解释得比我好:为什么ref和out参数不允许类型变化?


2
没错,这当然是可能的。不过为什么有人会设计一个这样的方法对我来说是无法理解的,如果他们这样做了,那只能怪他们自己。有时候C#太过于严格了。谢谢大家的回答。 - Timo

3

我知道这是一个非常古老的问题,但由于我试图找到解决此问题的方法并未能成功,因此我决定为那些将来访问此问题的人发布一个快速的解决方法。绕过C#类型系统的最佳方法是充分利用模板/泛型类型:

public class A {
  public void foo<T>( ref T x ) where T : X {  
    x._x = 1;  
  }  
}  

public class B : A {  
  public void bar( ref Y y ) {  
    foo<Y>( ref y ); // works now
    y._y = 2;
  }  
}

现在函数foo已经扩展到类型X的所有子类,但是您必须确保提供正确的总体类型。


你也可以在 T 的 where 子句中添加 ", new()",这样它也可以在 foo 函数中被实例化。 - Brain2000

2

以下是一个简单的例子,用类X和Y来说明为什么这是不允许的:

void ReplaceX(ref X x)
{
    x = new X();
}

void Test()
{
    X x = new X();
    ReplaceX(ref x); // Fine, our local variable x is now replaced

    Y y = new Y();
    ReplaceX(ref y); // Error, since it would replace our local 
                     // variable typed as Y with an instance of X
}

1

当你给一个需要 ref X 的函数传递一个 ref Y 时,你的意思是 "这里,我有一个 X 给你,你可以更改引用以让它指向一个新对象。"

尽管每个派生类都可以替代其超类,但反过来却不成立。

该方法可以简单地将您提供的 ref Y 指向一个不是 YX 实例。然而,这在 C# 的类型系统中不可能正常工作。


1
假设foo实际上是这样的:
x = new X();

那么在B.bar()中的“y”变量将不再引用 Y 的实例,这将是一个坏事。

有关更多详细信息,请参见Eric Lippert有关此主题的博客文章

我同意其他帖子的观点-您不需要使用 ref 。 有关该主题的更多信息,请参见我的参数传递文章



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接