about_Classes_Properties

简短说明

介绍如何定义 PowerShell 类的属性。

长说明

属性是包含数据的类的成员。 属性在类范围中声明为变量。 属性可以是任何内置类型,也可以是另一个类的实例。 类可以零个或多个属性。 类没有最大属性计数。

类属性可以具有任意数量的属性,包括 隐藏 属性和 静态 属性。 每个属性定义都必须包含属性的类型。 可以为属性定义默认值。

语法

类属性使用以下语法:

单行语法

[[<attribute>]...] [<property-type>] $<property-name> [= <default-value>]

多行语法

[[<attribute>]...]
[<property-type>]
$<property-name> [= <default-value>]

示例

示例 1 - 最小类属性

ExampleProject1 类的属性使用没有任何属性或默认值的内置类型。

class ExampleProject1 {
    [string]   $Name
    [int]      $Size
    [bool]     $Completed
    [string]   $Assignee
    [datetime] $StartDate
    [datetime] $EndDate
    [datetime] $DueDate
}

[ExampleProject1]::new()

$null -eq ([ExampleProject1]::new()).Name
Name      :
Size      : 0
Completed : False
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

True

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

示例 2 - 具有自定义类型的类属性

ExampleProject2 的属性包括在 ExampleProject2 类之前在 PowerShell 中定义的自定义枚举和类。

enum ProjectState {
    NotTriaged
    ReadyForWork
    Committed
    Blocked
    InProgress
    Done
}

class ProjectAssignee {
    [string] $DisplayName
    [string] $UserName

    [string] ToString() {
        return "$($this.DisplayName) ($($this.UserName))"
    }
}

class ExampleProject2 {
    [string]          $Name
    [int]             $Size
    [ProjectState]    $State
    [ProjectAssignee] $Assignee
    [datetime]        $StartDate
    [datetime]        $EndDate
    [datetime]        $DueDate
}

[ExampleProject2]@{
    Name     = 'Class Property Documentation'
    Size     = 8
    State    = 'InProgress'
    Assignee = @{
        DisplayName = 'Mikey Lombardi'
        UserName    = 'michaeltlombardi'
    }
    StartDate = '2023-10-23'
    DueDate   = '2023-10-27'
}
Name      : Class Property Documentation
Size      : 8
State     : InProgress
Assignee  : Mikey Lombardi (michaeltlombardi)
StartDate : 10/23/2023 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 10/27/2023 12:00:00 AM

示例 3 - 具有验证属性的类属性

ExampleProject3 类将 Size 属性定义为必须大于或等于 0 且小于或等于 16 的整数。 它使用 ValidateRange 属性来限制值。

class ExampleProject3 {
                           [string]   $Name
    [ValidateRange(0, 16)] [int]      $Size
                           [bool]     $Completed
                           [string]   $Assignee
                           [datetime] $StartDate
                           [datetime] $EndDate
                           [datetime] $DueDate
}

$project = [ExampleProject3]::new()
$project
Name      :
Size      : 0
Completed : False
Assignee  :
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

当 ExampleProject3 实例化时Size 默认为 0。 将属性设置为有效范围内的值将更新值。

$project.Size = 8
$project
Name      :
Size      : 8
Completed : False
Assignee  :
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

当 Size 设置为范围之外的无效值时,PowerShell 将引发异常,并且该值不会更改。

$project.Size = 32
$project.Size = -1

$project
SetValueInvocationException:
Line |
   1 |  $project.Size = 32
     |  ~~~~~~~~~~~~~~~~~~
     | Exception setting "Size": "The 32 argument is greater than the
     | maximum allowed range of 16. Supply an argument that is less than
     | or equal to 16 and then try the command again."

SetValueInvocationException:
Line |
   2 |  $project.Size = -1
     |  ~~~~~~~~~~~~~~~~~~
     | Exception setting "Size": "The -1 argument is less than the minimum
     | allowed range of 0. Supply an argument that is greater than or
     | equal to 0 and then try the command again."

Name      :
Size      : 8
Completed : False
Assignee  :
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

示例 4 - 具有显式默认值的类属性

ExampleProject4 类将 StartDate 属性的值默认为当前日期。

class ExampleProject4 {
    [string]   $Name
    [int]      $Size
    [bool]     $Completed
    [string]   $Assignee
    [datetime] $StartDate = (Get-Date).Date
    [datetime] $EndDate
    [datetime] $DueDate
}

[ExampleProject4]::new()

[ExampleProject4]::new().StartDate -eq (Get-Date).Date
Name      :
Size      : 0
Completed : False
Assignee  :
StartDate : 10/23/2023 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

True

示例 5 - 隐藏类属性

ExampleProject5 类的 Guid 属性具有hidden关键字 (keyword)。 Guid 属性不显示在类的默认输出或返回Get-Member的属性列表中。

class ExampleProject5 {
           [string]   $Name
           [int]      $Size
           [bool]     $Completed
           [string]   $Assignee
           [datetime] $StartDate
           [datetime] $EndDate
           [datetime] $DueDate
    hidden [string]   $Guid      = (New-Guid).Guid
}

$project = [ExampleProject5]::new()

"Project GUID: $($project.Guid)"

$project

$project | Get-Member -MemberType Properties | Format-Table
Project GUID: c72cef84-057c-4649-8940-13490dcf72f0

Name      :
Size      : 0
Completed : False
Assignee  :
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM


   TypeName: ExampleProject5

Name      MemberType Definition
----      ---------- ----------
Assignee  Property   string Assignee {get;set;}
Completed Property   bool Completed {get;set;}
DueDate   Property   datetime DueDate {get;set;}
EndDate   Property   datetime EndDate {get;set;}
Name      Property   string Name {get;set;}
Size      Property   int Size {get;set;}
StartDate Property   datetime StartDate {get;set;}

示例 6 - Static 类属性

ExampleProject6 类将静态 Projects 属性定义为所有已创建项目的列表。 类的默认构造函数将新实例添加到项目列表中。

class ExampleProject6 {
           [string]            $Name
           [int]               $Size
           [bool]              $Completed
           [string]            $Assignee
           [datetime]          $StartDate
           [datetime]          $EndDate
           [datetime]          $DueDate
    hidden [string]            $Guid     = (New-Guid).Guid
    static [ExampleProject6[]] $Projects = @()

    ExampleProject6() {
        [ExampleProject6]::Projects += $this
    }
}

"Project Count: $([ExampleProject6]::Projects.Count)"

$project1 = [ExampleProject6]@{ Name = 'Project_1' }
$project2 = [ExampleProject6]@{ Name = 'Project_2' }

[ExampleProject6]::Projects | Select-Object -Property Name, Guid
Project Count: 0

Name      Guid
----      ----
Project_1 75e7c8a0-f8d1-433a-a5be-fd7249494694
Project_2 6c501be4-e68c-4df5-8fce-e49dd8366afe

示例 7 - 在构造函数中定义属性

ExampleProject7 类使用 cmdlet 定义静态类构造函数Update-TypeData中的 Duration 脚本属性。 Update-TypeData使用或 Add-Member cmdlet 是定义 PowerShell 类的高级属性的唯一方法。

Duration 属性返回一个值$null,除非设置了 StartDateEndDate 属性,并且 StartDate 定义为早于 EndDate

class ExampleProject7 {
    [string]   $Name
    [int]      $Size
    [bool]     $Completed
    [string]   $Assignee
    [datetime] $StartDate
    [datetime] $EndDate
    [datetime] $DueDate

    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberName = 'Duration'
            MemberType = 'ScriptProperty'
            Value      = {
                [datetime]$UnsetDate = 0

                $StartNotSet   = $this.StartDate -eq $UnsetDate
                $EndNotSet     = $this.EndDate   -eq $UnsetDate
                $StartAfterEnd = $this.StartDate -gt $this.EndDate

                if ($StartNotSet -or $EndNotSet -or $StartAfterEnd) {
                    return $null
                }

                return $this.EndDate - $this.StartDate
            }
        }
    )

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

    ExampleProject7() {}

    ExampleProject7([string]$Name) {
        $this.Name = $Name
    }
}

$Project = [ExampleProject7]::new()
$Project

$null -eq $Project.Duration
Duration  :
Name      :
Size      : 0
Completed : False
Assignee  :
StartDate : 1/1/0001 12:00:00 AM
EndDate   : 1/1/0001 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

True

ExampleProject7 类实例的默认视图包括持续时间。 由于未设置 StartDateEndDate 属性,Duration 属性为 $null

$Project.StartDate = '2023-01-01'
$Project.EndDate   = '2023-01-08'

$Project
Duration  : 7.00:00:00
Name      :
Size      : 0
Completed : False
Assignee  :
StartDate : 1/1/2023 12:00:00 AM
EndDate   : 1/8/2023 12:00:00 AM
DueDate   : 1/1/0001 12:00:00 AM

正确设置属性后, Duration 属性将返回表示项目运行时间跨度的时间跨度。

默认属性值

每个类属性都有一个隐式默认值,具体取决于属性的类型。

如果属性是 引用类型(如字符串或对象),则隐式默认值为 $null。 如果属性是 值类型(如数字、布尔值或枚举),则属性具有默认值,具体取决于类型:

  • 数字类型(如整数和浮点数)默认为 0
  • 布尔值默认为 $false
  • 枚举默认为 0,即使枚举不定义 0标签。

有关 .NET 中的默认值的详细信息,请参阅 C# 类型的默认值(C# 参考)。

若要定义属性的显式默认值,请声明具有对默认值的赋值的属性。

例如,ProjectTask 类的此定义定义了 Guid 属性的显式默认值,为每个新实例分配一个随机 GUID。

class ProjectTask {
    [string] $Name
    [string] $Description
    [string] $Guid = (New-Guid).Guid
}

[ProjectTask]::new()
Name Description Guid
---- ----------- ----
                 aa96350c-358d-465c-96d1-a49949219eec

隐藏属性和静态属性也可以具有默认值。

隐藏的属性

可以通过使用hidden关键字 (keyword)声明类的属性来隐藏类的属性。 隐藏的类属性包括:

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

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

静态属性

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

  • 始终可用,独立于类实例化。
  • 在类的所有实例之间共享。
  • 始终可用。
  • 可修改。 可以更新静态属性。 默认情况下,它们不可变。
  • 整个会话范围的实时直播。

重要

PowerShell 中定义的类的静态属性不可变。 他们可以

派生类属性

当类派生自基类时,它将继承基类的属性。 基类上定义的任何属性(包括隐藏属性)都可用于派生类。

派生类可以通过在类定义中重新定义继承的属性来替代继承的属性。 派生类上的属性使用重新定义的类型和默认值(如果有)。 如果继承的属性定义了默认值并且重新定义的属性没有,则继承的属性没有默认值。

如果派生类不重写静态属性,则通过派生类访问静态属性将访问基类的静态属性。 通过派生类修改属性值会修改基类上的值。 任何其他不重写静态属性的派生类也使用基类上的属性的值。 更新不重写该属性的类中继承的静态属性的值可能对派生自同一基类的类产生意外的影响。

以下示例显示了派生类上的静态属性和实例属性的行为。

class BaseClass {
    static [string] $StaticProperty = 'Static'
    [string] $InstanceProperty = 'Instance'
}
class DerivedClassA : BaseClass     {}
class DerivedClassB : BaseClass     {}
class DerivedClassC : DerivedClassB {
    [string] $InstanceProperty
}
class DerivedClassD : BaseClass {
    static [string] $StaticProperty = 'Override'
    [string] $InstanceProperty = 'Override'
}

"Base instance      => $([BaseClass]::new().InstanceProperty)"
"Derived instance A => $([DerivedClassA]::new().InstanceProperty)"
"Derived instance B => $([DerivedClassB]::new().InstanceProperty)"
"Derived instance C => $([DerivedClassC]::new().InstanceProperty)"
"Derived instance D => $([DerivedClassD]::new().InstanceProperty)"
Base instance      => Instance
Derived instance A => Instance
Derived instance B => Instance
Derived instance C =>
Derived instance D => Override

DerivedClassCInstanceProperty 是一个空字符串,因为类在未设置默认值的情况下重新定义属性。 对于 DerivedClassD ,值是因为 Override 类将该字符串定义为默认值的属性。

"Base static        => $([BaseClass]::StaticProperty)"
"Derived static A   => $([DerivedClassA]::StaticProperty)"
"Derived static B   => $([DerivedClassB]::StaticProperty)"
"Derived static C   => $([DerivedClassC]::StaticProperty)"
"Derived static D   => $([DerivedClassD]::StaticProperty)"
Base static        => Static
Derived static A   => Static
Derived static B   => Static
Derived static C   => Static
Derived static D   => Override

除 DerivedClassD,派生类的静态属性的值与基类相同,因为它们不会重新定义属性。 这甚至适用于从 DerivedClassB 继承的 DerivedClassC,而不是直接从 BaseClass 继承

[DerivedClassA]::StaticProperty = 'Updated from A'
"Base static        => $([BaseClass]::StaticProperty)"
"Derived static A   => $([DerivedClassA]::StaticProperty)"
"Derived static B   => $([DerivedClassB]::StaticProperty)"
"Derived static C   => $([DerivedClassC]::StaticProperty)"
"Derived static D   => $([DerivedClassD]::StaticProperty)"
Base static        => Updated from A
Derived static A   => Updated from A
Derived static B   => Updated from A
Derived static C   => Updated from A
Derived static D   => Override

通过 DerivedClassA 访问和修改 StaticProperty,更改的值会影响派生类D 以外的每个类。

有关类继承的详细信息,包括一个全面的示例,请参阅 about_Classes_Inheritance

使用属性特性

PowerShell 包括多个属性类,可用于增强数据类型信息并验证分配给属性的数据。 通过验证属性,可以测试给定给属性的值是否满足定义的要求。 赋值时会触发验证。

有关可用属性的详细信息,请参阅 about_Functions_Advanced_Parameters

使用 Update-TypeData 定义实例属性

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

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

class <ClassName> {
    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberName = '<PropertyName>'
            MemberType = '<PropertyType>'
            Value      = <ValueDefinition>
        }
    )

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

提示

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

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

定义别名属性

在类属性声明上使用时,Alias 属性无效。 PowerShell 仅使用该属性来定义 cmdlet、参数和函数名称的别名。

若要定义类属性的别名,请与 MemberType 一起使用Update-TypeDataAliasProperty

例如,OperablePair 类的定义分别使用别名 LeftHandSideRightHandSide 定义两个整数属性 xy

class OperablePair {
    [int] $x
    [int] $y

    static [hashtable[]] $MemberDefinitions = @(
            @{
                MemberType = 'AliasProperty'
                MemberName = 'LeftHandSide'
                Value      = 'x'
            }
            @{
                MemberType = 'AliasProperty'
                MemberName = 'RightHandSide'
                Value      = 'y'
            }
    )

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

    OperablePair() {}

    OperablePair([int]$x, [int]$y) {
        $this.x = $x
        $this.y = $y
    }

    # Math methods for the pair of values
    [int]   GetSum()        { return $this.x + $this.y }
    [int]   GetProduct()    { return $this.x * $this.y }
    [int]   GetDifference() { return $this.x - $this.y }
    [float] GetQuotient()   { return $this.x / $this.y }
    [int]   GetModulus()    { return $this.x % $this.y }
}

定义别名后,用户可以使用任一名称访问属性。

$pair = [OperablePair]@{ x = 8 ; RightHandSide = 3 }

"$($pair.x) % $($pair.y) = $($pair.GetModulus())"

$pair.LeftHandSide  = 3
$pair.RightHandSide = 2
"$($pair.x) x $($pair.y) = $($pair.GetProduct())"
8 % 3 = 2

3 x 2 = 6

定义计算属性

若要定义引用其他属性的值的属性,请将 Update-TypeData cmdlet 与 MemberType 一起使用ScriptProperty

例如,Budget 类的此定义将“支出”和“收入”属性定义为浮点数数组。 它使用 Update-TypeData cmdlet 定义总支出、总收入和净收入的计算属性。

class Budget {
    [float[]] $Expenses
    [float[]] $Revenues

    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberType = 'ScriptProperty'
            MemberName = 'TotalExpenses'
            Value      = { ($this.Expenses | Measure-Object -Sum).Sum }
        }
        @{
            MemberType = 'ScriptProperty'
            MemberName = 'TotalRevenues'
            Value      = { ($this.Revenues | Measure-Object -Sum).Sum }
        }
        @{
            MemberType = 'ScriptProperty'
            MemberName = 'NetIncome'
            Value      = { $this.TotalRevenues - $this.TotalExpenses }
        }
    )

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

    Budget() {}

    Budget($Expenses, $Revenues) {
        $this.Expenses = $Expenses
        $this.Revenues = $Revenues
    }
}

[Budget]::new()

[Budget]@{
    Expenses = @(2500, 1931, 3700)
    Revenues = @(2400, 2100, 4150)
}
TotalExpenses : 0
TotalRevenues : 0
NetIncome     : 0
Expenses      :
Revenues      :

TotalExpenses : 8131
TotalRevenues : 8650
NetIncome     : 519
Expenses      : {2500, 1931, 3700}
Revenues      : {2400, 2100, 4150}

使用自定义 get 和 set 逻辑定义属性

PowerShell 类属性无法直接定义自定义 getter 和 setter 逻辑。 可以通过使用关键字 (keyword)定义后盾属性hidden,并使用Update-TypeData自定义逻辑定义可见属性来获取和设置值来近似此功能。

按照约定,使用下划线前缀定义隐藏后盾属性名称,并使用 camel 大小写。 例如,而不是 TaskCount命名隐藏后盾属性 _taskCount

在此示例中,ProjectSize 类定义一个名为_value隐藏整数属性。 它使用自定义逻辑定义值ScriptProperty来获取和设置_value属性。 setter scriptblock 处理将项目的字符串表示形式转换为正确的大小。

class ProjectSize {
    hidden [ValidateSet(0, 1, 2, 3)] [int] $_value

    static [hashtable[]] $MemberDefinitions = @(
        @{
            MemberType  = 'ScriptProperty'
            MemberName  = 'Value'
            Value       = { $this._value } # Getter
            SecondValue = {                # Setter
                $ProposedValue = $args[0]

                if ($ProposedValue -is [string]) {
                    switch ($ProposedValue) {
                        'Small'  { $this._value = 1 ; break }
                        'Medium' { $this._value = 2 ; break }
                        'Large'  { $this._value = 3 ; break }
                        default  { throw "Unknown size '$ProposedValue'" }
                    }
                } else {
                    $this._value = $ProposedValue
                }
            }
        }
    )

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

    ProjectSize()              {}
    ProjectSize([int]$Size)    { $this.Value = $Size }
    ProjectSize([string]$Size) { $this.Value = $Size }

    [string] ToString() {
        $Output = switch ($this._value) {
            1       { 'Small'     }
            2       { 'Medium'    }
            3       { 'Large'     }
            default { 'Undefined' }
        }

        return $Output
    }
}

定义自定义 getter 和 setter 后,可以将 Value 属性设置为整数或字符串。

$size = [ProjectSize]::new()
"The initial size is: $($size._value), $size"

$size.Value = 1
"The defined size is: $($size._value), $size"

$Size.Value += 1
"The updated size is: $($size._value), $size"

$Size.Value = 'Large'
"The final size is:   $($size._value), $size"
The initial size is: 0, Undefined

The defined size is: 1, Small

The updated size is: 2, Medium

The final size is:   3, Large

限制

PowerShell 类属性具有以下限制:

  • 静态属性始终可变。 PowerShell 类无法定义不可变的静态属性。

    解决方法:无。

  • 属性不能使用 ValidateScript 属性,因为类属性属性参数必须是常量。

    解决方法:定义继承自 ValidateArgumentsAttribute 类型的类,并改用该属性。

  • 直接声明的属性无法定义自定义 getter 和 setter 实现。

    解决方法:定义隐藏属性并用于 Update-TypeData 定义可见 getter 和 setter 逻辑。

  • 属性不能使用 Alias 属性。 该属性仅适用于参数、cmdlet 和函数。

    解决方法:使用 Update-TypeData cmdlet 在类构造函数中定义别名。

  • 使用 ConvertTo-Json cmdlet 将 PowerShell 类转换为 JSON 时,输出 JSON 包括所有隐藏的属性及其值。

    解决方法:无

另请参阅