about_Classes

簡単な説明

クラスを使用して独自のカスタム型を作成する方法について説明します。

詳細な説明

バージョン 5.0 以降、PowerShell には、クラスやその他のユーザー定義型を定義するための正式な構文があります。 クラスを追加することで、開発者や IT プロフェッショナルは、より広範なユース ケースに PowerShell を採用できます。

クラス宣言は、実行時にオブジェクトのインスタンスを作成するために使用されるブループリントです。 クラスを定義する場合、クラス名は型の名前です。 たとえば、Device という名前のクラスを宣言し、変数$devを Device の新しいインスタンスに初期化する場合、 $dev Deviceオブジェクトまたはインスタンスになります。 Device の各インスタンスは、そのプロパティに異なる値を持つことができます。

サポートされるシナリオ

  • クラス、プロパティ、メソッド、継承などのオブジェクト指向プログラミング セマンティクスを使用して、PowerShell でカスタム型を定義します。
  • PowerShell 言語を使用して、DSC リソースとそれに関連付けられている型を定義します。
  • 変数、パラメーター、およびカスタム型定義を装飾するカスタム属性を定義します。
  • 型名でキャッチできるカスタム例外を定義します。

構文

定義の構文

クラス定義では、次の構文を使用します。

class <class-name> [: [<base-class>][,<interface-list>]] {
    [[<attribute>] [hidden] [static] <property-definition> ...]
    [<class-name>([<constructor-argument-list>])
      {<constructor-statement-list>} ...]
    [[<attribute>] [hidden] [static] <method-definition> ...]
}

インスタンス化の構文

クラスのインスタンスをインスタンス化するには、次のいずれかの構文を使用します。

[$<variable-name> =] New-Object -TypeName <class-name> [
  [-ArgumentList] <constructor-argument-list>]
[$<variable-name> =] [<class-name>]::new([<constructor-argument-list>])
[$<variable-name> =] [<class-name>]@{[<class-property-hashtable>]}

Note

構文を使用する [<class-name>]::new() 場合は、クラス名を角かっこで囲む必要があります。 角かっこは、PowerShell の型定義を示します。

ハッシュテーブル構文は、パラメーターを予期しない既定のコンストラクターを持つクラスでのみ機能します。 既定のコンストラクターを使用してクラスのインスタンスを作成し、インスタンス プロパティにキーと値のペアを割り当てます。 hastable 内のキーが有効なプロパティ名でない場合、PowerShell はエラーを発生させます。

例 1 - 最小定義

この例では、使用可能なクラスを作成するために必要な最小限の構文を示します。

class Device {
    [string]$Brand
}

$dev = [Device]::new()
$dev.Brand = "Fabrikam, Inc."
$dev
Brand
-----
Fabrikam, Inc.

例 2 - インスタンス メンバーを持つクラス

この例では、いくつかのプロパティ、 コンストラクター、およびメソッドを使用して Book クラスを定義します。 定義されているすべてのメンバーは インスタンス メンバーであり、静的メンバーではありません。 プロパティとメソッドには、クラスの作成されたインスタンスを介してのみアクセスできます。

class Book {
    # Class properties
    [string]   $Title
    [string]   $Author
    [string]   $Synopsis
    [string]   $Publisher
    [datetime] $PublishDate
    [int]      $PageCount
    [string[]] $Tags
    # Default constructor
    Book() { $this.Init(@{}) }
    # Convenience constructor from hashtable
    Book([hashtable]$Properties) { $this.Init($Properties) }
    # Common constructor for title and author
    Book([string]$Title, [string]$Author) {
        $this.Init(@{Title = $Title; Author = $Author })
    }
    # Shared initializer method
    [void] Init([hashtable]$Properties) {
        foreach ($Property in $Properties.Keys) {
            $this.$Property = $Properties.$Property
        }
    }
    # Method to calculate reading time as 2 minutes per page
    [timespan] GetReadingTime() {
        if ($this.PageCount -le 0) {
            throw 'Unable to determine reading time from page count.'
        }
        $Minutes = $this.PageCount * 2
        return [timespan]::new(0, $Minutes, 0)
    }
    # Method to calculate how long ago a book was published
    [timespan] GetPublishedAge() {
        if (
            $null -eq $this.PublishDate -or
            $this.PublishDate -eq [datetime]::MinValue
        ) { throw 'PublishDate not defined' }

        return (Get-Date) - $this.PublishDate
    }
    # Method to return a string representation of the book
    [string] ToString() {
        return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))"
    }
}

次のスニペットは、クラスのインスタンスを作成し、その動作を示しています。 Book クラスのインスタンスを作成した後、この例では、and メソッドをGetReadingTime()GetPublishedAge()使用して書籍に関するメッセージを書き込みます。

$Book = [Book]::new(@{
    Title       = 'The Hobbit'
    Author      = 'J.R.R. Tolkien'
    Publisher   = 'George Allen & Unwin'
    PublishDate = '1937-09-21'
    PageCount   = 310
    Tags        = @('Fantasy', 'Adventure')
})

$Book
$Time = $Book.GetReadingTime()
$Time = @($Time.Hours, 'hours and', $Time.Minutes, 'minutes') -join ' '
$Age  = [Math]::Floor($Book.GetPublishedAge().TotalDays / 365.25)

"It takes $Time to read $Book,`nwhich was published $Age years ago."
Title       : The Hobbit
Author      : J.R.R. Tolkien
Synopsis    :
Publisher   : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount   : 310
Tags        : {Fantasy, Adventure}

It takes 10 hours and 20 minutes to read The Hobbit by J.R.R. Tolkien (1937),
which was published 86 years ago.

例 3 - 静的メンバーを持つクラス

この例の BookList クラスは、例 2 の Book クラスに基づいています。BookList クラスを静的としてマークすることはできませんが、実装では Books 静的プロパティと、そのプロパティを管理するための静的メソッドのセットのみが定義されます。

class BookList {
    # Static property to hold the list of books
    static [System.Collections.Generic.List[Book]] $Books
    # Static method to initialize the list of books. Called in the other
    # static methods to avoid needing to explicit initialize the value.
    static [void] Initialize()             { [BookList]::Initialize($false) }
    static [bool] Initialize([bool]$force) {
        if ([BookList]::Books.Count -gt 0 -and -not $force) {
            return $false
        }

        [BookList]::Books = [System.Collections.Generic.List[Book]]::new()

        return $true
    }
    # Ensure a book is valid for the list.
    static [void] Validate([book]$Book) {
        $Prefix = @(
            'Book validation failed: Book must be defined with the Title,'
            'Author, and PublishDate properties, but'
        ) -join ' '
        if ($null -eq $Book) { throw "$Prefix was null" }
        if ([string]::IsNullOrEmpty($Book.Title)) {
            throw "$Prefix Title wasn't defined"
        }
        if ([string]::IsNullOrEmpty($Book.Author)) {
            throw "$Prefix Author wasn't defined"
        }
        if ([datetime]::MinValue -eq $Book.PublishDate) {
            throw "$Prefix PublishDate wasn't defined"
        }
    }
    # Static methods to manage the list of books.
    # Add a book if it's not already in the list.
    static [void] Add([Book]$Book) {
        [BookList]::Initialize()
        [BookList]::Validate($Book)
        if ([BookList]::Books.Contains($Book)) {
            throw "Book '$Book' already in list"
        }

        $FindPredicate = {
            param([Book]$b)

            $b.Title -eq $Book.Title -and
            $b.Author -eq $Book.Author -and
            $b.PublishDate -eq $Book.PublishDate
        }.GetNewClosure()
        if ([BookList]::Books.Find($FindPredicate)) {
            throw "Book '$Book' already in list"
        }

        [BookList]::Books.Add($Book)
    }
    # Clear the list of books.
    static [void] Clear() {
      [BookList]::Initialize()
      [BookList]::Books.Clear()
    }
    # Find a specific book using a filtering scriptblock.
    static [Book] Find([scriptblock]$Predicate) {
        [BookList]::Initialize()
        return [BookList]::Books.Find($Predicate)
    }
    # Find every book matching the filtering scriptblock.
    static [Book[]] FindAll([scriptblock]$Predicate) {
        [BookList]::Initialize()
        return [BookList]::Books.FindAll($Predicate)
    }
    # Remove a specific book.
    static [void] Remove([Book]$Book) {
        [BookList]::Initialize()
        [BookList]::Books.Remove($Book)
    }
    # Remove a book by property value.
    static [void] RemoveBy([string]$Property, [string]$Value) {
        [BookList]::Initialize()
        $Index = [BookList]::Books.FindIndex({
            param($b)
            $b.$Property -eq $Value
        }.GetNewClosure())
        if ($Index -ge 0) {
            [BookList]::Books.RemoveAt($Index)
        }
    }
}

BookList が定義されたので、前の例の書籍をリストに追加できます。

$null -eq [BookList]::Books

[BookList]::Add($Book)

[BookList]::Books
True

Title       : The Hobbit
Author      : J.R.R. Tolkien
Synopsis    :
Publisher   : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount   : 310
Tags        : {Fantasy, Adventure}

次のスニペットは、クラスの静的メソッドを呼び出します。

[BookList]::Add([Book]::new(@{
    Title       = 'The Fellowship of the Ring'
    Author      = 'J.R.R. Tolkien'
    Publisher   = 'George Allen & Unwin'
    PublishDate = '1954-07-29'
    PageCount   = 423
    Tags        = @('Fantasy', 'Adventure')
}))

[BookList]::Find({
    param ($b)

    $b.PublishDate -gt '1950-01-01'
}).Title

[BookList]::FindAll({
    param($b)

    $b.Author -match 'Tolkien'
}).Title

[BookList]::Remove($Book)
[BookList]::Books.Title

[BookList]::RemoveBy('Author', 'J.R.R. Tolkien')
"Titles: $([BookList]::Books.Title)"

[BookList]::Add($Book)
[BookList]::Add($Book)
The Fellowship of the Ring

The Hobbit
The Fellowship of the Ring

The Fellowship of the Ring

Titles:

Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
At C:\code\classes.examples.ps1:114 char:13
+             throw "Book '$Book' already in list"
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Book 'The Hobbi...alread
   y in list:String) [], RuntimeException
    + FullyQualifiedErrorId : Book 'The Hobbit by J.R.R. Tolkien (1937)'
   already in list

クラスのプロパティ

プロパティは、クラス スコープで宣言された変数です。 プロパティには、任意の組み込み型または別のクラスのインスタンスを指定できます。 クラスには、0 個以上のプロパティを含めることができます。 クラスには最大プロパティ数がありません。

詳細については、about_Classes_Propertiesを参照してください

クラス メソッド

メソッドは、クラスが実行できるアクションを定義します。 メソッドは、入力データを指定するパラメーターを受け取ることができます。 メソッドは常に出力の種類を定義します。 メソッドが出力を返さない場合は、Void 出力型が必要です。 メソッドが出力型を明示的に定義しない場合、メソッドの出力型は Void です

詳細については、「about_Classes_Methods」を参照してください

クラス コンストラクター

コンストラクターを使用すると、クラスのインスタンスを作成するときに、既定値を設定し、オブジェクト ロジックを検証できます。 コンストラクターの名前はクラスと同じです。 コンストラクターには、新しいオブジェクトのデータ メンバーを初期化するためのパラメーターが含まれる場合があります。

詳細については、「about_Classes_Constructors」を参照してください

非表示のキーワード (keyword)

キーワード (keyword)はhiddenクラス メンバーを非表示にします。 メンバーは引き続きユーザーがアクセスでき、オブジェクトが使用可能なすべてのスコープで使用できます。 非表示のメンバーはコマンドレットから Get-Member 非表示になり、タブ補完またはクラス定義外の IntelliSense を使用して表示することはできません。

キーワード (keyword)はhiddenクラス メンバーにのみ適用され、クラス自体には適用されません。

非表示のクラス メンバーは次のとおりです。

  • クラスの既定の出力には含まれません。
  • コマンドレットによって返されるクラス メンバーの一覧には Get-Member 含まれません。 非表示のメンバーをGet-Member表示するには、Force パラメーターを使用します。
  • 非表示のメンバーを定義するクラスで入力候補が発生しない限り、タブ補完または IntelliSense には表示されません。
  • クラスのパブリック メンバー。 アクセス、継承、および変更が可能です。 メンバーを非表示にしても、プライベートにはなりません。 前のポイントで説明したように、メンバーのみが非表示になります。

Note

メソッドのオーバーロードを非表示にすると、そのメソッドは IntelliSense、入力候補の結果、および既定の出力 Get-Memberから削除されます。 コンストラクターを非表示にすると、IntelliSense と入力候補の new() 結果からオプションが削除されます。

キーワード (keyword)の詳細については、「about_Hidden」を参照してください。 非表示のプロパティの詳細については、「about_Classes_Properties」を参照してください。 非表示のメソッドの詳細については、「about_Classes_Methods」を参照してください。 非表示のコンストラクターの詳細については、「about_Classes_Constructors」を参照してください

静的キーワード (keyword)

キーワード (keyword)はstatic、クラスに存在し、インスタンスを必要としないプロパティまたはメソッドを定義します。

クラスのインスタンス化に関係なく、静的プロパティは常に使用できます。 静的プロパティは、クラスのすべてのインスタンスで共有されます。 静的メソッドは常に使用できます。 すべての静的プロパティは、セッションスパン全体に対して有効です。

キーワード (keyword)はstaticクラス メンバーにのみ適用され、クラス自体には適用されません。

静的プロパティの詳細については、「about_Classes_Properties」を参照してください。 静的メソッドの詳細については、「about_Classes_Methods」を参照してください。 静的コンストラクターの詳細については、「about_Classes_Constructors」を参照してください

PowerShell クラスでの継承

既存のクラスから派生する新しいクラスを作成することで、クラスを拡張できます。 派生クラスは、基底クラスのプロパティとメソッドを継承します。 必要に応じて、基底クラスのメンバーを追加またはオーバーライドできます。

PowerShell では、複数の継承はサポートされていません。 クラスは、複数のクラスから直接継承することはできません。

クラスは、コントラクトを定義するインターフェイスから継承することもできます。 インターフェイスから継承するクラスは、そのコントラクトを実装する必要があります。 その場合、クラスは、そのインターフェイスを実装する他のクラスと同様に使用できます。

基底クラスから継承するクラスまたはインターフェイスを実装するクラスの派生の詳細については、about_Classes_Inheritanceを参照してください

型アクセラレータを使用したクラスのエクスポート

既定では、PowerShell モジュールは、PowerShell で定義されているクラスと列挙型を自動的にエクスポートしません。 カスタム型は、ステートメントを呼び出さずにモジュールの外部では using module 使用できません。

ただし、モジュールが型アクセラレータを追加した場合、ユーザーがモジュールをインポートした後、それらの型アクセラレータはすぐにセッションで使用できます。

Note

型アクセラレータをセッションに追加するには、内部 (パブリックではない) API を使用します。 この API を使用すると、競合が発生する可能性があります。 モジュールのインポート時に同じ名前の型アクセラレータが既に存在する場合、次に示すパターンではエラーがスローされます。 また、セッションからモジュールを削除すると、型アクセラレータも削除されます。

このパターンにより、セッションで型を使用できるようになります。 VS Code でスクリプト ファイルを作成する場合、IntelliSense や入力候補には影響しません。 VS Code でカスタム型の IntelliSense と入力候補を取得するには、スクリプトの先頭にステートメントを追加 using module する必要があります。

次のパターンは、PowerShell クラスと列挙型をモジュールの型アクセラレータとして登録する方法を示しています。 任意の型定義の後に、ルート スクリプト モジュールにスニペットを追加します。 モジュールを $ExportableTypes インポートするときにユーザーが使用できるようにする各型が変数に含まれていることを確認します。 他のコードでは、編集は必要ありません。

# Define the types to export with type accelerators.
$ExportableTypes =@(
    [DefinedTypeName]
)
# Get the internal TypeAccelerators class to use its static methods.
$TypeAcceleratorsClass = [psobject].Assembly.GetType(
    'System.Management.Automation.TypeAccelerators'
)
# Ensure none of the types would clobber an existing type accelerator.
# If a type accelerator with the same name exists, throw an exception.
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get
foreach ($Type in $ExportableTypes) {
    if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
        $Message = @(
            "Unable to register type accelerator '$($Type.FullName)'"
            'Accelerator already exists.'
        ) -join ' - '

        throw [System.Management.Automation.ErrorRecord]::new(
            [System.InvalidOperationException]::new($Message),
            'TypeAcceleratorAlreadyExists',
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $Type.FullName
        )
    }
}
# Add type accelerators for every exportable type.
foreach ($Type in $ExportableTypes) {
    $TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
# Remove type accelerators when the module is removed.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    foreach($Type in $ExportableTypes) {
        $TypeAcceleratorsClass::Remove($Type.FullName)
    }
}.GetNewClosure()

ユーザーがモジュールをインポートすると、セッションの型アクセラレータに追加された型は、IntelliSense と完了にすぐに使用できます。 モジュールが削除されると、型アクセラレータも削除されます。

PowerShell モジュールからクラスを手動でインポートする

Import-Module ステートメントは、 #requires モジュールによって定義されているモジュール関数、エイリアス、変数のみをインポートします。 クラスはインポートされません。

モジュールでクラスと列挙型を定義するが、それらの型に型アクセラレータを追加しない場合は、ステートメントを using module 使用してそれらをインポートします。

このステートメントは using module 、スクリプト モジュールまたはバイナリ モジュールのルート モジュール (ModuleToProcess) からクラスと列挙型をインポートします。 入れ子になったモジュールで定義されているクラスや、ルート モジュールにドット ソース化されたスクリプトで定義されているクラスは、一貫してインポートされません。 ルート モジュール内のモジュール外のユーザーが直接使用できるようにするクラスを定義します。

ステートメントのusing詳細については、about_Usingを参照してください

開発中に新しく変更されたコードを読み込む

スクリプト モジュールの開発中は、コードに変更を加え、Force パラメーターを使用してImport-Module新しいバージョンのモジュールを読み込むのが一般的です。 モジュールの再読み込みは、ルート モジュール内の関数に対する変更に対してのみ機能します。 Import-Module では、入れ子になったモジュールは再読み込みされません。 また、更新されたクラスを読み込む方法はありません。

最新バージョンを確実に実行するには、新しいセッションを開始する必要があります。 PowerShell で定義され、ステートメントで using インポートされたクラスと列挙型はアンロードできません。

もう 1 つの一般的な開発方法は、コードを異なるファイルに分割することです。 別のモジュールで定義されたクラスを使用する関数がある場合は、ステートメントを using module 使用して、関数に必要なクラス定義があることを確認する必要があります。

PSReference 型は、クラス メンバーではサポートされていません

[ref]型アクセラレータは、PSReference クラスの短縮形です。 クラス メンバーを型キャストするために使用 [ref] すると、サイレント モードで失敗します。 パラメーターを使用 [ref] する API は、クラス メンバーでは使用できません。 PSReference クラスは、COM オブジェクトをサポートするように設計されています。 COM オブジェクトには、値を参照渡しする必要がある場合があります。

詳細については、「PSReference クラス」を参照してください

制限事項

次の一覧には、PowerShell クラスを定義するための制限事項と、それらの制限がある場合の回避策が含まれます。

一般的な制限事項

  • クラス メンバーは、型として PSReference を使用できません。

    回避策: なし。

  • PowerShell クラスをセッションでアンロードまたは再読み込みすることはできません。

    回避策: 新しいセッションを開始します。

  • モジュールで定義されている PowerShell クラスは自動的にはインポートされません。

    回避策: ルート モジュールの型アクセラレータの一覧に定義済みの型を追加します。 これにより、モジュールのインポート時に型を使用できるようになります。

  • およびhiddenstatic キーワード (keyword)は、クラス定義ではなく、クラス メンバーにのみ適用されます。

    回避策: なし。

コンストラクターの制限事項

  • コンストラクターのチェーンは実装されていません。

    回避策: 非表示 Init() のメソッドを定義し、コンストラクター内から呼び出します。

  • コンストラクター パラメーターでは、検証属性を含む属性を使用できません。

    回避策: コンストラクター本体のパラメーターを検証属性で再割り当てします。

  • コンストラクター パラメーターでは既定値を定義できません。 パラメーターは常に必須です。

    回避策: なし。

  • コンストラクターのオーバーロードが非表示の場合、コンストラクターのすべてのオーバーロードも非表示として扱われます。

    回避策: なし。

メソッドの制限事項

  • メソッド パラメーターでは、検証属性を含む属性を使用できません。

    回避策: メソッド本体のパラメーターを検証属性で再割り当てするか、コマンドレットを使用して静的コンストラクターでメソッドを Update-TypeData 定義します。

  • メソッド パラメーターでは既定値を定義できません。 パラメーターは常に必須です。

    回避策: コマンドレットを使用して、静的コンストラクターでメソッドを Update-TypeData 定義します。

  • メソッドは、非表示の場合でも常にパブリックです。 これらは、クラスが継承されるときにオーバーライドできます。

    回避策: なし。

  • メソッドのオーバーロードが非表示の場合、そのメソッドのすべてのオーバーロードも非表示として扱われます。

    回避策: なし。

プロパティの制限事項

  • 静的プロパティは常に変更可能です。 PowerShell クラスでは、変更できない静的プロパティを定義できません。

    回避策: なし。

  • クラス プロパティ属性の引数は定数である必要があるため、プロパティでは ValidateScript 属性を使用できません。

    回避策: ValidateArgumentsAttribute 型から継承するクラスを定義し、代わりにその属性を使用します。

  • 直接宣言されたプロパティでは、カスタム getter と setter の実装を定義できません。

    回避策: 非表示のプロパティを定義し、表示されるゲッターとセッターのロジックを定義するために使用 Update-TypeData します。

  • プロパティで Alias 属性を使用することはできません。 この属性は、パラメーター、コマンドレット、関数にのみ適用されます。

    回避策: このコマンドレットを Update-TypeData 使用して、クラス コンストラクターでエイリアスを定義します。

  • コマンドレットを使用して PowerShell クラスを JSON ConvertTo-Json に変換すると、出力 JSON にはすべての非表示プロパティとその値が含まれます。

    回避策: なし

継承の制限事項

  • PowerShell では、スクリプト コードでのインターフェイスの定義はサポートされていません。

    回避策: C# でインターフェイスを定義し、インターフェイスを定義するアセンブリを参照します。

  • PowerShell クラスは、1 つの基本クラスからのみ継承できます。

    回避策: クラスの継承は推移的です。 派生クラスは、別の派生クラスから継承して、基底クラスのプロパティとメソッドを取得できます。

  • ジェネリック クラスまたはインターフェイスから継承する場合は、ジェネリックの型パラメーターが既に定義されている必要があります。 クラス自体をクラスまたはインターフェイスの型パラメーターとして定義することはできません。

    回避策: ジェネリック 基底クラスまたはインターフェイスから派生するには、別 .psm1 のファイルでカスタム型を定義し、ステートメントを using module 使用して型を読み込みます。 ジェネリックから継承するときに、カスタム型が型パラメーターとしてそれ自体を使用する回避策はありません。

関連項目