about_Classes_Constructors

简短说明

介绍如何定义 PowerShell 类的构造函数。

长说明

构造函数使你能够在创建类实例时设置默认值并验证对象逻辑。 构造函数与类具有相同的名称。 构造函数可能具有参数来初始化新对象的数据成员。

PowerShell 类构造函数定义为类上的特殊方法。 它们的行为与 PowerShell 类方法的行为相同,但出现以下异常:

  • 构造函数没有输出类型。 他们无法使用return关键字 (keyword)。
  • 构造函数始终与类同名。
  • 无法直接调用构造函数。 它们仅在创建实例时运行。
  • 构造函数永远不会出现在 cmdlet 的 Get-Member 输出中。

有关 PowerShell 类方法的详细信息,请参阅 about_Classes_Methods

该类可以定义零个或多个构造函数。 如果未定义构造函数,则为该类提供默认的无参数构造函数。 此构造函数将所有成员初始化为其默认值。 对象类型和字符串被赋予 null 值。 定义构造函数时,不会创建默认的无参数构造函数。 如果需要,请创建无参数构造函数。

还可以定义无 参数静态构造函数

语法

类构造函数使用以下语法:

默认构造函数语法

<class-name> () [: base([<params>])] {
    <body>
}

静态构造函数语法

static <class-name> () [: base([<params>])] {
    <body>
}

参数化构造函数语法(单行)

<class-name> ([[<parameter-type>]$<parameter-name>[, [<parameter-type>]$<parameter-name>...]]) [: base([<params>])] {
    <body>
}

参数化构造函数语法 (多行)

<class-name> (
    [<parameter-type>]$<parameter-name>[,
    [<parameter-type>]$<parameter-name>...]
) [: base([<params>])] {
    <body>
}

示例

示例 1 - 使用默认构造函数定义类

ExampleBook1 类不定义构造函数。 而是使用自动默认构造函数。

class ExampleBook1 {
    [string]   $Name
    [string]   $Author
    [int]      $Pages
    [datetime] $PublishedOn
}

[ExampleBook1]::new()
Name Author Pages PublishedOn
---- ------ ----- -----------
                0 1/1/0001 12:00:00 AM

注意

Name 和 Author 属性的默认值是$null因为它们被键入为字符串,这是引用类型。 其他属性具有其已定义类型的默认值,因为它们是值类型属性。 有关属性的默认值的详细信息,请参阅 about_Classes_Properties中的“默认属性值”。

示例 2 - 重写默认构造函数

ExampleBook2 显式定义默认构造函数,将 PublishedOn 的值设置为当前日期,将 Pages 设置为 1

class ExampleBook2 {
    [string]   $Name
    [string]   $Author
    [int]      $Pages
    [datetime] $PublishedOn

    ExampleBook2() {
        $this.PublishedOn = (Get-Date).Date
        $this.Pages       = 1
    }
}

[ExampleBook2]::new()
Name Author Pages PublishedOn
---- ------ ----- -----------
                1 11/1/2023 12:00:00 AM

示例 3 - 定义构造函数重载

ExampleBook3 类定义三个构造函数重载,使用户能够通过哈希表、传递每个属性值以及传递书籍和作者的名称来创建类的实例。 该类不定义默认构造函数。

class ExampleBook3 {
    [string]   $Name
    [string]   $Author
    [int]      $Pages
    [datetime] $PublishedOn

    ExampleBook3([hashtable]$Info) {
        switch ($Info.Keys) {
            'Name'        { $this.Name        = $Info.Name }
            'Author'      { $this.Author      = $Info.Author }
            'Pages'       { $this.Pages       = $Info.Pages }
            'PublishedOn' { $this.PublishedOn = $Info.PublishedOn }
        }
    }

    ExampleBook3(
        [string]   $Name,
        [string]   $Author,
        [int]      $Pages,
        [datetime] $PublishedOn
    ) {
        $this.Name        = $Name
        $this.Author      = $Author
        $this.Pages       = $Pages
        $this.PublishedOn = $PublishedOn
    }

    ExampleBook3([string]$Name, [string]$Author) {
        $this.Name   = $Name
        $this.Author = $Author
    }
}

[ExampleBook3]::new(@{
    Name        = 'The Hobbit'
    Author      = 'J.R.R. Tolkien'
    Pages       = 310
    PublishedOn = '1937-09-21'
})
[ExampleBook3]::new('The Hobbit', 'J.R.R. Tolkien', 310, '1937-09-21')
[ExampleBook3]::new('The Hobbit', 'J.R.R. Tolkien')
[ExampleBook3]::new()
Name       Author         Pages PublishedOn
----       ------         ----- -----------
The Hobbit J.R.R. Tolkien   310 9/21/1937 12:00:00 AM
The Hobbit J.R.R. Tolkien   310 9/21/1937 12:00:00 AM
The Hobbit J.R.R. Tolkien     0 1/1/0001 12:00:00 AM

MethodException:
Line |
  42 |  [ExampleBook3]::new()
     |  ~~~~~~~~~~~~~~~~~~~~~
     | Cannot find an overload for "new" and the argument count: "0".

调用默认构造函数将返回方法异常。 仅当类不定义任何构造函数时,才会为类定义自动默认构造函数。 由于 ExampleBook3 定义了多个重载,因此默认构造函数不会自动添加到类中。

示例 4 - 使用共享方法链接构造函数

此示例演示如何为构造函数编写可重用的共享代码。 PowerShell 类不能使用构造函数链接,因此此示例类改为定义方法 Init() 。 方法有多个重载。 参数较少的重载调用具有未指定参数默认值的更显式重载。

class ExampleBook4 {
    [string]   $Name
    [string]   $Author
    [datetime] $PublishedOn
    [int]      $Pages

    ExampleBook4() {
        $this.Init()
    }
    ExampleBook4([string]$Name) {
        $this.Init($Name)
    }
    ExampleBook4([string]$Name, [string]$Author) {
        $this.Init($Name, $Author)
    }
    ExampleBook4([string]$Name, [string]$Author, [datetime]$PublishedOn) {
        $this.Init($Name, $Author, $PublishedOn)
    }
    ExampleBook4(
      [string]$Name,
      [string]$Author,
      [datetime]$PublishedOn,
      [int]$Pages
    ) {
        $this.Init($Name, $Author, $PublishedOn, $Pages)
    }

    hidden Init() {
        $this.Init('Unknown')
    }
    hidden Init([string]$Name) {
        $this.Init($Name, 'Unknown')
    }
    hidden Init([string]$Name, [string]$Author) {
        $this.Init($Name, $Author, (Get-Date).Date)
    }
    hidden Init([string]$Name, [string]$Author, [datetime]$PublishedOn) {
        $this.Init($Name, $Author, $PublishedOn, 1)
    }
    hidden Init(
        [string]$Name,
        [string]$Author,
        [datetime]$PublishedOn,
        [int]$Pages
      ) {
        $this.Name        = $Name
        $this.Author      = $Author
        $this.PublishedOn = $PublishedOn
        $this.Pages       = $Pages
    }
}

[ExampleBook4]::new()
[ExampleBook4]::new('The Hobbit')
[ExampleBook4]::new('The Hobbit', 'J.R.R. Tolkien')
[ExampleBook4]::new('The Hobbit', 'J.R.R. Tolkien', (Get-Date '1937-9-21'))
[ExampleBook4]::new(
    'The Hobbit',
    'J.R.R. Tolkien',
    (Get-Date '1937-9-21'),
    310
)
Name       Author         PublishedOn           Pages
----       ------         -----------           -----
Unknown    Unknown        11/1/2023 12:00:00 AM     1
The Hobbit Unknown        11/1/2023 12:00:00 AM     1
The Hobbit J.R.R. Tolkien 11/1/2023 12:00:00 AM     1
The Hobbit J.R.R. Tolkien 9/21/1937 12:00:00 AM     1
The Hobbit J.R.R. Tolkien 9/21/1937 12:00:00 AM   310

示例 5 - 派生类构造函数

以下示例使用为基类和从基类继承的派生类定义静态、默认值和参数化构造函数的类。

class BaseExample {
    static [void] DefaultMessage([type]$Type) {
        Write-Verbose "[$($Type.Name)] default constructor"
    }

    static [void] StaticMessage([type]$Type) {
        Write-Verbose "[$($Type.Name)] static constructor"
    }

    static [void] ParamMessage([type]$Type, [object]$Value) {
        Write-Verbose "[$($Type.Name)] param constructor ($Value)"
    }

    static BaseExample() { [BaseExample]::StaticMessage([BaseExample])  }
    BaseExample()        { [BaseExample]::DefaultMessage([BaseExample]) }
    BaseExample($Value)  { [BaseExample]::ParamMessage([BaseExample], $Value) }
}

class DerivedExample : BaseExample {
    static DerivedExample() { [BaseExample]::StaticMessage([DerivedExample])  }
           DerivedExample() { [BaseExample]::DefaultMessage([DerivedExample]) }

    DerivedExample([int]$Number) : base($Number) {
        [BaseExample]::ParamMessage([DerivedExample], $Number)
    }
    DerivedExample([string]$String) {
        [BaseExample]::ParamMessage([DerivedExample], $String)
    }
}

以下块显示了用于调用基类构造函数的详细消息传送。 静态构造函数消息仅在首次创建类实例时发出。

PS> $VerbosePreference = 'Continue'
PS> $b = [BaseExample]::new()

VERBOSE: [BaseExample] static constructor
VERBOSE: [BaseExample] default constructor

PS> $b = [BaseExample]::new()

VERBOSE: [BaseExample] default constructor

PS> $b = [BaseExample]::new(1)

VERBOSE: [BaseExample] param constructor (1)

下一个块显示用于在新会话中调用派生类构造函数的详细消息传送。 首次调用派生类构造函数时,将调用基类和派生类的静态构造函数。 会话中不会再次调用这些构造函数。 基类的构造函数始终在派生类的构造函数之前运行。

PS> $VerbosePreference = 'Continue'
PS> $c = [DerivedExample]::new()

VERBOSE: [BaseExample] static constructor
VERBOSE: [DerivedExample] static constructor
VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] default constructor

PS> $c = [DerivedExample]::new()

VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] default constructor

PS> $c = [DerivedExample]::new(1)

VERBOSE: [BaseExample] param constructor (1)
VERBOSE: [DerivedExample] param constructor (1)

PS> $c = [DerivedExample]::new('foo')

VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] param constructor (foo)

构造函数运行排序

类实例化时,将执行一个或多个构造函数的代码。

对于不从另一个类继承的类,排序为:

  1. 类的静态构造函数。
  2. 类的适用构造函数重载。

对于从另一个类继承的派生类,排序为:

  1. 基类的静态构造函数。
  2. 派生类的静态构造函数。
  3. 如果派生类构造函数显式调用基构造函数重载,则它会为基类运行该构造函数。 如果未显式调用基构造函数,它将运行基类的默认构造函数。
  4. 派生类的适用构造函数重载。

在所有情况下,静态构造函数仅在会话中运行一次。

有关构造函数行为和排序的示例,请参阅 示例 5

隐藏的构造函数

可以通过使用关键字 (keyword)声明类的hidden构造函数来隐藏这些构造函数。 隐藏的类构造函数包括:

  • 不包括在类的默认输出中。
  • 不包含在 cmdlet 返回 Get-Member 的类成员列表中。 若要显示隐藏的属性, Get-Member请使用 Force 参数。
  • 除非完成发生在定义隐藏属性的类中,否则不会显示在 Tab 补全或 IntelliSense 中。
  • 类的公共成员。 可以访问和修改它们。 隐藏属性不会使其成为私有属性。 它仅隐藏上一点中所述的属性。

注意

隐藏任何构造函数时,将从 new() IntelliSense 和完成结果中删除该选项。

有关关键字 (keyword)的详细信息hidden,请参阅about_Hidden

静态构造函数

可以通过使用关键字 (keyword)声明构造函数static,将构造函数定义为属于类本身,而不是类的实例。 静态类构造函数:

  • 仅在会话中创建类的实例时调用。
  • 不能有任何参数。
  • 无法使用变量访问实例属性或方法 $this

派生类的构造函数

当类从另一个类继承时,构造函数可以使用关键字 (keyword)从基类base调用构造函数。 如果派生类未从基类显式调用构造函数,则会改为调用基类的默认构造函数。

若要调用非默认基构造函数,请在构造函数参数之后和正文块之前添加 : base(<parameters>)

class <derived-class> : <base-class> {
    <derived-class>(<derived-parameters>) : <base-class>(<base-parameters>) {
        # initialization code
    }
}

定义调用基类构造函数的构造函数时,参数可以是以下任一项:

  • 派生类构造函数上任何参数的变量。
  • 任何静态值。
  • 计算结果为参数类型值的任何表达式。

有关派生类上的构造函数的示例,请参阅 示例 5

链接构造函数

与 C# 不同,PowerShell 类构造函数不能将链接与语法结合使用 : this(<parameters>) 。 若要减少代码重复,请使用具有多个重载的隐藏 Init() 方法具有相同的效果。 示例 4 显示了使用此模式的类。

使用 Update-TypeData 添加实例属性和方法

除了直接在类定义中声明属性和方法外,还可以使用 Update-TypeData cmdlet 在静态构造函数中定义类实例的属性。

将此代码片段用作模式的起点。 根据需要替换尖括号中的占位符文本。

class <class-name> {
    static [hashtable[]] $MemberDefinitions = @(
        @{
            Name       = '<member-name>'
            MemberType = '<member-type>'
            Value      = <member-definition>
        }
    )

    static <class-name>() {
        $TypeName = [<class-name>].Name
        foreach ($Definition in [<class-name>]::MemberDefinitions) {
            Update-TypeData -TypeName $TypeName @Definition
        }
    }
}

提示

cmdlet Add-Member 可以将属性和方法添加到非静态构造函数中的类,但每次调用构造函数时都会运行该 cmdlet。 在 Update-TypeData 静态构造函数中使用可确保将成员添加到类的代码只需在会话中运行一次。

仅当不能使用 Update-TypeData非静态构造函数(如只读属性)定义属性时,才会将属性添加到类。

有关使用定义实例方法 Update-TypeData的详细信息,请参阅 about_Classes_Methods。 有关使用 Update-TypeData定义实例属性的详细信息,请参阅 about_Classes_Properties

限制

PowerShell 类构造函数具有以下限制:

  • 未实现构造函数链接。

    解决方法:定义隐藏 Init() 的方法,并从构造函数中调用它们。

  • 构造函数参数不能使用任何属性,包括验证属性。

    解决方法:使用验证属性重新分配构造函数正文中的参数。

  • 构造函数参数不能定义默认值。 参数始终是必需的。

    解决方法:无。

  • 如果隐藏构造函数的任何重载,则构造函数的每个重载也被视为隐藏。

    解决方法:无。

另请参阅