Home > database >  Does "C style" object slicing exist in C#?
Does "C style" object slicing exist in C#?

Time:02-17

"Slicing" here refers to the C use of that term. For reference: What is object slicing?

I thought about this in the following context:

I have this Person:

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!");
        }
    }
}

Teacher

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");
        }
    }
}

Student

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!");
        }
    }
}

and Main

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();
            }
        }
    }
}

I expected to see "Hello!" twice on the screen (because of this slicing concept) but I got Hello! I'm a student and Hello! I'm a teacher.

According to MS Docs, this is an implicit cast. But in C , due to object slicing, I'm very much sure that I would've got "Hello!" twice.

So, my questions are: Does C# have slicing? Also, if I wanted to use Student and Teacher as a Person, how would I do that? (without changing the classes, only Main)

Thanks!

CodePudding user response:

You can achieve a similar effect by applying the new keyword instead of override on the method, but it's not slicing the object as in 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!");
    }
}

Output:

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

How does it work?

First I emphasized that we use the new modifier to hide a method explicitly. If you don'g use that, it'll generate a compiler warning. Next, member lookup is strictly defined in C# and considers hidden methods.

As a result we see in IL code is that the compiler generates different methods calls:

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()

but in the debugger we see that there are 4 local variable but only two objects:

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

Why is that? Each object in C# carries along its type information. That thing is called method table (MT), basically a number of pointer size which is unique for types. You can't slice that number into meaningful smaller parts.

I have not understood all the details about the method table and related stuff yet.

  • Related