Inheritance
Access Modifiers
One of the core ideas that OOP tries to promote is to make your classes into black boxes. That's because if you expose the implementation details of a class, other code that uses that class will be affected if you change those implementation details.
In contrast, everything we want to make accessible is part of the public interface of a class: it's what we want to expose to our users.
To create the black box and public interface of a class, we have access modifiers. If you recall, these are the ones we have available in C#:
publicprivateprotectedinternalprotected internal
public
publicMakes a field/property/method accessible everywhere. You use public when you want your field/property/method to be part of the public interface of the class.
public class Customer
{
public void Promote() {}
}
var customer = new Customer();
customer.Promote();private
privateMakes a field/property/method only accessible inside the class. You use private when you're dealing with the implementation details and so don't want to give open access.
protected
protectedMakes a field/property/method accessible from the class AND its derived classes.
Pro tip: protected is not good practice because it's not enough of a black box. Any derived classes still are granted access! For that reason, private is preferred over protected.
internal
internalMakes a class itself accessible only from the same assembly. You use internal when a class you create is a useful implementation detail for all the classes in its assembly.
protected internal
protected internalMakes a field/property/method accessible from the same assembly OR accessible from any derived classes.
Pro tip: This access modifier is really weird. You likely won't use it.
Constructors and Inheritance
Suppose you create a Vehicle class and a derived Car class that inherits from Vehicle. There are 2 things to note about the Vehicle constructor:
Base class constructor is always executed before the derived class constructor.
Base class constructor is never inherited by the derived class.
In code, that means you have to explicitly create a new constructor for the derived class:
There's 2 troubles with this though!
You can't initialize
_registrationNumberin theCarconstructor because it's aprivatefield. This means it's not accessible in a derived class likeCar.The constructor for
Carfeels like repeated code. Is there a way to simplify this?
To solve this, we introduce the base keyword: it passes arguments to the base class constructor while inside the derived class.
Upcasting and Downcasting
Upcasting is converting a derived class to a base class, and downcasting is converting a base class to a derived class.
Upcasting
Suppose you have a base class and derived class. To upcast, i.e., convert a derived class to its base class, C# does it using implicit type conversion.
Note:
shapeandcircleare actually the same object in memory (shape == circlereturnstrue), but they have different views.circlecan see all theCirclemembers and theShapemembers.In contrast,
shapecan only see theShapemembers.You'll see later how this is useful when we talk about polymorphism.
Pro tip: One common use case for upcasting is when you pass arguments to a function and/or constructor. When a parameter supports a base class, you know you can also pass the derived class, and it will be implicitly converted!
Downcasting
With downcasting, you must perform explicit type conversion, which is known as casting.
Pro tip: A common use case for downcasting is when you need access to a greater view of available fields/properties/methods. For example, maybe you have a generic object, but you believe it is a Button, and you want to use the Button members.
The as keyword
as keywordWhen downcasting, you can't always guarantee it will work. That means there may be an InvalidCastException error, breaking the application if it's not handled.
To more elegantly solve this without creating an error, you use the as keyword:
The is keyword
is keywordAlternatively, instead of as where you cast first and then find out if it failed, you can use is to check if it's possible to cast.
Boxing and Unboxing
If you recall, there are value types and reference types.
Value types:
Stored in stack where items in memory get removed immediately after they go out of scope.
Stack also has more limited amount of memory.
Examples:
int,bool,char
Reference types:
Stored in heap where items require a longer lifetime.
Heap also has a lot more memory allocation.
Examples: any classes like objects, arrays, strings
Additionally, we also know that object is the base class of all classes.
Boxing
What happens if we implicitly convert a value type into an object? This is known as boxing:
Behind the scenes, the CLR boxes the value 10 and stores it in the heap. Then it places a reference to that object in the stack.
Note: Creating an object has a performance cost, so be aware of this.
Unboxing
Unboxing is exactly what you think: after a value type has been boxed in a reference in the heap, you can extract the value and put it back in the stack by casting the reference.
Last updated