about_Classes

简短说明

介绍如何使用类创建自己的自定义类型。

长说明

PowerShell 5.0 添加了用于定义类和其他用户定义的类型的正式语法。 通过添加类,开发人员和 IT 专业人员能够接受 PowerShell,以便更广泛的用例。 它简化了 PowerShell 项目的开发,并加速了管理图面的覆盖。

类声明是一个蓝图,用于在运行时创建对象的实例。 定义类时,类名是类型的名称。 例如,如果声明名为 Device 的类并将变量 $dev 初始化为 Device 的新实例, $dev 则为 Device 类型的对象或实例。 设备的每个实例在其属性中可以具有不同的值。

支持的方案

  • 使用熟悉的对象导向编程语义(如类、属性、方法、继承等)在 PowerShell 中定义自定义类型。
  • 使用 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>])

注意

使用 [<class-name>]::new() 语法时,类名周围的括号是必需的。 括号指示 PowerShell 的类型定义。

示例语法和用法

此示例显示了创建可用类所需的最小语法。

class Device {
    [string]$Brand
}

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

类属性

属性是在类范围内声明的变量。 属性可以是任何内置类型或另一类的实例。 类在属性数量方面没有限制。

包含简单属性的示例类

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

$device = [Device]::new()
$device.Brand = "Fabrikam, Inc."
$device.Model = "Fbk5040"
$device.VendorSku = "5072641000"

$device
Brand          Model   VendorSku
-----          -----   ---------
Fabrikam, Inc. Fbk5040 5072641000

类属性中的复杂类型示例

此示例使用 Device 类定义空 Rack 类。 此示例遵循此示例,演示如何将设备添加到机架,以及如何从预装机架开始。

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

class Rack {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new(8)

}

$rack = [Rack]::new()

$rack

Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, $null, $null...}


类方法

方法定义类可以执行的操作。 方法可能采用提供输入数据的参数。 方法可以返回输出。 方法返回的数据可以是任何定义的数据类型。

定义类的方法时,可以使用 $this 自动变量引用当前类对象。 这样,就可以访问当前类中定义的属性和其他方法。

具有属性和方法的示例简单类

扩展 Rack 类以向其添加或删除设备。

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    [string]ToString(){
        return ("{0}|{1}|{2}" -f $this.Brand, $this.Model, $this.VendorSku)
    }
}

class Rack {
    [int]$Slots = 8
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void]RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }

    [int[]] GetAvailableSlots(){
        [int]$i = 0
        return @($this.Devices.foreach{ if($_ -eq $null){$i}; $i++})
    }
}

$rack = [Rack]::new()

$device = [Device]::new()
$device.Brand = "Fabrikam, Inc."
$device.Model = "Fbk5040"
$device.VendorSku = "5072641000"

$rack.AddDevice($device, 2)

$rack
$rack.GetAvailableSlots()

Slots     : 8
Devices   : {$null, $null, Fabrikam, Inc.|Fbk5040|5072641000, $null…}
Brand     :
Model     :
VendorSku :
AssetId   :

0
1
3
4
5
6
7

类方法中的输出

方法应定义返回类型。 如果方法不返回输出,则输出类型应为 [void]

在类方法中,除了语句中 return 提到的对象外,不会向管道发送任何对象。 代码中没有意外输出到管道。

注意

这与 PowerShell 函数处理输出的方式基本不同,一切都会转到管道。

从类方法内部写入错误流的非终止错误不会通过传递。 必须使用 throw 该错误来显示终止错误。 Write-*使用 cmdlet,仍可以从类方法中写入 PowerShell 的输出流。 但是,应避免这种情况,以便该方法仅使用语句 return 发出对象。

方法输出

此示例演示了从类方法到管道的意外输出,但语句除外 return

class FunWithIntegers
{
    [int[]]$Integers = 0..10

    [int[]]GetOddIntegers(){
        return $this.Integers.Where({ ($_ % 2) })
    }

    [void] GetEvenIntegers(){
        # this following line doesn't go to the pipeline
        $this.Integers.Where({ ($_ % 2) -eq 0})
    }

    [string]SayHello(){
        # this following line doesn't go to the pipeline
        "Good Morning"

        # this line goes to the pipeline
        return "Hello World"
    }
}

$ints = [FunWithIntegers]::new()
$ints.GetOddIntegers()
$ints.GetEvenIntegers()
$ints.SayHello()
1
3
5
7
9
Hello World

构造函数

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

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

构造函数基本语法

在此示例中,Device 类使用属性和构造函数定义。 若要使用此类,用户需要为构造函数中列出的参数提供值。

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$device = [Device]::new(
    "Fabrikam, Inc.",
    "Fbk5040",
    "5072641000"
)

$device
Brand          Model   VendorSku
-----          -----   ---------
Fabrikam, Inc. Fbk5040 5072641000

包含多个构造函数的示例

在此示例中, 设备 类使用属性、默认构造函数和用于初始化实例的构造函数进行定义。

默认构造函数将 品牌 设置为 “未定义”,并将 模型供应商 SKU 保留为 null 值。

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(){
        $this.Brand = 'Undefined'
    }

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$someDevice = [Device]::new()
[Device]$server = [Device]::new(
    "Fabrikam, Inc.",
    "Fbk5040",
    "5072641000"
)

$someDevice, $server
Brand          Model   VendorSku
-----          -----   ---------
Undefined
Fabrikam, Inc. Fbk5040 5072641000

隐藏属性

hidden 属性隐藏属性或方法。 该属性或方法仍可供用户访问,并且可在对象可用的所有范围内使用。 隐藏成员从 Get-Member cmdlet 中隐藏,不能在类定义之外使用选项卡完成或 IntelliSense 显示。

有关详细信息,请参阅 About_hidden

使用隐藏属性的示例

创建 Rack 对象时,设备的槽数是固定值,不应随时更改。 此值在创建时是已知的。

使用隐藏属性,开发人员可以保持隐藏的槽数,并防止意外更改机架的大小。

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    [int] hidden $Slots = 8
    [string]$Brand
    [string]$Model
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)
    }
}

[Rack]$r1 = [Rack]::new("Fabrikam, Inc.", "Fbk5040", 16)

$r1
$r1.Devices.Length
$r1.Slots
Devices                       Brand          Model
-------                       -----          -----
{$null, $null, $null, $null…} Fabrikam, Inc. Fbk5040
16
16

请注意 ,输出$r1 未显示 Slots 属性。 但是,构造函数更改了大小。

静态属性

static 属性定义类中存在的属性或方法,并且不需要任何实例。

静态属性始终可用,独立于类实例化。 静态属性在类的所有实例之间共享。 静态方法始终可用。 整个会话范围的所有静态属性都实时显示。

使用静态属性和方法的示例

假设此处实例化的机架存在于数据中心。 因此,你想要跟踪代码中的机架。

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    hidden [int] $Slots = 8
    static [Rack[]]$InstalledRacks = @()
    [string]$Brand
    [string]$Model
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [string]$id, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.AssetId = $id
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)

        ## add rack to installed racks
        [Rack]::InstalledRacks += $this
    }

    static [void]PowerOffRacks(){
        foreach ($rack in [Rack]::InstalledRacks) {
            Write-Warning ("Turning off rack: " + ($rack.AssetId))
        }
    }
}

测试静态属性和方法存在

PS> [Rack]::InstalledRacks.Length
0

PS> [Rack]::PowerOffRacks()

PS> (1..10) | ForEach-Object {
>>   [Rack]::new("Adatum Corporation", "Standard-16",
>>     $_.ToString("Std0000"), 16)
>> } > $null

PS> [Rack]::InstalledRacks.Length
10

PS> [Rack]::InstalledRacks[3]
Brand              Model       AssetId Devices
-----              -----       ------- -------
Adatum Corporation Standard-16 Std0004 {$null, $null, $null, $null...}

PS> [Rack]::PowerOffRacks()
WARNING: Turning off rack: Std0001
WARNING: Turning off rack: Std0002
WARNING: Turning off rack: Std0003
WARNING: Turning off rack: Std0004
WARNING: Turning off rack: Std0005
WARNING: Turning off rack: Std0006
WARNING: Turning off rack: Std0007
WARNING: Turning off rack: Std0008
WARNING: Turning off rack: Std0009
WARNING: Turning off rack: Std0010

请注意,每次运行此示例时,机架数都会增加。

属性验证属性

通过验证属性,可以测试给定属性的值是否满足定义的要求。 验证在分配值的那一刻触发。 请参阅 about_functions_advanced_parameters

使用验证属性的示例

class Device {
    [ValidateNotNullOrEmpty()][string]$Brand
    [ValidateNotNullOrEmpty()][string]$Model
}

[Device]$dev = [Device]::new()

Write-Output "Testing dev"
$dev

$dev.Brand = ""
Testing dev

Brand Model
----- -----

Exception setting "Brand": "The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again."
At C:\tmp\Untitled-5.ps1:11 char:1
+ $dev.Brand = ""
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

PowerShell 类中的继承

可以通过创建派生自现有类的新类来扩展类。 派生类继承基类的属性。 可以根据需要添加或替代方法和属性。

PowerShell 不支持多个继承。 类不能从多个类继承。 但是,可以使用接口实现此目的。

继承实现由 : 运算符定义;这意味着扩展此类或实现这些接口。 派生类应始终在类声明中最左侧。

使用简单继承语法的示例

此示例演示简单的 PowerShell 类继承语法。

Class Derived : Base {...}

本示例演示基类后面的接口声明的继承。

Class Derived : Base, Interface {...}

PowerShell 类中的简单继承示例

在此示例中,前面示例中使用的 RackDevice 类定义得更好:避免属性重复、更好地对齐通用属性并重复使用通用业务逻辑。

数据中心中的大多数对象都是公司资产,因此可以开始将其跟踪为资产。 设备类型由 DeviceType 枚举定义,有关枚举的详细信息,请参阅 about_Enum

在我们的示例中,我们仅定义 RackComputeServer;类的 Device 这两个扩展。

enum DeviceType {
    Undefined = 0
    Compute = 1
    Storage = 2
    Networking = 4
    Communications = 8
    Power = 16
    Rack = 32
}

class Asset {
    [string]$Brand
    [string]$Model
}

class Device : Asset {
    hidden [DeviceType]$devtype = [DeviceType]::Undefined
    [string]$Status

    [DeviceType] GetDeviceType(){
        return $this.devtype
    }
}

class ComputeServer : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Compute
    [string]$ProcessorIdentifier
    [string]$Hostname
}

class Rack : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Rack
    hidden [int]$Slots = 8

    [string]$Datacenter
    [string]$Location
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack (){
        ## Just create the default rack with 8 slots
    }

    Rack ([int]$s){
        ## Add argument validation logic here
        $this.Devices = [Device[]]::new($s)
    }

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void] RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }
}

$FirstRack = [Rack]::new(16)
$FirstRack.Status = "Operational"
$FirstRack.Datacenter = "PNW"
$FirstRack.Location = "F03R02.J10"

(0..15).ForEach({
    $ComputeServer = [ComputeServer]::new()
    $ComputeServer.Brand = "Fabrikam, Inc."       ## Inherited from Asset
    $ComputeServer.Model = "Fbk5040"              ## Inherited from Asset
    $ComputeServer.Status = "Installed"           ## Inherited from Device
    $ComputeServer.ProcessorIdentifier = "x64"    ## ComputeServer
    $ComputeServer.Hostname = ("r1s" + $_.ToString("000")) ## ComputeServer
    $FirstRack.AddDevice($ComputeServer, $_)
  })

$FirstRack
$FirstRack.Devices
Datacenter : PNW
Location   : F03R02.J10
Devices    : {r1s000, r1s001, r1s002, r1s003...}
Status     : Operational
Brand      :
Model      :

ProcessorIdentifier : x64
Hostname            : r1s000
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

ProcessorIdentifier : x64
Hostname            : r1s001
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

<... content truncated here for brevity ...>

ProcessorIdentifier : x64
Hostname            : r1s015
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

调用基类构造函数

若要从子类调用基类构造函数,请添加 base 关键字。

class Person {
    [int]$Age

    Person([int]$a)
    {
        $this.Age = $a
    }
}

class Child : Person
{
    [string]$School

    Child([int]$a, [string]$s ) : base($a) {
        $this.School = $s
    }
}

[Child]$littleOne = [Child]::new(10, "Silver Fir Elementary School")

$littleOne.Age

10

调用基类方法

若要重写子类中的现有方法,请使用同一名称和签名声明方法。

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
}

[ChildClass1]::new().days()

2

若要从重写的实现调用基类方法,请强制转换为基类 ([baseclass]$this) 调用。

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
    [int]basedays() {return ([BaseClass]$this).days()}
}

[ChildClass1]::new().days()
[ChildClass1]::new().basedays()

2
1

从接口继承

PowerShell 类可以使用用于扩展基类的相同继承语法实现接口。 由于接口允许多个继承,实现接口的 PowerShell 类可能会从多个类型继承,方法是将冒号 () : 后的类型名称与逗号分隔 (,) 。 实现接口的 PowerShell 类必须实现该接口的所有成员。 省略实现接口成员会导致脚本中的分析时错误。

注意

PowerShell 目前不支持在 PowerShell 脚本中声明新接口。

class MyComparable : System.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

class MyComparableBar : bar, System.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

从 PowerShell 模块导入类

Import-Module#requires和语句仅导入模块函数、别名和变量,如模块定义。 不导入类。 该 using module 语句导入模块中定义的类。 如果模块未在当前会话中加载,则 using 语句会失败。 有关该语句的详细信息 using ,请参阅 about_Using

using module 语句从脚本模块或二进制模块的 ModuleToProcess 根模块 () 导入类。 它不一致地将嵌套模块中定义的类或脚本中定义的类导入到模块中。 应在根模块中定义要提供给模块外部的用户使用的类。

在开发过程中加载新更改的代码

在开发脚本模块期间,通常会更改代码,然后使用 Force 参数加载模块Import-Module的新版本。 这仅适用于根模块中函数的更改。 Import-Module 不重新加载任何嵌套模块。 此外,无法加载任何更新的类。

若要确保运行最新版本,必须使用 cmdlet 卸载模块 Remove-ModuleRemove-Module 删除根模块、所有嵌套模块以及模块中定义的任何类。 然后,可以使用和语句重新加载模块和using moduleImport-Module

另一种常见的开发做法是将代码分成不同的文件。 如果在一个文件中具有使用在另一个模块中定义的类的函数,则应使用 using module 语句来确保函数具有所需的类定义。

类成员不支持 PSReference 类型

[ref] 类型强制转换与类成员无提示方式配合使用会失败。 使用 [ref] 参数的 API 不能与类成员一起使用。 PSReference 类旨在支持 COM 对象。 COM 对象具有需要按引用传入值的情况。

有关类型 [ref] 的详细信息,请参阅 PSReference 类

请参阅