Person p;
declares a new variable with the
compile-time type of
Person
.
It can hold any object of type
Person
, or of any type derived from
Person
. As a result, the compiler will only let you access the members defined on the
Person
type or its base types.
p = new Student();
creates a new instance of the
Student
class, and stores it in the
p
variable.
This works, because the
Student
class inherits from the
Person
class. A
Student
is a Person
.
The variable now has a
run-time type of
Student
. But the
compile-time type is still
Person
.
The compiler won't let you access the
ID
property, because it's not defined on the
Person
class or one of its base classes. The compiler has no idea what the run-time type of the variable will be, and whether it will have an
ID
property.
p = p as Student;
takes the value stored in
p
, and attempts to cast it to a
Student
instance. If it succeeds, the value will be put back in the variable
p
. If it fails, the variable
p
will be set to
null
.
But the compile-time type of
p
does not change. You still cannot access the
ID
property, because the compiler has no idea whether it even exists.
There are various ways you can access the property:
Student s = p as Student;
int id = s == null ? 0 : s.ID;
int id = (p as Student)?.ID ?? 0;
int id = ((Student)p).ID;
dynamic d = p;
int id = d.ID;