Encapsulation and reusable design · Lesson 29 About 90 min with practice
C# Inheritance and the Base Class: Share Rules, Extend Carefully
A derived class should be a meaningful specialization of its base. Learn to initialize shared state once, reuse guarded behavior and add focused capabilities without duplicating or bypassing the base rules.
What you will understand
Explain a genuine is-a relationship between base and derived classes.
Declare a derived class with a single direct base class.
Reuse inherited operations while respecting private base state.
Forward required initialization with : base(...).
Call an accessible base implementation without claiming an override.
Trace upcasting and the compile-time base contract.
Choose composition for a has-a helper relationship.
Repair hidden duplicate state and verify both shared and specialized rules.
Choose inheritance for a genuine specialization
Inheritance declares that one class is a specialized kind of another. InventoryItem provides a name, stock and a guarded sale operation. BookItem is an InventoryItem under this exercise’s contract, with additional author information. The colon in class BookItem : InventoryItem states that relationship.
Ask whether code expecting the base contract can sensibly work with the derived object. A book item still has valid nonnegative inventory and follows the same sale rule. If a proposed subtype must refuse all ordinary base operations or reinterpret their meaning, reconsider whether that base relationship fits.
Do not choose inheritance only because two classes happen to print similar text. Reusing a helper can be modeled with composition instead. We will compare both approaches after tracing a small, explicit base-and-derived model. Each editor is a separate complete program with its own Program and Main.
C# · .NET 10 COMPILER
Program.cs · A specialized item reuses the shared item contract
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3,"Mina Noor");
Console.WriteLine(book.Describe());
Console.WriteLine(book.Author);}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
River Maps: 3 in stock
Mina Noor
The colon declares that BookItem derives from InventoryItem. The book has inherited Name, Stock and public operations, plus its own Author. This is one BookItem object with base and derived state, not two separately created inventory objects.
Keep common state and rules in the base class
A derived class can reuse accessible base members without redeclaring their implementations. BookItem uses inherited Name, Stock, Describe and TrySell. The base defines the quantity check and stock update once, while the derived class adds information that only book items need.
The base part and derived part belong to one object. They are not two separately created items linked by a variable. Creating two BookItem instances still gives each object its own instance stock. The class relationship shares definitions, not one shared inventory count.
Inheritance does not remove encapsulation. Stock has a public getter and private setter declared in InventoryItem. BookItem can read the count but cannot assign through that private setter. Keep the rule behind the base operation rather than making storage protected merely for convenience.
C# · .NET 10 COMPILER
Program.cs · Use an inherited guarded operation without rewriting it
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3,"Mina Noor");
Console.WriteLine(book.TrySell(2));
Console.WriteLine(book.Describe());
Console.WriteLine(book.TrySell(2));
Console.WriteLine(book.Stock);}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
True
River Maps: 1 in stock
False
1
BookItem does not redeclare TrySell. The base implementation updates the inherited stock of this book and refuses an oversell. Keeping the rule in one place avoids a second version that might forget quantity checks.
C# · .NET 10 COMPILER
Program.cs · A derived class cannot bypass a private base setter
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3,"Mina Noor");}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();
Stock =-1;}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Compilation fails: the Stock set accessor is inaccessible.
The public getter remains usable, but the setter belongs privately to InventoryItem. Deriving from a class does not turn all its members into writable protected state. Use the base class’s guarded operations instead of weakening the setter to remove the diagnostic.
Initialize the base contract with : base(...)
Constructors are not inherited. BookItem declares its own constructor, accepts the shared and specialized inputs it needs, and forwards name and stock using : base(name, stock). The base constructor establishes its state before the derived constructor body validates and stores Author.
If no constructor initializer is written, C# attempts an implicit call to the accessible parameterless base constructor. That call cannot work when InventoryItem only declares a required name-and-stock constructor. The intentional compiler example shows the missing input contract rather than a runner failure.
: base(...) initializes the base part of the same instance. It does not create another item and it is not a method-body call. The logging example illustrates base constructor body before derived constructor body only; field initialization and inheritance have additional ordering details. Our normal constructors avoid calling overridable work while an object is still being initialized.
C# · .NET 10 COMPILER
Program.cs · The base constructor body runs before the derived body
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps");}}classInventoryItem{publicstring Name {get;}publicInventoryItem(string name){
Name = name;
Console.WriteLine($"Base body: {Name}");}}classBookItem:InventoryItem{publicBookItem(string name):base(name){
Console.WriteLine($"Derived body: {Name}");}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Base body: River Maps
Derived body: River Maps
: base(name) supplies the base constructor input for this same instance. Its body completes before the BookItem constructor body. The logging isolates constructor-body order; it is not a full trace of all field-initializer rules. The main inventory constructors avoid presentation side effects.
C# · .NET 10 COMPILER
Program.cs · A required base constructor must receive its inputs
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("Mina Noor");}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string author){
Author = author;}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Compilation fails: no argument was supplied for the required base constructor parameter name.
Without an explicit constructor initializer, this constructor attempts an implicit base() call. InventoryItem has no parameterless constructor. Constructors are not inherited: declare useful BookItem inputs and forward name and stock with : base(name, stock).
Extend behavior without hiding the base contract
DescribeBook is a new, clearly named operation that uses base.Describe() and appends Author. base selects an accessible base implementation on this object. The base formatter still owns the common stock report, so the derived report does not repeat that formula.
This lesson intentionally avoids virtual and override. A new method with another name is not an override, and declaring a member with the same name does not automatically create runtime polymorphism. The next lesson studies those dispatch rules directly.
C# allows one direct base class for a class. Further inheritance can form a chain, and a class can implement interfaces, covered later. A sealed class prevents further derivation when extension is not part of its contract. Avoid building deep class hierarchies before a real responsibility requires them.
C# · .NET 10 COMPILER
Program.cs · Extend a report by calling the base implementation
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3,"Mina Noor");
Console.WriteLine(book.DescribeBook());}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
River Maps: 3 in stock | Author: Mina Noor
DescribeBook adds book-specific information while base.Describe() formats the shared fields. This is an explicitly named extra method, not an override. base refers to the base implementation on the same object, not a separate parent object stored somewhere else.
A base reference changes the visible contract, not the object
A BookItem reference can be assigned to an InventoryItem variable because the derived instance satisfies the base relationship. This implicit upcast copies the reference value. It does not clone the object, discard Author or allocate another instance.
The variable’s compile-time type determines which members are available at the call site. An InventoryItem variable permits the base stock operation but does not expose Author. The object can remain a BookItem while a particular reference is used through the narrower base contract.
Use object.ReferenceEquals to confirm shared identity in the example. A sale through item updates the same stock observed through book. Do not force every caller to cast down to a subtype; base consumers should normally rely on the base contract they requested. Runtime virtual dispatch is a separate topic for the next lesson.
C# · .NET 10 COMPILER
Program.cs · A base reference can identify the same derived object
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3,"Mina Noor");InventoryItem item = book;
item.TrySell(1);
Console.WriteLine(book.Stock);
Console.WriteLine(object.ReferenceEquals(book, item));}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
2
True
Assigning the BookItem reference to InventoryItem is an implicit upcast. It does not clone or remove derived state. Both references reach the same object, so the inherited sale is visible through book too. A base-typed variable exposes the base contract at compile time.
C# · .NET 10 COMPILER
Program.cs · A base-typed variable does not expose every derived member
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){InventoryItem item =newBookItem("River Maps",3,"Mina Noor");
Console.WriteLine(item.Author);}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Compilation fails: InventoryItem does not contain a definition for Author.
The object is a BookItem, but the expression item has compile-time type InventoryItem. Author is not part of that base contract. Keep a BookItem reference where book-specific work is actually needed; do not add Author to every inventory item merely to satisfy this call.
Use has-a relationships for helper collaboration
A StockReport has a LabelPrinter that formats its text. It is not itself a specialized LabelPrinter. Store the helper as a field and delegate to it when work is needed. This composition relationship shares behavior without claiming that a report can stand in for a printer.
Inheritance couples a derived implementation to the base contract and its extension points. Composition makes the collaboration explicit through an object reference. Neither technique is universally better; choose based on the relationship and how the responsibilities should change independently.
The small composition example uses a concrete helper to keep the syntax familiar. Later interfaces can make such dependencies more flexible. For now, write the relationship in plain language first: a book item is an inventory item; a report uses a formatting helper. That distinction is more useful than inheriting simply to avoid a few repeated lines.
C# · .NET 10 COMPILER
Program.cs · A helper relationship can use composition instead
Predict the result, edit Program.cs, then run your program.
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Pen: 5 units
StockReport has a LabelPrinter helper; it is not a specialized LabelPrinter. Storing the helper in a field and delegating formatting is composition. This small example assumes a non-null helper and fixed valid report inputs. Composition can share work without asserting a misleading is-a relationship.
Test the base invariant and the derived additions
Test shared behavior through the derived instance: normal sale, exact remaining quantity and refused nonpositive or excessive requests. Then test the derived additions: trimmed author input, missing-author rejection and the specialized report. A correct author line cannot compensate for broken shared inventory state.
The repair demonstrates a hidden Stock property that stores another count. The base sale changes one value while the derived view reads another. Remove duplicate shared state instead of trying to synchronize two counts manually. The new hiding modifier is not a shortcut for overriding.
Also test constructor failures in the correct layer: invalid name or stock is rejected by the base constructor; invalid author is rejected by the derived constructor. These single-process examples do not persist data or coordinate simultaneous purchases. Their purpose is a coherent type relationship with one shared rule and clear specialized responsibility.
Guided repair: remove duplicate inherited state
After selling two of three books, book.Stock says three but the base report says one. The derived class hides Stock with a second stored count. Restore one authoritative count in the base class.
C# · .NET 10 COMPILER
Program.cs · A duplicate derived stock creates two conflicting truths
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3);
book.TrySell(2);
Console.WriteLine($"Derived view: {book.Stock}");
Console.WriteLine($"Base report: {book.Describe()}");}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicnewint Stock {get;}publicBookItem(string name,int stock):base(name, stock){
Stock = stock;}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Derived view: 3
Base report: River Maps: 1 in stock
The new modifier explicitly hides the inherited property with another one. TrySell updates base Stock, while the derived view reads the duplicate stored count. This is not overriding. Duplicating state to avoid the base boundary produces inconsistent reports.
Repair checkpoints
Find public new int Stock in BookItem.
Remove that duplicate property and its constructor assignment.
Keep : base(name, stock) to initialize the shared count.
Use inherited TrySell and Stock without redeclaring their storage.
Verify both the derived view and base report show one remaining unit.
Reveal the repair and explanation
C# · .NET 10 COMPILER
Program.cs · Keep the shared state in the base class only
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem("River Maps",3);
book.TrySell(2);
Console.WriteLine($"Derived view: {book.Stock}");
Console.WriteLine($"Base report: {book.Describe()}");}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicBookItem(string name,int stock):base(name, stock){}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Derived view: 1
Base report: River Maps: 1 in stock
Removing the hidden duplicate property leaves one stock value governed by InventoryItem. Both reads now agree. BookItem can add genuinely book-specific members without redeclaring the base state.
Independent lab: a specialized book-item report
Complete the specialized report for a validated BookItem while preserving inherited inventory behavior. Keep each rule in the layer responsible for it.
Acceptance checklist
Keep name validation, stock storage and TrySell in InventoryItem.
Keep BookItem derived from InventoryItem with one Author property.
Forward name and stock through : base(name, stock).
Preserve rejection of blank author and trimming of valid author input.
Make DescribeBook call base.Describe() and append | Author: followed by Author.
Do not hide Stock, duplicate the sale formula or introduce override before the next lesson.
Preserve the reference sale results and final report with one remaining unit.
C# · .NET 10 COMPILER
Program.cs · Specialized inventory report starter
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem(" River Maps ",3," Mina Noor ");
Console.WriteLine($"Sale: {book.TrySell(2)}");
Console.WriteLine($"Oversell: {book.TrySell(2)}");
Console.WriteLine(book.DescribeBook());}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){returnbase.Describe();}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Sale: True
Oversell: False
River Maps: 1 in stock
The constructor forwarding, validation and inherited sale rule already work. The specialized report omits Author. Complete the report without copying the stock rule, hiding members or adding another stock field.
Show hints
The base report already contains the normalized name and current stock.
Use string interpolation to combine base.Describe() and Author.
The constructor already validates both the shared and specialized inputs.
Predict the result, edit Program.cs, then run your program.
usingSystem;classProgram{staticvoidMain(){BookItem book =newBookItem(" River Maps ",3," Mina Noor ");
Console.WriteLine($"Sale: {book.TrySell(2)}");
Console.WriteLine($"Oversell: {book.TrySell(2)}");
Console.WriteLine(book.DescribeBook());}}classInventoryItem{publicstring Name {get;}publicint Stock {get;privateset;}publicInventoryItem(string name,int stock){if(string.IsNullOrWhiteSpace(name)){thrownewArgumentException("A name is required.",nameof(name));}if(stock <0){thrownewArgumentOutOfRangeException(nameof(stock));}
Name = name.Trim();
Stock = stock;}publicboolTrySell(int quantity){if(quantity <=0|| quantity > Stock){returnfalse;}
Stock -= quantity;returntrue;}publicstringDescribe(){return$"{Name}: {Stock} in stock";}}classBookItem:InventoryItem{publicstring Author {get;}publicBookItem(string name,int stock,string author):base(name, stock){if(string.IsNullOrWhiteSpace(author)){thrownewArgumentException("An author is required.",nameof(author));}
Author = author.Trim();}publicstringDescribeBook(){return$"{base.Describe()} | Author: {Author}";}}
Standard input · optional
TERMINAL OUTPUT
Select Run code to compile and execute this program.
Expected output · trace the object and its state
Sale: True
Oversell: False
River Maps: 1 in stock | Author: Mina Noor
The base owns name, stock and the sale invariant; BookItem owns author and the specialized report. Both constructor inputs are trimmed in their appropriate layer. The report calls the shared base formatter and adds Author. This is an in-memory model, not a saved catalog or payment flow.
Changed-input checks
Selling exactly three from a three-unit book succeeds and leaves zero.
Zero, negative and excessive quantities are refused without stock changes.
A base reference and derived reference to the same book observe the same count.
Blank name, negative stock and blank author are rejected with the relevant parameter name.
Whitespace around name and author is trimmed in the final report.
A second BookItem starts with independent stock.
Recap: relationship, initialization and contract
Keep shared rules in the base, specialized data in the derived type and helper relationships explicit.
class BookItem : InventoryItem
Declare a specialization of the base inventory contract.
: base(name, stock)
Initialize the base part of the same object with required inputs.
inherited TrySell
Reuse the shared quantity guard and stock transition.
private base setter
Preserve the base state boundary even inside a derived class.
base.Describe()
Select the accessible base formatter on the current object.
InventoryItem item = book
Use the same object through the narrower base contract.
new member hiding
Can obscure a base member; it is not overriding.
composition
Store or receive a helper for a has-a or uses-a relationship.
Frequently asked questions
Does a derived object contain a separate base object I must create?
No. Base and derived instance state belong to the one object initialized by the constructor chain.
Are constructors inherited?
No. A derived class declares its own constructors and calls an accessible base constructor explicitly or through an implicit parameterless call.
Can a derived class write a private base setter?
No. In this model it can read Stock and use inherited guarded operations, but cannot assign through the private setter.
Does assigning a book to an InventoryItem variable remove its author?
No. It copies a reference to the same object. The base-typed expression simply exposes the base contract at compile time.
Is base.Describe() an override?
No. It explicitly calls the base implementation. Overriding requires a virtual or abstract member and an override declaration, covered next.
When should I use composition?
When one responsibility uses or contains a helper rather than being a true specialization of it. The report has a printer helper; it is not a printer subtype.
KNOWLEDGE CHECK
Can you trace the base and derived contract?
Trace constructor forwarding, shared state, reference identity and the difference between specialization and composition. Read the explanation after checking your answers.
Next step and references
Inheritance models a specialization with one shared contract. Initialize the base part explicitly, preserve its state boundary and distinguish the reference’s visible contract from the object’s identity. Use composition for helper relationships.
Next is C# Polymorphism and Overriding. You will follow virtual and override behavior through base references and distinguish it from overload selection and member hiding.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.