【问题标题】:C#: Why does safe casting (as) return overridden methods instead of base methods?C#:为什么安全强制转换(as)返回被覆盖的方法而不是基方法?
【发布时间】:2017-02-14 11:30:32
【问题描述】:

查看 as 关键字在 C# 中的工作方式,我可以看到它允许在显式转换(即使用括号)阻止编译的地方进行转换。

但是,在下面的代码中,我发现如果我重写派生类中的方法,然后安全地转换为基类,派生类中的重写方法仍然会执行。为什么是这样?我希望执行基类中定义的方法。

class Base
{
    public override string ToString()
    {
        return base.ToString();
    }

    public string OtherMethod()
    {
        return "Other method";
    }
}

class Derived : Base
{
    public override string ToString()
    {
        return "Derived class";
    }
}

class Program
{
    static void Main()
    {
        Derived d = new Derived();
        Base b = new Base();

        System.Console.WriteLine(b.ToString()); // Base
        System.Console.WriteLine(d.ToString()); // Derived class
        System.Console.WriteLine((d as Base).ToString()); // Derived class => WHY IS THIS?
        System.Console.WriteLine((d as Base).OtherMethod()); // Other method
        // System.Console.WriteLine((Base)d.OtherMethod()); // --- prevents compilation

        // As noted in the comments, this works
        System.Console.WriteLine(((Base)d).OtherMethod()); // Other method

        System.Console.ReadLine();
    }
}

【问题讨论】:

  • 标记为“防止编译”的行并没有因为类型转换而失败。它失败了,因为您试图从OtherMethod 转换返回值((Base)d).OtherMethod() 应该编译得很好。
  • 您正试图打破面向对象编程的多态特性。幸运的是你不能那样做,那会很糟糕。
  • 它调用被覆盖的方法,因为该方法被覆盖。这就是重写方法的全部意义所在,以便调用它们而不是基方法。
  • “这是为什么?” as 运算符不会更改内存中对象的实际类型。它只是返回一个对该对象的引用(或者如果转换失败则返回一个空引用)。

标签: c# casting


【解决方案1】:

唯一需要强制转换为基类并且会导致执行不同方法的情况是您遇到阴影的情况。

当派生类实现与基类中的方法同名的方法时会发生阴影1,并且明确地说,这不是override。通常,这些是用关键字new 标记的。如果您有派生类的实例但希望调用基类成员,则需要将其强制转换为该基类(或继承链中本身不会影响相关方法的任何中间类)

在没有阴影的情况下,任何方法调用总是使用派生最多的方法,基于对象的运行时类型,而不是变量的声明类型。

但是,通常最好避免阴影。


1不一定是直接基类 - 在继承链中的任何位置返回到 object

【讨论】:

  • 谢谢。看看这个 (shadowing-in-csharp.blogspot.co.za/2012/10/…),当您希望具有相同方法名称的派生类返回不同类型时,是时候使用阴影了?还有其他用例吗?
  • @ZachSmith - 我通常认为使用阴影的时间是从不。对于不同的返回类型,我通常会寻找基于泛型的解决方案。或替代名称。唯一不可避免的阴影是围绕着不同作者的等级制度展开的。当基类作者稍后引入继承者已经使用的方法名称时。这就是为什么阴影在技术上是一种可选功能。您没有必须使用new 来引起阴影隐藏,但它会引发警告。
【解决方案2】:

这可能会有所帮助:https://msdn.microsoft.com/en-us/library/ms173152.aspx

基类可以定义和实现虚拟方法,派生类可以覆盖它们,这意味着它们提供自己的定义和实现。 在运行时,当客户端代码调用该方法时,CLR 会查找对象的运行时类型,并调用该虚拟方法的覆盖。因此,在您的源代码中,您可以调用基类上的方法,并执行派生类的方法版本。

在您的情况下,您正在调用 Object.ToString() 虚拟方法,并且在运行时选择了适当的覆盖。

【讨论】:

    【解决方案3】:

    这就是继承和多态在 C# 中的工作原理。 as 运算符只进行安全转换,但在内部它仍然是 Derived。它只是更改它,以便编译器知道它具有正确的类型。对于您的类型,无需进行强制转换。这很有效:

     Base d = new Derived();
    

    这仍然会调用被Derived覆盖的虚方法。

    至于显式转换,它失败了,因为它的优先级低于该表达式上的其他运算符。首先评估整个右手边,然后评估演员表。本质上,这就是您要告诉编译器的内容。

     string result = d.OtherMethod();
     Base result2 = (Base)d;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-16
      相关资源
      最近更新 更多