Сведения о классах

Краткое описание

Описывает, как можно использовать классы для создания собственных пользовательских типов.

Подробное описание

PowerShell 5.0 добавляет формальный синтаксис для определения классов и других пользовательских типов. Добавление классов позволяет разработчикам и ИТ-специалистам использовать PowerShell для широкого спектра вариантов использования. Это упрощает разработку артефактов PowerShell и ускоряет покрытие поверхностей управления.

Объявление класса — это схема, используемая для создания экземпляров объектов во время выполнения. При определении класса имя класса — это имя типа. Например, если объявить класс с именем Device и инициализировать переменную $dev для нового экземпляра Device, $dev это объект или экземпляр типа Device. Каждый экземпляр устройства может иметь разные значения в своих свойствах.

Поддерживаемые сценарии

  • Определите пользовательские типы в PowerShell с помощью знакомой объектно-ориентированной семантики программирования, таких как классы, свойства, методы, наследование и т. д.
  • Типы отладки с помощью языка PowerShell.
  • Создание и обработка исключений с помощью формальных механизмов.
  • Определите ресурсы DSC и связанные с ними типы с помощью языка PowerShell.

Синтаксис

Классы объявляются с помощью следующего синтаксиса:

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 = "Microsoft"
$dev
Brand
-----
Microsoft

Свойства класса

Свойства — это переменные, объявленные в области класса. Свойство может иметь любой встроенный тип или экземпляр другого класса. Классы не имеют ограничений в количестве свойств, которые у них есть.

Пример класса с простыми свойствами

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

$device = [Device]::new()
$device.Brand = "Microsoft"
$device.Model = "Surface Pro 4"
$device.VendorSku = "5072641000"

$device
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Примеры сложных типов в свойствах класса

В этом примере определяется пустой класс Rack с помощью класса Device . В примерах ниже показано, как добавить устройства в стойку и как начать с предварительно загруженной стойки.

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...}


Методы класса

Методы определяют действия, которые может выполнить класс. Методы могут принимать параметры, предоставляющие входные данные. Методы могут возвращать выходные данные. Данные, возвращаемые методом, могут быть любым определенным типом данных.

Пример простого класса со свойствами и методами

Расширение класса 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()

$surface = [Device]::new()
$surface.Brand = "Microsoft"
$surface.Model = "Surface Pro 4"
$surface.VendorSku = "5072641000"

$rack.AddDevice($surface, 2)

$rack
$rack.GetAvailableSlots()

Slots     : 8
Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, Microsoft|Surface Pro 4|5072641000, $null...}

0
1
3
4
5
6
7

Выходные данные в методах класса

Методы должны иметь определенный тип возвращаемого значения. Если метод не возвращает выходные данные, тип выходных данных должен быть [void].

В методах класса объекты не отправляются в конвейер, кроме упомянутых в инструкции return . Нет случайных выходных данных в конвейер из кода.

Примечание

Это существенно отличается от того, как функции PowerShell обрабатывают выходные данные, где все идет в конвейер.

Выходные данные метода

В этом примере не показаны случайные выходные данные в конвейер из методов класса, кроме инструкции 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]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$surface
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Пример с несколькими конструкторами

В этом примере класс Device определяется свойствами, конструктором по умолчанию и конструктором для инициализации экземпляра.

Конструктор по умолчанию устанавливает для торговой маркизначение Undefined и оставляет модель и номер 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]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$somedevice
$surface
Brand       Model           VendorSku
-----       -----           ---------
Undefined
Microsoft   Surface Pro 4   5072641000

Скрытый атрибут

Атрибут hidden делает свойство или метод менее видимым. Свойство или метод по-прежнему доступны пользователю и доступны во всех областях, в которых доступен объект. Скрытые члены скрыты от командлета Get-Member и не могут отображаться с помощью завершения табуляции или IntelliSense за пределами определения класса.

Пример использования скрытых атрибутов

При создании объекта 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("Microsoft", "Surface Pro 4", 16)

$r1
$r1.Devices.Length
$r1.Slots
Brand     Model         Devices
-----     -----         -------
Microsoft Surface Pro 4 {$null, $null, $null, $null...}
16
16

Свойство "Слоты" не отображается в $r1 выходных данных. Однако размер был изменен конструктором.

Статический атрибут

Атрибут 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

В этом примере классы стоек и устройств , используемые в предыдущих примерах, лучше определяются таким образом: избегайте повторений свойств, лучше выравнивайте общие свойства и повторно используйте общую бизнес-логику.

Большинство объектов в центре обработки данных являются активами компании, что имеет смысл начать отслеживание их в качестве активов. Типы устройств определяются 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

Интерфейсы

Синтаксис объявления интерфейсов аналогичен C#. Интерфейсы можно объявлять после базовых типов или сразу после двоеточия (:), если базовый тип не указан. Разделите все имена типов запятыми.

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 .

См. также раздел