about_Classes
Краткое описание
Описывает, как использовать классы для создания собственных настраиваемых типов.
Подробное описание
В PowerShell 5.0 добавлен формальный синтаксис для определения классов и других определяемых пользователем типов. Добавление классов позволяет разработчикам и ИТ-специалистам использовать PowerShell для более широкого спектра вариантов использования. Это упрощает разработку артефактов PowerShell и ускоряет охват поверхностей управления.
Объявление класса — это схема, используемая для создания экземпляров объектов во время выполнения. При определении класса имя класса — это имя типа. Например, если объявить класс с именем Device и инициализировать переменную $dev
для нового экземпляра Device, $dev
это объект или экземпляр типа Device. Каждый экземпляр 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...}
Методы класса
Методы определяют действия, которые может выполнить класс. Методы могут принимать параметры, предоставляющие входные данные. Методы могут возвращать выходные данные. Данные, возвращаемые методом, могут иметь любой определенный тип данных.
При определении метода для класса вы ссылаетесь на текущий объект класса с помощью автоматической переменной $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()
$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 обрабатывают выходные данные, когда все передается в конвейер.
Неустранимые ошибки, записанные в поток ошибок из метода класса, не передаются. Необходимо использовать для throw
отображения неустранимой ошибки.
Write-*
С помощью командлетов вы по-прежнему можете выполнять запись в выходные потоки 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. При определении конструктора не создается конструктор без параметров по умолчанию. Create конструктор без параметров, если он необходим.
Базовый синтаксис конструктора
В этом примере класс 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 и оставляет model и vendor-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 за пределами определения класса.
Дополнительные сведения см. в разделе 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("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
В этом примере классы Rack и Device , используемые в предыдущих примерах, лучше определены для того, чтобы избежать повторений свойств, лучше выровнять общие свойства и повторно использовать общую бизнес-логику.
Большинство объектов в центре обработки данных являются активами компании, поэтому имеет смысл начать отслеживать их как ресурсы. Типы устройств определяются перечислением. Дополнительные сведения о перечислениях DeviceType
см. в разделе about_Enum .
В нашем примере мы определяем Rack
только и ComputeServer
; оба расширения для 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
Чтобы вызвать методы базового класса из переопределенных реализаций, приведите к базовому классу ([базовый класс]$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
) модуля скрипта или двоичного модуля. Он не выполняет согласованного импорта классов, определенных во вложенных модулях, или классов, определенных в скриптах, которые находятся в модуле с точками. Классы, которые должны быть доступны пользователям за пределами модуля, должны быть определены в корневом модуле.
Загрузка нового измененного кода во время разработки
Во время разработки модуля скрипта обычно вносятся изменения в код, а затем загружают новую версию модуля с помощью Import-Module
параметра Force . Это работает только для изменений функций в корневом модуле. Import-Module
не перезагружает вложенные модули. Кроме того, невозможно загрузить обновленные классы.
Чтобы убедиться, что используется последняя версия, необходимо выгрузить модуль с помощью командлета Remove-Module
. Remove-Module
удаляет корневой модуль, все вложенные модули и все классы, определенные в модулях. Затем можно перезагрузить модуль и классы с помощью Import-Module
инструкции using module
и .
Другой распространенной практикой разработки является разделение кода на разные файлы. Если в одном файле есть функция, которая использует классы, определенные в другом модуле, следует использовать инструкцию using module
, чтобы убедиться, что функции имеют необходимые определения классов.
Тип PSReference не поддерживается членами класса
[ref]
При использовании приведения типа с членом класса не удается автоматически. API, использующие [ref]
параметры, нельзя использовать с членами класса. Класс PSReference предназначен для поддержки COM-объектов. COM-объекты имеют случаи, когда необходимо передать значение в по ссылке.
Дополнительные сведения о типе см. в [ref]
разделе Класс PSReference.