Inheritance in PowerShell Classes
- Details
- Written by June Blender
- Last Updated: 09 October 2019
- Created: 16 March 2016
- Hits: 16486
Applies to: PowerShell 5.0.10586.122
If you’re learning about classes in Windows PowerShell 5.0, one of the first new concepts that you’ll encounter is inheritance. When you create a class that is based on another class, your new subclass or child class automatically gets the inheritable members (e.g. properties and methods) of the parent class or base class.
Inheritance is a really powerful and useful concept, so it’s important that you understand it. Fortunately, it’s pretty easy. Also, the inheritance principles that you learn in PowerShell are also used in other programming languages, so learning them in PowerShell gives you a head start on new languages.
Subclasses of System.Object
All PowerShell classes are subclasses of the System.Object class. So, they inherit the inheritable methods of the System.Object class.
Create an empty class.
PS C:\> class AnyClass {}
Look at its type. The BaseType property shows the immediate parent class.
PS C:\ > [AnyClass] IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False AnyClass System.Object
And, when you create an instance of the AnyClass class and pass it to Get-Member, you can see that there are four methods of the AnyClass class that you didn’t define. These methods are inherited from the System.Object base class.
PS C:\> $anything = New-Object -TypeName AnyClass PS C:\> $anything | Get-Member TypeName: AnyClass Name MemberType Definition ---- ---------- ---------- Equals Method bool Equals(System.Object obj) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString()
You can call these methods on the instance of the AnyClass class. They work as though you wrote them in the AnyClass class.
PS C:\> $anything.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False Anyclass System.Object PS C:\> $anything.ToString() Anyclass
NOTE: All PowerShell classes are automatically subclasses of System.Object. You do not need to specify System.Object as the base class.
Create a subclass
Creating a subclass is easy.
Let’s create a base class and a subclass. Here’s the Glass class. The full code is in Glass.ps1 on GitHub. If you’re testing this code, be sure to dot-source the file so that the class is added to the session.
class Glass { # Properties [int]$Size [int]$CurrentAmount # Constructors Glass ([int]$Size, [int]$Amount) { $this.Size = $Size $this.CurrentAmount = $Amount } # Methods + [Boolean] Fill ([int]$volume) {} + [Boolean] Drink ([int]$amount) {} } |
The syntax for a subclass is:
class subclass : base class { ... } |
PowerShell classes can inherit from only one base class, so you can’t specify more than one class name. (The class can implement multiple interfaces, but we’ll save that for another blog post.)
To start with the simplest case, let’s create an empty subclass based on the Glass class. We’ll call it AnyGlass.
PS C:\> class AnyGlass : Glass {}
First, make sure that the class was created and that the base type is Glass.
PS C:\> [AnyGlass] IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False AnyGlass Glass
Now, let’s create an AnyGlass object. There are several different ways to create an object (“instantiate”) based on a class, but we’ll use New-Object here, because it’s so PowerShell.
PS C:\> $anyGlass = New-Object -TypeName AnyGlass PS C:\> $anyGlass | Get-Member TypeName: AnyGlass Name MemberType Definition ---- ---------- ---------- Drink Method bool Drink(int amount) Equals Method bool Equals(System.Object obj) Fill Method bool Fill(int volume) GetHashCode Method int GetHashCode() GetType Method type GetType() ToString Method string ToString() CurrentAmount Property int CurrentAmount {get;set;} Size Property int Size {get;set;}
Notice that the AnyGlass object has the properties defined in the Glass class (Size, CurrentAmount), the methods defined in the Glass class (Drink, Fill), and the methods that the Glass class inherited from its base class, System.Object (Equals, GetHashCode, GetType, ToString).
So, the AnyGlass class inherits from its parent and grandparent classes — its entire ancestry — just like people do.
Add Members to a Subclass
A subclass can have properties and methods (and all types of members) that are not in the base class. For example, the Glass class has properties that are not in System.Object.
I’ll add a Name property to the AnyGlass class.
PS C:\> class AnyGlass : Glass { [String]$Name } PS C:\> $anyGlass = New-Object AnyGlass PS C:\> $anyGlass Name Size CurrentAmount ---- ---- ------------- 0 0
You use inherited members in exactly the same way as you use members that are defined in the current class. In fact, unless you do some quick discovery, you really don’t know if a property or method is inherited or defined.
PS C:\> $anyGlass.Name = 'AnyGlass' PS C:\> $anyGlass.Size = 6 PS C:\> $anyGlass Name Size CurrentAmount ---- ---- ------------- AnyGlass 6 0
Also, inside the class, when you use $this to refer to the current object, you treat defined properties and inherited properties in the same way.
class AnyGlass : Glass { [String]$Name AnyGlass () { $this.Size = 6 $this.CurrentAmount = 0 $this.Name = "My glass" } } |
Overloading Methods in the Base Class
You can have members that have the same name as a member in the base class, but take different parameters. This is called an overloaded method. In a subclass, it works the same way that it would if you had a method defined twice in the same class.
For example, the Fill method in the Glass class takes one integer argument that represents the volume added to the Glass.
+ [Boolean] Fill ([int]$volume) |
The Fill method in the AnyGlass takes the same volume integer argument and a string argument that represents a warning that it returns when you’ve overfilled the glass. (The Fill method in the base class has a predetermined warning.)
class AnyGlass : Glass { # Methods [void] Fill ([int]$volume, [string]$Warning) { if ($this.currentAmount + $volume -le $this.Size) { $this.CurrentAmount += $volume } else { Write-Warning $Warning } } } |
When I pipe $AnyGlass to Get-Member and specify the Fill method, it tells me it has two overloads. It doesn’t mention (and doesn’t really care) which one is defined locally and which one is inherited.
PS C:\> $anyGlass | Get-Member -MemberType Method -Name Fill TypeName: AnyGlass Name MemberType Definition ---- ---------- ---------- Fill Method void Fill(int volume, string Warning), bool Fill(int volume)
When I call the Fill methods, they works exactly as they would if both Fill methods were defined in the same class. That is, the argument number and types determine which Fill method is called. I don’t need to do anything to call the Fill method of the Glass class, other than to provide only an integer.
As a reminder, here’s the signature of each of the Fill methods:
# In Glass + [Boolean] Fill ([int]$volume) # In AnyGlass + [void] Fill ([int]$volume, [string]$Warning) |
If I call the Fill method with 2 arguments, an integer and a string, it calls the Fill method of the AnyGlass class, which when full, returns the warning string I specify.
PS C:\> $anyGlass.Fill(3, "Oops! Too much") WARNING: Oops! Too much
If I call the Fill method with 1 argument, an integer, it calls the Fill method of the Glass class, which has a predefined warning message that includes the room left in the glass. It also returns a Boolean value, $False, which indicates that the AnyGlass was not filled. Notice that I didn’t need to specify anything to get the inherited Glass method, because the AnyGlass class has both methods.
PS C:\> $anyGlass.Fill(3) WARNING: Sorry. The glass isn't big enough. You have room for 2. False
Overriding Methods of the Base Class
To hide a method in the base class, write a method that has the same arguments as the method in the parent class. That’s called overriding a method. When methods have the same name and take arguments of the same type, the method in the subclass takes precedence, hides, or overrides the method in the parent class.
You can do the same thing with Properties.
Here’s the AnyGlass class with a Fill method that takes just one argument, an integer, just like the Fill method in the Glass class.
class AnyGlass : Glass { # Methods [void] Fill ([int]$volume) { if ($this.currentAmount + $volume -le $this.Size) { $this.CurrentAmount += $volume } else { Write-Warning "Oops! Not enough room" } } } |
Notice that the signatures of the Fill methods are not identical. The Fill method of the Glass class returns a Boolean. The Fill method of the AnyGlass class returns nothing ([void]).
# In Glass + [Boolean] Fill ([int]$volume) # In AnyGlass + [void] Fill ([int]$volume) |
But, when I create an AnyGlass object and pipe it to Get-Method, it returns only one Fill method signature, instead of two. And, because the return type is Void, we know that it’s returning the Fill method of the AnyGlass class, which overrides the Fill method of the Glass class.
PS C:\> $anyGlass | Get-Member -MemberType Method -Name Fill TypeName: AnyGlass Name MemberType Definition ---- ---------- ---------- Fill Method void Fill(int volume)
When I call the Fill method with a volume, it calls the local Fill method.
PS C:\> $anyGlass.Fill(1) PS C:\> $anyGlass.Fill(1) PS C:\> $anyGlass.Fill(1) WARNING: Oops! Not enough room. PS C:\>
When a subclass overrides a base class, does it completely hide the parent class? Happily, no!
Calling a member of a base class
Even when a base class member is hidden by a subclass member, you can still call the base class member on an instance of the subclass.
Here’s the syntax:
([baseClass]$instance).member |
Essentially, you cast the object to the base class, enclose the casted object in parentheses to make sure it’s evaluated first, and then call the member in the usual way. For example, to call the Fill method of the Glass class on an AnyGlass object:
PS C:\> ([Glass]$anyGlass).Fill(4) WARNING: Sorry. The glass isn't big enough. You have room for 2. False
In the script for a class, you can use the same syntax to modify the $this variable that refers to the current object.
For example, in my WineGlass class, the Fill method of the WineGlass class calls the Fill method of the Glass class, which updates the CurrentAmount property. Then, the Fill method of the WineGlass class updates a local property, TotalPoured.
[void] Fill ([int]$volume) { if (([Glass]$this).Fill($volume)) { $this.TotalPoured += $volume } } |
You can also call all the way back to our common ancestor, System.Object. For example, if I add a ToString method to the AnyGlass or Glass class, I can call it. Or, I can call the ToString method on System.Object.
PS C:\> $anyGlass = [AnyGlass]::New() PS C:\> $anyGlass.Size = 6 PS C:\> $anyGlass.CurrentAmount = 4 PS C:\> $anyGlass.ToString() A 6-ounce glass with 4 ounces. PS C:\> ([System.Object]$anyGlass).ToString() AnyGlass
Constructors are not inherited
When I talked about inheritance, I was careful not to say that “all” members, or all properties and methods, are inherited. Constructors, which are really just special methods, are not inherited. Instead, like all PowerShell classes, the new class is created with a default (parameter-less) constructor.
(Need help with constructors? See: Why do we need constructors?)
To find the constructors of a class, use the New static property that PowerShell adds to all classes. (Be sure to use the property, with no parentheses, not the method, with parentheses, which creates a new object).
This command shows that the Glass class has two constructors; a default constructor and a constructor that takes two integers; the first for the size and the second for the amount.
PS C:\> [Glass]::New OverloadDefinitions ------------------- Glass new() Glass new(int Size, int Amount)
But the AnyGlass class just has the default constructor that is added automatically to all PowerShell classes.
PS C:\> [AnyGlass]::New OverloadDefinitions ------------------- AnyGlass new()
You can add constructors to the subclass as you would to any class. Remember, that when you add a constructor to the class, it replaces the automatic default constructor. So, if you want a default constructor on your class, you need to add it explicitly.
Calling a base class constructor
The last piece of the inheritance puzzle is calling the constructor of the base class.
(Need help with constructors? See: Why do we need constructors?)
Here’s the scenario. The base class defines a constructor that takes property values as arguments and assigns each value to the correct property. The Glass class is intentionally simple, but it can get complex when the class defines many properties, or the arguments are used to calculate property values.
Glass ([int]$Size, [int]$Amount) { $this.Size = $Size $this.CurrentAmount = $Amount } |
In the subclass, you could easily define a corresponding constructor, such as:
AnyGlass ([int]$Size, [int]$Amount) { $this.Size = $Size $this.CurrentAmount = $Amount } |
But, you’re maintaining virtually the same code twice. Instead, you can call the base class constructor from the subclass.
The syntax, which uses the base keyword, is:
Subclass (arguments) : base (argumentsToBaseClassConstructor) { ... } |
For example, this AnyGlass constructor calls the Glass class constructor. In this case, there’s no logic in the constructor (inside the script block), but it’s permitted if you need it.
AnyGlass ([int]$Size, [int]$Amount) : base($Size, $Amount) { } |
The arguments and their types can be different, just so the call to the base class constructor provides the arguments it requires. For example, this AnyGlass constructor creates a full AnyGlass (Size -eq CurrentAmount) every time.
AnyGlass ([int]$Amount) : base($Amount, $Amount) { } |
However, when creating an instance of a subclass, you cannot call the constructor of a base class. It’s explicitly prohibited, because it’s error prone.
You can’t inherit from that (it’s sealed)
Now, a few limits to all of this power.
You can’t create a subclass based on any class; only classes that are not sealed. The sealed keyword in C# creates a class that you cannot use as a base class.
Programmers use sealed when they want to protect the interface from development that they cannot control. They don’t want to be responsible for someone’s bad implementation or any side effects it might have. They’d rather weather a bit of anger from a stymied programmer than a pile of bugs in code they don’t own. (Many thanks to Doug Finke, Kevin Ilsen, Adam Driscoll, Lee Holmes, Rodney Stewart, Eric Slesar, and David J. Brown for their insights on this topic.)
As an aside, the opposite of sealed, well sort of, is abstract, which indicates that a class is designed only to be a base class for other subclasses. You can’t create an instance of an abstract class (e.g. New-Object or [<Class>}::New() ), but you can create subclasses.
Most .NET classes are sealed, but the PowerShell classes that you create are not sealed (and the sealed keyword is not valid), so you can use any PowerShell class as a base class. And, you can use .NET classes that are not sealed, such as KeyedCollection, which is an abstract base class.
You can’t inherit from that either (no default constructor)
In Windows PowerShell 5.0.10586.122 classes, you cannot inherit from a class that does not have a default constructor (a constructor with no parameters), unless you explicitly define a default constructor in the subclass.
According to Jason Shirk on the PowerShell Team, it’s a bug, but it’s not yet fixed.
When it’s fixed, you’ll be able to base a class on a class that has no default constructor, but the derived class constructor must call a constructor in the base class, just like in C#.
That just about wraps it up. Thanks to Jason Shirk, Sergei Vorobev, and many others on Twitter and the PowerShell Facebook group for their help with this post.
June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at This email address is being protected from spambots. You need JavaScript enabled to view it. or follow her on Twitter at @juneb_get_help.
For licensed customers, use the forum associated with your product in our Product Support Forums for Registered Customers.
For users of trial versions, please post in our Former and Future Customers - Questions forum.