【问题标题】:Upcasting derived class to base class in C#在 C# 中将派生类向上转换为基类
【发布时间】:2017-11-12 22:55:05
【问题描述】:

我很难通过投射来理解这种情况。 出于演示目的,我创建了这些类:

public interface IFoo {}
public class Foo : IFoo
{
      public string Name { get; set; }
}
public class Bar : Foo
{
      public string Surname { get; set; }
}

现在,在我的 Main 方法中,我创建了一个返回 IFoo 的静态方法:

class Program 
{
    static void Main()
    {
        IFoo iFoo = GetIFoo();
        Foo foo = (Foo)iFoo;           
       //the GetType ext method is executed and it returns Bar
       if(foo is Foo)
       {
            Console.WriteLine($"foo is of type: {foo.GetType()}");
       }
       Console.ReadLine();
    }
    static IFoo GetIFoo()
    {
         var bar = new Bar
         {
             Name = "MyName",
             Surname = "MySurname"
         }    
         return bar;
    }
}

问题是:即使我在 GetIFoo 方法中创建了一个 Bar,为什么如果我将 IFoo 转换为 Foo,foo 的类型是 Bar 而不是 Foo?

【问题讨论】:

    标签: c# class inheritance polymorphism


    【解决方案1】:

    这真的是inheritance 101:创建的对象是Bar,并且转换只会影响您查看它的方式,而不是真正的

    在这里您将其视为IFoo

    IFoo iFoo = GetIFoo();
    

    在这里,您故意将其视为Foo。如果它不是 Foo 或其任何后代,您将在运行时获得 InvalidCastException

    Foo foo = (Foo)iFoo;
    

    Bar 实例可以合法地作为 BarFooIFoo 使用(为了完整起见,也可以作为 Object 使用)。无论您如何查看该实例,它仍然是 Bar

    【讨论】:

    • 意思是获取 Foo 对象的唯一方法是专门实例化一个对象,然后拥有一个从另一个对象获取必要属性的构造函数?
    • @allencage 是的。强制转换不会改变(转换)对象的实际类型。一旦像 Bar 一样实例化它,它就会保持为 Bar
    【解决方案2】:

    Type.GetType 为您提供底层实例的类型(即您在new 之后指定的类型),无论它是什么类型的变量。此外,foo is Foo 不仅会在实际实例为 Foo 的情况下提供 true,而且还会为从 Foo 派生的每种类型提供。

    【讨论】:

      【解决方案3】:

      为什么如果我将iFoo 转换为Foofoo 的类型是Bar 而不是Foo

      这是因为强制转换改变了静态类型。它对动态类型没有影响。 foostatic类型是Foo,但它的dynamic类型是Bar

      每个变量都有编译器已知的类型(静态类型)和运行时已知的类型(动态类型)。在某些情况下,这两种类型是相同的,但它们不必一直相同。

      静态类型是变量的声明类型。动态类型是在将对象分配给该变量后调用GetType 得到的。

      当变量的静态类型与其动态类型不匹配时,您构造了一个示例:iFoo 的静态类型为IFoo,但其动态类型为Bar。这很好,因为BarIFoo 兼容,因为它实现了接口,也与Foo 兼容,因为它扩展了类。

      【讨论】:

      • 是否可以在运行时将动态类型 Bar 转换为 Foo?
      • 谢谢,我找到了我评论的答案。这正是我想要理解的。
      【解决方案4】:

      因为有了这条线:

      Foo foo = (Foo) iFoo;
      

      您确实转换:BarFooIFoo 同时。因此,当您转换为 (Foo) 时,C# 解释器会注意到 iFooBar 并因此保持对象不变。关键是,从现在开始,编译器将假定foo 确实是一个Foo 对象,但它可以是任何扩展Foo 类的类。

      让我们通过以下示例更清楚地说明:

      interface IDriveable {}  // IFoo
      class Car : IDriveable { // Foo
          string Name {get; set; }
      }
      class Volkswagen : Car { // Bar
          bool CorrectPolution { get; set; }
      }
      

      现在如果你构造一个Volkswagen,那么十个显然是一个Car 和一个IDriveable。但是通过说:Car car = (Car) volkswagen;。你确实没有改变大众汽车,你现在才让编译器知道car是一个Car对象。

      【讨论】:

        【解决方案5】:

        因为 GetType() 返回对象的类型。当您将派生类显式转换为基类时,您没有创建新实例,您只有对现有对象的新类型引用。您可以使用以下代码查看它:

        IFoo iFoo = GetIFoo();
        Foo foo = (Foo)iFoo;
        Console.WriteLine(ifoo.Equals(foo));
        

        【讨论】:

          【解决方案6】:

          您对对象的运行时类型和它的编译时类型感到困惑。

          IFoo iFoo = GetIFoo();
          

          在上述行中,iFoo 的运行时类型仍然是Bar。这是对象的真实类型。但是如果您通过IFoo 类型的引用访问Bar 对象,那么编译器只知道该类型是IFoo,因此称为引用iFoo 指向的对象的编译时类型。请注意,当执行GetIFoo 中的return bar 语句时,编译器会丢失此类型信息。

          Foo foo = (Foo)iFoo;
          

          再往下,您可以将其转换为Bar 层次结构中所需的任何类型,任何高于并包括Bar 的类型都将成功,编译时类型将基于您将其类型转换为的类型。但是运行时类型仍然保持不变,即Bar

          【讨论】:

          • 感谢您的回答。这让事情变得更加清晰。使用这种方法在运行时创建 Foo 的另一种方法是什么?
          • @allencage 如果你修改你的方法GetIFoo 如下static IFoo GetIFoo() { var foo = new Foo { Name = "MyName" }; return foo; } 那么你的运行时类型和编译时类型都是Foo
          猜你喜欢
          • 1970-01-01
          • 2020-08-30
          • 2017-01-08
          • 2013-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多