3

I understand that a base class cannot be converted into a derived class. What I don't understand is why this isn't caught at compile time? For example:

class GradeBook
{

}

class ProfessorGradeBook : GradeBook
{

}

class Program
{
    static void Main(string[] args)
    {
        ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook(); 
        //shouldn't this be a compile time error?
    }
}

I've looked at other questions on stackoverflow but it still doesn't make sense to me why this would compile? ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook(); would never succeed under any circumstance (right?) so why is this a run-time error instead of a compile-time error?

EDIT:

I already knew why the compiler would never catch this:

GradeBook a = new ProfessorGradeBook();
ProfessorGradeBook b = (ProfessorGradeBook)a; 

At run-time, a could be pointing to anything so the compiler should just trust you. I was more concerned with why the compiler would never catch this:

ProfessorGradeBook a = (ProfessorGradeBook)new GradeBook();

I guess the answer that makes the most sense is Eric Lippert's first comment, specifically "the vast majority of developers would never type that line of code" so the compiler team was never concerned with trying to make that an error.

madmac
  • 33
  • 4
  • Its not a compile problem because the compiler separate the 2 commands. First it makes a new instance of GradeBook, then he tries to cast an object from a base object, and that's fine too. The compiler doesn't "know" that this object is really just a base object. – Ziv Weissman Dec 12 '15 at 23:27
  • 1
    In short: the compiler team could spend time and energy making that a warning or an error. The goal of spending that limited effort is to **solve real problems that real developers actually have**. An explicit non-goal is **find at compile time any possible mistake**. The compiler could easily give a warning about this. It does not because the vast majority of developers would simply never type that in the first place. A warning that never fires because no one types that code is wasted effort that could be spent on designing more useful warnings. – Eric Lippert Dec 13 '15 at 00:24
  • More specifically, the cast operator has a very specific meaning here; it means "*attention compiler, I am writing code that you cannot prove is typesafe here. I know it is typesafe; trust me, and allow this code to compile. If I am wrong, please crash my program*" That is, the cast is *explicitly turning off a safety system*. The compiler can hardly be blamed for not giving an error when you turn off the error detection system! When you write a cast, *you* are responsible for ensuring that the cast will succeed, not the compiler! – Eric Lippert Dec 13 '15 at 00:29

3 Answers3

1

This is a downcast. The compiler can't know if a less typed reference may or may not be castable to a more specialized type.

For example:

public class A {}
public class B {}

A a = new B();
B b = (B)a;

a is typed as A while the object stored there is of type B. BTW, when you try to cast it to B, since an instance of A can be A itself or any derived class of A (including B, but not only B), the compiler can't assume that a won't be castable to B because you're providing the assumption that it will be possible using an explicit cast.

At the end of the day, typing is metadata. If you declare a reference as A you're telling the compiler that whatever you set there will be A, assuming that you're losing compile-time metadata to access more specialized members from a possible derived type. In other words: you're telling the compiler that the reference is A and you don't care about the derived type metadata in compile-time and any downcast will be evaluated during run-time because the compiler can't prove a downcast unless the code is executed and runtime finds that the so-called downcast isn't possible since the explicitly provided type isn't part of the source type hierarchy.

Probably compilers could be able to catch invalid downcasts, but this could also increase build times...

Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
0

You are right that it would never succeed in practice, but the new instruction is a run-time instruction, not a compile-time instruction, and you are doing an explicit cast to (ProfessorGradeBook) which is basically saying to the compiler: "hey compiler, just trust me it'll work".

And so the compiler does.

There are scenarios where one could use things like Fody or PostSharp to add conversion operators after compilation

Lindsey1986
  • 381
  • 1
  • 3
  • 15
0

The compiler does not catch all statically knowable errors. That runs into the halting problem. The compiler catches a well defined subset of all statically discoverable errors.

Also note, that the compiler is not allowed to be arbitrarily smart. The C# language spec says how smart exactly it must be so that all C# compilers behave the same way.

How smart do you like it? Do you want it to catch this as well?

static void Main(string[] args)
{
    var g = new GradeBook();
    ProfessorGradeBook a = (ProfessorGradeBook)g; 
    //shouldn't this be a compile time error?
}

That's harder. We can make it arbitrarily hard.

usr
  • 168,620
  • 35
  • 240
  • 369