Dive into sealed keyword and abstract class constructors.
I recently submitted two Pull Requests to the official C# documentation, and I’m excited to share the story behind them and what I learned in the process. These PRs stemmed directly from exercises on the Exercism.org platform in the C# path, followed by some great discussions in my C# mentoring group. While working through the exercises, I noticed that certain areas in the documentation could benefit from some updates and clarification, so I decided to contribute!
Let’s jump straight into the specifics of my contributions:
PR #1: Clarifying “sealed” for Overridden Methods
My first Pull Request (https://github.com/dotnet/docs/pull/44196) focused on improving the documentation for the sealed keyword, mainly when applied to overridden methods.
During our mentoring session, a question arose about preventing methods from being overridden further down the inheritance chain. We knew about the sealed keyword, but there was some confusion about its limitations, especially regarding methods inherited from the Object class, like ToString().
Here’s the key takeaway I clarified in the documentation:
You can use sealed to prevent further overriding of methods that your class has overridden from a base class, including fundamental methods like ToString(), GetHashCode(), and Equals() that are inherited from the Object class.
Why is this a big deal?
Every class in C# ultimately inherits from Object. This means they all get methods like ToString(). I discovered that you can seal overrides of methods inherited from the Object class in your class! Here is the sample from my Pull Request:
public class Animal
{
public virtual string ToString() => "I'm an animal";
}
public class Dog : Animal
{
public sealed override string ToString() => "I'm a dog";
}
public class Beagle : Dog
{
// This will cause a compiler error!
// public override string ToString() => "I'm a beagle";
}
This provides much finer control over method behavior in class hierarchies.
PR #2: Demystifying Constructors in Abstract Classes
My second Pull Request (https://github.com/dotnet/docs/pull/44223) delved into the sometimes confusing world of constructors in abstract classes.
Our mentoring group discussed abstract classes’ roles, and we realized the documentation regarding constructors could be more apparent.
Here’s what I aimed to clarify:
Even though you can’t directly create instances of an abstract class (they’re like templates), they can still have constructors! And what’s more, those constructors are often protected.
Why have a constructor in an abstract class?
It’s all about initialization. The protected constructor of an abstract class can be used to set up common properties or perform initial actions that all derived classes will need.
The crucial detail about derived classes and constructors:
My PR highlighted that when you define a constructor in an abstract class that takes parameters, derived classes must explicitly call that constructor using : base(). This is because C# does not automatically provide a parameterless constructor when you’ve defined any constructor yourself.
Here’s the example I used in the documentation:
public abstract class Shape
{
protected string Color { get; set; }
protected Shape(string color)
{
Color = color;
Console.WriteLine("Shape constructor called");
}
public abstract double GetArea();
}
public class Square : Shape
{
private double Side { get; set; }
public Square(string color, double side) : base(color)
{
Side = side;
Console.WriteLine("Square constructor called");
}
public override double GetArea() => Side * Side;
}
In this example, Square must call the Shape constructor using : base(color).
Default Parameter Values:
I also emphasized that even if you provide default values for the parameters in your abstract class constructor (e.g., protected Shape(string color = “red”)), that constructor will still be invoked when a derived class object is created, even if the call to base() is omitted. In this case, the default values will be used. This behavior ensures consistent initialization, even if the derived class doesn’t explicitly pass arguments to the base constructor.
Conclusion
These two Pull Requests, born from discussions in my C# mentoring group, helped solidify my understanding of sealed and abstract class constructors. I hope that by sharing these insights, especially the nuances about sealing overridden methods from Object and the behavior of default parameters in abstract class constructors, I’ve helped you gain a deeper appreciation for these C# features. Contributing to the documentation was a great way to learn, and I encourage everyone to get involved!
