"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 的所有细节。
这里的“切片”是指 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 的所有细节。