为什么初始化派生类变量并分配给它的基类类型不允许访问其成员?

时间:2014-06-19 06:08:34

标签: c# oop inheritance polymorphism

我已经开始学习C#和面向对象的编程,我必须承认它非常有趣。现在,当我学习继承和多态时,我想到了以下思想,并尝试在代码中使用它并查看结果。我会发布代码,然后提出具体问题,这样任何回答我的人都会更容易按照我的思路。

class Program
{
    public static void Main(string[] args)
    {
        B b = new B();
        b.b = 5;
        A a = b;
        Console.WriteLine(a.b);
    }
}

class A
{
}

class B : A
{
    public int b;
}

现在,当我尝试运行上面的代码时,它给了我一个编译时错误,说''ConsoleApplication1.A'不包含'b'的定义,没有扩展方法'b'接受类型的第一个参数'ConsoleApplication1.A'可以找到“。基本上我理解这意味着成员变量b不可访问。

但是,如果我注释掉Console.WriteLine()并在Debug模式下执行并将鼠标悬停在声明为'A'类类型的变量上,则它指向的对象确实有一个成员变量b,其值为5

我的问题是 -

  • 为什么成员变量不能用于类类型A的变量? (我承认我有点理解,在编译时编译器不知道分配给变量'a'的对象有成员变量b,它被赋值为5,这让我想到了下一个问题)
  • 这是继承概念中的某种固有缺陷,虽然基类的对象具有派生类的成员,但由于它们被分配给基类类型的变量,所以无法访问它们吗?

3 个答案:

答案 0 :(得分:1)

第一个项目符号点中的自我回答是正确的:编译器在确定哪些成员可用时,不会查看已赋给变量的值。 关心变量的编译时类型。这与b变量是否可以访问"无关。在正常意义上(公共,私有,内部等) - 就编译器而言,该成员是否被认为是存在。您正在使用A类型的表达式,因此当编译器尝试解析成员b时,它根本找不到它。

至于你的第二个要点:不,我根本不认为这是一个固有的缺陷。可以说它是的好处,因为它让你思考你正在努力工作的抽象层次。这反过来允许您将该抽象的一个实现替换为另一个。如果您总是(以某种方式)访问派生类型的成员,那么如果您以后想要使用不同派生类型的实例替换该值,则您的代码可能会中断。如果您希望依赖a作为B引用的值,则只需将变量声明为类型B而不是A。通过使用类型A声明变量,您明确地说"我不关心实现是什么;我只关心从A开始使用这个对象。"

在C#中有一种方法:使用dynamic代替。这样,所有成员访问都在执行时解决,而不是编译时,并作用于动态类型变量的执行时类型。 (或者更确切地说,遵循正常的访问控制规则,您通常可以访问的任何视图。)

例如:

B b = new B();
b.b = 5;
dynamic a = b;
Console.WriteLine(a.b);

那将编译并运行。当然,它不是很安全,因为你可以同样拥有:

Console.WriteLine(a.typoInMemberName);

...仍将编译,但在执行时失败。这是使用动态类型的一个缺点。

答案 1 :(得分:0)

您可以通过将a转换为B来查看变量。(a as B).b您无法访问未声明的成员。只有当类像ViewBag一样动态时。这是因为在C#中严格键入并且实际上并不是关于OOP。在PHP的OOP中,您发布的代码是有效的。

答案 2 :(得分:0)

在这种情况下无法访问""。上下文是一种类型" A"上下文,所以它没有字段" b"。

将其投射到" B"你可以访问公共领域。

 A a = b;
 Console.WriteLine((a as B).b); 

在这种情况下,它不是关于可访问性(成员访问),而是关于类型签名。什么是" A"?一个" A"没有.b字段。但是,如果你使用反射来检查" a"对象,你应该找到该字段,如果它真的是" B"。

这不是一个缺陷。考虑另一种选择。如果" A"确实有一个" b"字段,但你重新定义了" b"课堂上的字段" B" (阴影)。你期望编译器做什么?哪个.b是对的?由类型上下文决定的那个。