1

"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!

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
Octavian Niculescu
  • 1,177
  • 1
  • 3
  • 24
  • C# has slicing and ranges since C# 8. What you described isn't slicing – Panagiotis Kanavos Feb 14 '22 at 14:38
  • 8
    No, C# does not do slicing. This is because all classes are reference types which are heap-allocated, and slicing only happens when you have values (and you're trying to put a large value into a small space). C# does have stack-allocated struct types, but they can't use inheritance, so that avoids the issue – canton7 Feb 14 '22 at 14:38
  • 4
    @PanagiotisKanavos Different sort of slicing - read OP's link – canton7 Feb 14 '22 at 14:38
  • @canton7 okay. So is there any way to use s and t as a Person, not as a Student/Teacher? – Octavian Niculescu Feb 14 '22 at 14:39
  • @canton7 wrong term in that case. In most languages, slicing *doesn't* mean what the OP meant here. I'm not sure if any language other than C++ had this problem – Panagiotis Kanavos Feb 14 '22 at 14:40
  • @PanagiotisKanavos It's called "Slicing" in C++, and OP defined their use of the term (and even gave you a link to click on, which explains exactly what meaning they're assigning to that term). – canton7 Feb 14 '22 at 14:40
  • @canton7 I do remember this problem - 20 years ago. It was something to be avoided and worked around, not something to take advantage of. This sounds like an XY problem – Panagiotis Kanavos Feb 14 '22 at 14:41
  • @OctavianNiculescu No. If you do `Person p = new Student()`, and `Student` has overridden some virtual members from `Person`, there is no way to invoke `Person`'s versions of those members from outside of the `Student` class – canton7 Feb 14 '22 at 14:41
  • If you use `new` instead of `override` then you'd get the result you want. – juharr Feb 14 '22 at 14:42
  • 5
    @OctavianNiculescu why do you want this? In C++ it was considered a major pain, not something to take advantage of. What's the actual problem you want to solve? – Panagiotis Kanavos Feb 14 '22 at 14:42
  • @PanagiotisKanavos not any - I'm just learning C# and I want to know :) – Octavian Niculescu Feb 14 '22 at 14:43
  • C# does allow custom implicit conversions to other types - you could use that. – Dai Feb 14 '22 at 14:46
  • Protip: don't use `class` inheritance to model domain (real-world) relationships like people/humans, because eventually you'll run into very painful problems. Also, **read this:** https://boxbase.org/entries/2020/aug/3/case-against-oop/ – Dai Feb 14 '22 at 14:47
  • @OctavianNiculescu in that case, comparing other languages to C++ will be a problem. C++ isn't quite as object-oriented as any language created in the last 30 years. Object slicing is a quirk not found in other languages, caused by the way virtual tables work in C++. – Panagiotis Kanavos Feb 14 '22 at 14:48
  • 1
    It exists in C++ because any object can behave like a value, with automatic support for copying. Very important to speed, processors really like this behavior because it now becomes trivial to store objects in registers, crucial to make code fast. C# supports that as well, you use the `struct` keyword. The language rule to prevents slicing is that you can't derive a struct from a base struct, so a copy can't arbitrarily drop members. – Hans Passant Feb 14 '22 at 15:10

1 Answers1

1

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.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222