Last Updated: May 22, 2026
The : BaseClass syntax makes one class inherit from another. Once a derived class exists, it needs a way to reach the pieces of itself that came from the parent: the constructor that initializes the parent's fields, the methods the parent already wrote, the properties the parent already exposes. The base keyword provides that access. This lesson covers what base refers to, the three places it's used (constructor calls, method calls, property access), and how it differs from this.
base Refers ToEvery object built from a derived class carries two things inside it: the data and behavior added by the derived class, and the data and behavior inherited from the base class. The base keyword refers to the second part from inside the first.
A small Product hierarchy:
When the program creates new Book("Clean Code", 29.99m, "Robert Martin"), the resulting object has a Name, a Price, and an Author. The first two came from Product. The third came from Book. The base(name, price) call in Book's constructor is what tells the Product part of the object how to initialize itself.
A picture of the object's shape:
base is not a separate object. The book is one object. base accesses the inherited slice of that one object specifically, without going through anything the derived class might have added on top.
That distinction matters more once method overriding enters the picture. For now, base is the keyword to use when accessing the parent's constructor, methods, or properties from inside derived code.
: base(...)Creating a Book requires initializing both halves of the object. The Product part needs a Name and a Price. The Book part needs an Author. The derived constructor is responsible for kicking off both initializations, and : base(...) is the syntax for handing the right values to the base constructor.
The order is fixed: the base constructor runs first, the derived constructor runs second. The order is structural: the Book constructor's body might want to read Name or Price, and those fields don't exist in any usable form until Product's constructor has assigned them. Initializing the base part first guarantees the derived part has a complete foundation to build on.
The flow as a diagram:
The : base(name, price) appears after the parameter list but before the opening brace. The arguments inside the parentheses can be parameters of the current constructor, literal values, or any expression that returns the right type.
A derived class doesn't have to use the same parameters one-to-one. The next example forwards name and price but supplies a literal for the category:
Every Book belongs to the "Books" category, so the derived constructor hard-codes that argument. Callers of new Book(...) never see the category parameter; the class itself supplies it. This pattern is common: a derived class often knows a few facts about itself that the base class doesn't, and : base(...) is where it injects them.
: base(...) Is RequiredIf the base class has a parameterless constructor (either declared explicitly or compiler-generated), : base(...) can be omitted from the derived constructor. The compiler inserts : base() automatically.
The Book constructor never wrote : base(), but Product's parameterless constructor still ran, which is why Name ended up as "Unnamed" (the field initializer fired). The compiler handles this automatically when the base offers a parameterless option.
Once the base class declares a constructor with parameters and does not declare a parameterless one, the free version disappears. : base(...) is no longer optional. Every derived constructor has to choose which base constructor to call and supply the arguments.
The fix is to call the existing base constructor explicitly:
The rule: a derived constructor must always chain to some base constructor. If the base offers a parameterless option, the compiler picks it silently. Otherwise, the derived constructor must pick one and write the call.
A small table for the four shapes that appear in code:
| Base has parameterless ctor? | Derived writes : base(...)? | Result |
|---|---|---|
| Yes | No | Compiler inserts : base() |
| Yes | Yes | The explicit call wins |
| No | No | Compile error CS7036 |
| No | Yes | The explicit call is required |
This is a compile-time check, so there's no runtime overhead. Forgetting : base(...) is a common error when adding inheritance to a class that previously had no children. Adding a parameterized constructor to a base class without keeping a parameterless one breaks every derived class at compile time until updated.
base.MethodName(...)The second use of base is calling a method that the base class already defined, from inside a derived method. The syntax is base.MethodName(args), and it bypasses any overriding the derived class might have done.
For now, with no overriding in the picture, calling a base method directly looks like this:
PrintFullInfo does what PrintBasicInfo does, plus one more line. Reusing PrintBasicInfo is the standard approach, which is what base.PrintBasicInfo() does. Writing this.PrintBasicInfo() or just PrintBasicInfo() would work too, since the method is inherited and accessible. So why use base.?
Two reasons. First, clarity: base.PrintBasicInfo() states explicitly that the call is reaching into the parent's behavior. Second, and more important: once a derived class can replace a base method with its own version, base.MethodName() becomes the only way to bypass the replacement and call the original. base. is the unambiguous way to specify "the parent's version, specifically."
A common pattern in code is "do what the parent does, then add more." It looks like this:
The derived method extends the base behavior rather than replacing it. The base call comes first because the "extra" work makes sense only after the basic save has happened. The order can also be reversed (extra work first, then the base call) or the base call placed in the middle. The position is a design choice; the keyword provides the access.
How a base.PrintBasicInfo() call resolves at runtime:
The call hops up to Product, runs PrintBasicInfo there, and comes back. It's a normal method call with the lookup starting at the base class instead of the derived class.
The same base. syntax works for properties and fields the base class defines. This pattern mainly appears when the base exposes a property that the derived class needs to read or modify as part of doing its own work.
base.Price reads the Price property defined on Product. In this particular example, plain Price (or this.Price) would do the same thing, because DiscountedBook hasn't hidden or overridden the property. The base. prefix is a readability choice here: it signals "reading the inherited price." Once a derived class hides or overrides a property, base.Price reaches past the hiding to get to the parent's value.
For fields, the same idea applies:
base.Category reads the field declared on Product. The field was already accessible without the prefix (since fields are inherited), but the prefix is explicit about where the field lives.
A practical reminder: base. can only reach things the base class allows the derived class to see. A private member on the base class is invisible to Book, regardless of the access spelling. The access modifier rules from the OOP section still apply here. protected members are designed for exactly this case: hidden from the outside world but visible to derived classes through base. or plain inherited access.
base vs thisBoth keywords refer to the current object, but they point at different views of it. this is the "full" object as it actually exists, including any derived overrides or additions. base is specifically the base-class slice of that same object, with derived overrides bypassed.
| Aspect | this | base |
|---|---|---|
| Refers to | The current object | The base-class portion of the current object |
| Calls | The most-derived version of a method (the one that ran when called from outside) | The version defined on the base class, bypassing overrides |
| Constructor chain | : this(...) calls another constructor of the same class | : base(...) calls a constructor of the base class |
| Valid where | Any instance member of any class | Instance members of a derived class only |
| Pass as argument | SomeMethod(this) passes the current object | Cannot pass base as an argument |
A side-by-side example makes the distinction concrete:
In this snapshot, with no overriding, this.Describe() and base.Describe() produce identical output. The _Method Overriding (virtual/override)_ lesson is where the difference becomes visible: once Book provides its own Describe method using override, this.Describe() runs the Book version and base.Describe() runs the Product version. The keywords do different work; the work happens to converge in this simpler case.
For the constructor side, the two keywords are not interchangeable at all:
: this(...) chains to another constructor of the same class.: base(...) chains to a constructor of the base class.A constructor can have one or the other, never both. With : this(...), control eventually has to reach a constructor that does call : base(...) (either explicitly or implicitly), since every constructor chain has to end at a base constructor.
The two-argument Book constructor chains to the three-argument Book constructor with a default price. The three-argument constructor chains to Product's constructor with : base(name, price). Every chain ends in a base constructor call, even when the derived class has its own internal chaining.
base Is Allowed (and Not)base is a keyword tied to the current object's inherited slice, so it only has meaning in specific places. Using it elsewhere produces compile errors that are hard to interpret without the rule.
The legal places:
: base(...) or as base.Field, base.Method(), etc. in the body).The illegal places:
static method, property, or constructor. There's no "current object," so base has nothing to refer to.Book, book.base.Save() from outside the class is invalid. base is a view that only exists from inside the class.EBook inherits from Book, and Book inherits from Product, then inside EBook the base keyword refers to Book, not to Product. base.base.SomeMethod() is not valid.The multi-level case made concrete:
EBook.Describe calls base.Describe, which is Book.Describe. That, in turn, calls its own base.Describe, which is Product.Describe. Each level only reaches one step up. To get the chain of three messages, each class explicitly calls its direct parent, and the parent does the same. A single call cannot skip a level.
The new keyword on each method is method hiding. The mechanics of base.Describe() are the same either way: bypass anything the current class added, look up the method starting at the direct parent.
Multi-level base calls have no special overhead, but stacking them in deep hierarchies can make a single call chain through many classes. That's fine if every layer adds value; otherwise, the inheritance tree is deeper than it should be.
The most common reason base shows up in code is the "do what the parent does, then add more" pattern. The derived class isn't trying to replace the parent's behavior, just to wrap it.
Consider a small e-commerce flow where every Order writes a basic log line, but GiftOrder needs to also log the recipient.
Three things are happening here:
: base(id, customer, total) to forward the values Order needs. Without that line, the derived constructor would have to assign Id, Customer, and Total itself, duplicating logic that already exists in the base.LogPlacedWithGift calls base.LogPlaced() first to get the standard log line, then adds the gift-specific line. The base class doesn't know anything about gifts, and the derived class doesn't reimplement the base log format. Each class owns the part it should own.LogPlaced (adds a timestamp, switches to JSON, etc.), GiftOrder automatically picks up the change. That's the payoff: the derived class reuses, rather than copies, the parent's work.This pattern is what base is for. Constructors and method calls are the two places it shows up, and the goal is the same: "the parent already knows how to do part of this. Reuse that part and add the specific work."
base.LogPlaced() is a normal method call. If the base method throws an exception, that exception propagates up through LogPlacedWithGift like any other call. There's no special handling. To fall back to local code when the base call fails, wrap the base. call in a try/catch as with any other method.
A small Subscription hierarchy that uses every form of base from this chapter:
Three uses of base show up here, one of each kind:
: base(customerEmail, monthlyPrice) in the constructor signature, forwarding the base class's required values.base.MonthlyPrice inside CalculateAnnualPrice, explicitly reading the inherited property.base.PrintBasicSummary() inside PrintFullSummary, calling the base method before adding more output.Compare this to what the code would look like with no base available. The derived class would have to redeclare CustomerEmail and MonthlyPrice (or reach them through some workaround), reimplement PrintBasicSummary's logic, and the relationship between the two classes would become a copy-paste arrangement instead of real reuse. base is what makes inheritance a shortcut instead of a constraint.
One note about the price arithmetic. The (15m / 100m) calculation produces a decimal, and multiplying that by another decimal produces a decimal. The number of fractional digits in the result reflects decimal's preservation of scale, which is why the output shows $101.898 rather than $101.90. Formatting the output with {value:C} would round to currency, but the underlying number stays at full precision. That detail is about decimal, not about base.
Calling the base method is one option a derived class has. To completely change a behavior the base provides, rather than just extending it, use method overriding.