2

This is possibly a duplicate, but I couldn't find a similar question. I don't understand how an implicit cast to a base-type works without data-loss. For example I created the class Person:

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

Now, when I do following:

object personAsObject = new Person { Name = "Foo", Age = 1 };
var person = (Person) personAsObject;
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);

How was the object able to keep the values of the properties Name and Age since it only has the 4 methods Equals, GetHashCode, GetType and ToString? And why does a cast to a subclass throw an InvalidCastException? I would've expected that to be possible, since members are inherited if they're not private.

Damien Flury
  • 769
  • 10
  • 23
  • Something is wrong with your mental model of reference types here; are you by chance a C++ programmer who is new to C#? – Eric Lippert Nov 08 '17 at 23:23
  • 1
    Something is also wrong with your understanding of casts, but this can be forgiven because casts are a bit crazy. A cast can mean many things in C#, including "do a runtime type test of this reference and throw if it fails", "given an object of type X, find me an 'equivalent' object of type Y", "unbox this boxed value", "box this unboxed value", and a few others. It's an all purpose operator. If you have a *specific* question about casts, my advice would be to create a new question. – Eric Lippert Nov 08 '17 at 23:43
  • @EricLippert Nope, I've been working with c# for about 1 year by now but casually did some c++, to understand pointers and the memory-system better. Well apparently I haven't understood it completely yet. :) Do you know a great book or something to learn about references, pointers and memory handling etc.? I know the syntax, but I really want to learn more about what happens "under the hood". – Damien Flury Nov 09 '17 at 19:44
  • @EricLippert Yeah, I actually always thought that a cast would create a new object on the heap and box the old type into another somehow... So basically always creating a new instance. But after thinking about it that wouldn't make that much sense indeed, because you can also handle an object as an interface/abstract class ^^ So is the type of an object just something that grants us type-safety and compile-time-errors and the runtime doesn't really care? – Damien Flury Nov 09 '17 at 19:49
  • 1
    I have not read it myself, but Jeff Richter's book is apparently good for that; I've often seen it recommended for that purpose. – Eric Lippert Nov 09 '17 at 19:53
  • This one: https://www.amazon.com/CLR-via-4th-Developer-Reference/dp/0735667454? – Damien Flury Nov 09 '17 at 19:54
  • Be careful to distinguish between the *run time type of a referred-to-object* and the *compile time type of a variable or expression*. The compiler rules and the runtime rules work together to ensure that there is no way to, say, put a reference to a Giraffe into a variable of type Tiger. Attempts to do so at compile time will fail with type errors; if you do an end-run around the compiler by using casts, the runtime will throw exceptions. – Eric Lippert Nov 09 '17 at 19:56

2 Answers2

9

It's all about references.

personAsObject is not an instance of an object.

When you called new Person, you created an instance of a Person in managed memory.

The variable personAsObject is a reference: a pointer to the managed memory location where the Person object resides.

The same goes for person: it is not an instance of a Person, it's a reference to a Person (which happens to be the same object that personAsObject is referencing) in memory.

VARIABLES                  MANAGED MEMORY
=========                  ==============

personAsObject----|     +-----------------+
                  |---->| Person Instance |
person------------|     +-----------------+
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • Ah I see... Why does boxing/unboxing always have quite some overhead on performance if it's basically just a copy of the reference? – Damien Flury Nov 08 '17 at 22:59
  • 1
    @DamienFlury boxing/unboxing only applies to values that live on the stack (ie. structs), and occurs when you reference/dereference a value object as a heap object (ie. `object`) – Richard Szalay Nov 08 '17 at 23:04
  • 3
    @DamienFlury: Please ignore that comment; **value types do not "live on the stack"**. Value types are **copied by value** and **inhabit variables by value**. Do you think that if you make an array of a million ints, those four megs of ints somehow live on a one-meg stack? Of course not. Value types values no more "live on the stack" than references do; value types values live *in variables* and variables live *in a storage pool determined by an analysis of their lifetimes*. – Eric Lippert Nov 08 '17 at 23:25
  • @DamienFlury: If you want to really understand the boxing penalty, you might start with my answer from earlier today on the subject: https://stackoverflow.com/questions/47182453/memory-usage-difference-between-generic-and-non-generic-collections-in-net/47183183#47183183 – Eric Lippert Nov 08 '17 at 23:26
  • 1
    @DamienFlury: Also note that Andrew's answer cheerfully conflates *references* with *pointers*. Though references are typically *implemented* as pointers behind the scenes, references and pointers have very different rules in C#. You would do better to think of references as *references*; they are *values which refer to an object of reference type, or null*. Thinking of references as *pointers to a storage location in memory* will lead you astray. – Eric Lippert Nov 08 '17 at 23:29
  • 1
    @EricLippert I've changed my answer to replace the word `pointers` with `references`, hopefully this makes the answer more correct. – Andrew Shepherd Nov 08 '17 at 23:35
1

To paraphrase an overused example - and this isn't necessarily an example of good class design:

public class Employee
{
    public string Name { get; set; }
    public Guid EmployeeId { get; set; }
}

public class Manager : Employee
{
    public IEnumerable<Employee> DirectReports { get; set; }
}

If you cast a Manager as an Employee

var manager = GetManager(guid employeeId);
var employee = manager as Employee;

or

var employee = (Employee)manager;

You're not turning the Manager into an Employee, because the Manager already is an Employee.

In effect what you're saying is that you don't care what else this object is other than that it's an Employee. Because in some cases you might not care. Suppose you want to create a list of employees - List<Employee> - approaching their five-year anniversaries so that you can invite them to a party. It's important that you can add every sort of Employee to that list.

That also enables polymorphism. Another common example:

public interface IAnimal
{
    string MakeSound();
}

public class Cow : IAnimal
{
    public string MakeSound()
    {
        return "Moo";
    }
}

public class Giraffe : IAnimal
{
    public string MakeSound()
    {
        return "I don't make any sound. Bad example.";
    }
}

Now you can do this:

var animals = new List<IAnimal>();
animals.Add(new Cow());
animals.Add(new Giraffe());
foreach(var animal in animals) // or IAnimal in animals
{
    Console.WriteLine(animal.MakeSound());
}

which would display

Moo
I don't make any sound. Bad example.

When you go through the items in your List<IAnimal> you don't know what the actual class is for each. All you know is that each item is an IAnimal and implements MakeSound(). It doesn't change the "concrete" type for each one. It just means that for the purpose of what you're doing, you don't care about the concrete type.

This makes all sorts of things possible. You can have a class like this

public class Cow : IAnimal, ITastesGood
{
    public string MakeSound()
    {
        return "Croak";
    }
}

var cow = new Cow();
var animals = new List<IAnimal>();
var food = new List<ITastesGood>();
animals.Add(cow);
food.Add(cow);

The same instance of Cow can be cast as any class it inherits from or any interface it implements.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62