この記事は、PowerShell Advent Calendar 2017の14日目の記事です。
PowerShellのコンストラクタについてまとめました。
動作環境
Windows 10
PowerShell 5.1
Visual Studio Code (拡張機能として ms-vscode.powershell を使用)
$PSVersionTable
Name Value
---- -----
PSVersion 5.1.15063.726
PSEdition Desktop
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 10.0.15063.726
CLRVersion 4.0.30319.42000
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
コンストラクタの役割
この記事はあくまで文法について焦点を当てた記事なので、簡潔に述べます。
1. 初期化と不可分一体の処理を定義する
→ インスタンス生成する上で前提となる処理を実装する。
2. インスタンスにメモリを割り当てる
→ 基本、PowerShellプログラマーは気にする必要はない。
基本的な定義方法
まずは、基本となる使い方から記載しておきます。
宣言
他のプログラム言語と同じように書けます。
class Employee {
[int] $Id
[String] $Name
[String] $Department
Employee([int] $Id, [String] $Name) {
$this.Id = $Id
$this.Name = $Name
$this.Department = "未配属"
}
[String] ToString() {
return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
}
}
呼び出し
コンストラクタを呼び出す方法は2通り存在します。
基本は前者を使用し、動的に生成するクラス名を変更したい場合だけ後者を用いる感じでいいと思います。
[クラス名]::new()
new [クラス名] でないことに注意してください。
$emp = [Employee]::new(1, "Smith")
$emp.ToString()
# Id=1, Name=Smith, Department=未配属
New-Objectコマンドレット
-TypeName は省略可能です。
$className = "Employee"
$emp = New-Object -TypeName $className -ArgumentList 1, "Smith"
$emp.ToString()
# Id=1, Name=Smith, Department=未配属
デフォルトコンストラクタ
引数なしのコンストラクタを定義することができます。
特に処理を書く必要がない場合は、宣言する必要はありません。
class Employee {
[int] $Id
[String] $Name
[String] $Department
Employee() {
$this.Id = -1
$this.Name = "不明"
$this.Department = "未配属"
}
[String] ToString() {
return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
}
}
> [Employee]::new().ToString()
# Id=-1, Name=不明, Department=未配属
オーバーロード
コンストラクタをオーバーロードで定義することはできますが、
他のオーバーロードをthisで呼ぶことはできません。
Class-definition syntax should support calling the same class's other constructors #3820
PowerShell is not C# and it was not a goal to replicate all of C#'s capabilities.
For this specific area - we added the : base() syntax because some syntax is required to call a base class constructor.
We did not add : this() syntax because there is a reasonable alternative that is also somewhat more intuitive syntax wise - introduce a shared initialization method and just call it,
上のIssueの要点をまとめると、PowerShell開発者の一人であるShirk氏曰く、
親クラスのコンストラクタ呼び出しをサポートしたのは文法上必要であったため。
オーバーロードの場合、初期化する際の共通処理をメソッドで定義しておけば、
可読性をそこまで落とすことなく代用できるため意図してサポートしていない、とのことです
提示されているサンプルコードでは hidden Init()
と定義し、
そこに各コンストラクタ共通の処理を実装していけばいいと書いているので、
慣例として使用する程度の心づもりでいいのではと思います。
class Employee {
[int] $Id
[String] $Name
[String] $Department
# ------ Constructors ------------------
Employee([int] $Id, [String] $Name, [String] $Department) {
$this.Init($Id, $Name, $Department)
}
Employee([int] $Id, [String] $Name) {
$this.Init($Id, $Name)
}
Employee() {
$this.Init()
}
# ------ Initializing for constructors ------------------
hidden Init([int] $Id, [String] $Name, [String] $Department) {
$this.Id = $Id
$this.Name = $Name
$this.Department = $Department
}
hidden Init([int] $Id, [String] $Name) {
$this.Init($Id, $Name, "未配属")
}
hidden Init() {
$this.Init(-1, "不明")
}
[String] ToString() {
return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department
}
}
> [Employee]::new().ToString()
# Id=-1, Name=不明, Department=未配属
> [Employee]::new(2, "Allen").ToString()
# Id=2, Name=Allen, Department=未配属
> [Employee]::new(3, "Ward", "朝読書推進部").ToString()
# Id=3, Name=Ward, Department=朝読書推進部
デストラクタ(不可能)
結構ググりましたが、どうやらないみたいです。
ただ、断言している情報源がなかったので、
この記事内ではドキュメント内で言及されていない、としておきます。
継承
PowerShellは継承もサポートしていますが、
同じファイル内に親クラスと子クラスの両方を定義しておく必要があります。
class SalesMan : Employee {
[String[]] $Clients
SalesMan([int] $Id, [String] $Name, [String] $Department, [String[]] $Clients) : base($Id, $Name, $Department) {
$this.Clients = $Clients
}
[String] ToString() {
return "Id=" + $this.Id + ", Name=" + $this.Name + ", Department=" + $this.Department + ", Clients=" + $this.Clients
}
}
> [SalesMan]::new(4, "Martin", "シエスタ推進部", @("Miller", "Ford", "James")).ToString()
# Id=4, Name=Martin, Department=シエスタ推進部, Clients=Miller Ford James
ちなみに継承をしなくてもbaseキーワード自体は指定可能です。
これは全てのクラスは基本クラスであるSystem.Objectを継承するように動作するためです。
なので、正確に言うと、継承をしてないクラスなんてない、ということです。
System.Objectにはデフォルトコンストラクタが定義されているので、以下のように書けます。
(書いたところで意味はないです。)
class Employee {
[int] $Id
[String] $Name
Emploee() : base() {
$this.Id = -1
$this.Name = "不明"
}
}
外部ファイル(不可能)
サブクラスを定義する場合、親クラスと同じファイル内に書く必要があることから、
親クラスのコンストラクタの呼び出しも外部ファイルからはできません。
コンストラクタというよりかは、クラス構文上の制約です。
ちなみに、以下検証結果。
. .\Employee.class.ps1
class Clerk : Employee {
Clerk([int] $Id, [String] $Name): base($Id, $Name) {
}
}
> [Clerk]::new(5, "Blake")
: + class Clerk : Employee {
: + ~~~~~~
: 型 [Employee] が見つかりません。
初期化される順番
PowerShellでインスタンス生成時に初期化される順番はいたって他のオブジェクト指向言語と
同じ順番となります。
[int] $Script:count = 0
class Parent {
[int] $second = ++$Script:count
[int] $third
Parent() {
$this.third = ++$Script:count
}
}
class Child : Parent {
[int] $first = ++$Script:count
[int] $forth
Child() : base() {
$this.forth = ++$Script:count
}
}
$child = [Child]::new()
$child.first # 1
$child.second # 2
$child.third # 3
$child.forth # 4
デザインパターン
コンストラクタに限った話題ではないですが、いくつかまとめておきます。
抽象クラス
インスタンス生成することを禁止し、継承したサブクラスでインスタンス生成させるようにしたい場合、
以下のようなやり方があります。
ただ、実際に実行してみないとエラーにならないため、使い勝手は微妙です。
class Foo {
Foo () {
if ([Foo] -eq $this.GetType()) {
throw("Class [Foo] must be inherited")
}
}
}
Singleton
同一プロセス内に生成されるインスタンス数を一つだけにする方法です。
他の実装方法もあるかと思いますので、細かいやり方は実装者のお好みで。
class Singleton {
static [Singleton] $Instance
[Object] $SingletonTarget
hidden Singleton() {
}
static [Singleton] GetInstance() {
if ([Singleton]::instance -eq $null) {
[Singleton]::instance = [Singleton]::new()
}
return [Singleton]::instance
}
}
参考資料
公式ドキュメント|about_Classes
Powershell v5 Classes & Concepts
Class-definition syntax should support calling the same class's other constructors #3820