"C++ style" C# 中是否存在对象切片?

Does "C++ style" object slicing exist in C#?

这里的“切片”是指 C++ 对该术语的使用。以供参考: What is object slicing?

我是在以下情况下考虑这个问题的:

我有这个人:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Person(string name)
        {
            Name = name;
        }

        public override string ToString()
        {
            return Name;
        }

        public virtual void Greet()
        {
            Console.WriteLine("Hello!");
        }
    }
}

老师

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Teacher : Person
    {
        public Teacher() : base("empty")
        {

        }

        public override void Greet()
        {
            base.Greet();
            Console.WriteLine("I'm a teacher");
        }
    }
}

学生

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Student : Person
    {
        public Student() : base("empty")
        {

        }

        public override void Greet()
        {
            base.Greet();
            Console.WriteLine("I'm a student!");
        }
    }
}

和主要

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            Student s = new Student();
            Teacher t = new Teacher();
            List<Person> persoane = new List<Person>();
            persoane.Add(s);
            persoane.Add(t);
            foreach(Person person in persoane)
            {
                person.Greet();
            }
        }
    }
}

我希望看到“你好!”在屏幕上两次(因为这个切片概念)但我得到了你好!我是学生,你好!我是老师。

根据 MS 文档,这是一个隐式转换。但是在 C++ 中,由于对象切片,我非常确定我会得到“Hello!”。两次。

所以,我的问题是:C# 有切片吗?另外,如果我想将 Student 和 Teacher 作为一个人使用,我该怎么做? (不改类,只有Main)

谢谢!

您可以通过在方法上应用 new 关键字而不是 override 来实现类似的效果,但它不会像在 C++ 中那样对对象进行切片:

Person p = new Person();
p.Greet();
Student s = new Student();
s.Greet();
Person ps = s;
ps.Greet();
Student s2 = (Student)ps;
s2.Greet();

class Person
{
    public virtual void Greet()
    {
        Console.WriteLine("Hello!");
    }
}

class Student : Person
{
    new public void Greet()                    // note new here
    {
        Console.WriteLine("I'm a student!");
    }
}

输出:

Hello!              // from Person
I'm a student!      // from Student
Hello!              // from Student assigned to Person
I'm a student!      // from Student as Person casted back to Student

它是如何工作的?

首先我强调我们使用的new modifier to hide a method explicitly. If you don'g use that, it'll generate a compiler warning. Next, member lookup是在C#中严格定义的并且考虑了隐藏的方法。

结果我们在 IL 代码中看到编译器生成了不同的方法调用:

IL_0008: callvirt     instance void xxx.Person::Greet()
...
IL_0015: callvirt     instance void xxx.Student::Greet()
...
IL_001e: callvirt     instance void xxx.Person::Greet()
...
IL_002c: callvirt     instance void xxx.Student::Greet()

但在调试器中我们看到有 4 个局部变量但只有两个对象:

0:000> !clrstack -a
...
010ff1b8 017f0929 xxx.Program.Main(System.String[]) [C:\...\Program.cs @ 22]
    PARAMETERS:
        args (0x010ff1d4) = 0x031a2420
    LOCALS:
        0x010ff1d0 = 0x031a244c                 // p
        0x010ff1cc = 0x031a41a8                 // s
        0x010ff1c8 = 0x031a41a8                 // ps
        0x010ff1c4 = 0x031a41a8                 // s2

这是为什么? C# 中的每个对象都带有其类型信息。那个东西叫做 method table (MT),基本上是一些类型唯一的指针大小。您不能将该数字分割成有意义的较小部分。

我还没有完全理解 method table and related stuff 的所有细节。