Поделиться через


about_Classes

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

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

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

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

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

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

  • Определение пользовательских типов в 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>])
[$<variable-name> =] [<class-name>]@{[<class-property-hashtable>]}

Примечание

При использовании синтаксиса [<class-name>]::new() обязательны квадратные скобки вокруг имени класса. Квадратные скобки сигналит об определении типа для PowerShell.

Синтаксис хэш-структуры работает только для классов, имеющих конструктор по умолчанию, который не ожидает никаких параметров. Он создает экземпляр класса с конструктором по умолчанию, а затем назначает пары "ключ-значение" свойствам экземпляра. Если какой-либо ключ в хэш-сводке не является допустимым именем свойства, PowerShell выдает ошибку.

Примеры

Пример 1. Минимальное определение

В этом примере показан минимальный синтаксис, необходимый для создания пригодного для использования класса.

class Device {
    [string]$Brand
}

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

Пример 2. Класс с элементами экземпляра

В этом примере определяется класс Book с несколькими свойствами, конструкторами и методами. Каждый определенный элемент является элементом экземпляра , а не статическим элементом. Доступ к свойствам и методам можно получить только через созданный экземпляр класса .

class Book {
    # Class properties
    [string]   $Title
    [string]   $Author
    [string]   $Synopsis
    [string]   $Publisher
    [datetime] $PublishDate
    [int]      $PageCount
    [string[]] $Tags
    # Default constructor
    Book() { $this.Init(@{}) }
    # Convenience constructor from hashtable
    Book([hashtable]$Properties) { $this.Init($Properties) }
    # Common constructor for title and author
    Book([string]$Title, [string]$Author) {
        $this.Init(@{Title = $Title; Author = $Author })
    }
    # Shared initializer method
    [void] Init([hashtable]$Properties) {
        foreach ($Property in $Properties.Keys) {
            $this.$Property = $Properties.$Property
        }
    }
    # Method to calculate reading time as 2 minutes per page
    [timespan] GetReadingTime() {
        if ($this.PageCount -le 0) {
            throw 'Unable to determine reading time from page count.'
        }
        $Minutes = $this.PageCount * 2
        return [timespan]::new(0, $Minutes, 0)
    }
    # Method to calculate how long ago a book was published
    [timespan] GetPublishedAge() {
        if (
            $null -eq $this.PublishDate -or
            $this.PublishDate -eq [datetime]::MinValue
        ) { throw 'PublishDate not defined' }

        return (Get-Date) - $this.PublishDate
    }
    # Method to return a string representation of the book
    [string] ToString() {
        return "$($this.Title) by $($this.Author) ($($this.PublishDate.Year))"
    }
}

В следующем фрагменте кода создается экземпляр класса и показано его поведение. После создания экземпляра класса Book в примере используются GetReadingTime() методы и GetPublishedAge() для записи сообщения о книге.

$Book = [Book]::new(@{
    Title       = 'The Hobbit'
    Author      = 'J.R.R. Tolkien'
    Publisher   = 'George Allen & Unwin'
    PublishDate = '1937-09-21'
    PageCount   = 310
    Tags        = @('Fantasy', 'Adventure')
})

$Book
$Time = $Book.GetReadingTime()
$Time = @($Time.Hours, 'hours and', $Time.Minutes, 'minutes') -join ' '
$Age  = [Math]::Floor($Book.GetPublishedAge().TotalDays / 365.25)

"It takes $Time to read $Book,`nwhich was published $Age years ago."
Title       : The Hobbit
Author      : J.R.R. Tolkien
Synopsis    :
Publisher   : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount   : 310
Tags        : {Fantasy, Adventure}

It takes 10 hours and 20 minutes to read The Hobbit by J.R.R. Tolkien (1937),
which was published 86 years ago.

Пример 3. Класс со статическими элементами

Класс BookList в этом примере основан на классе Book в примере 2. Хотя класс BookList нельзя пометить как статический, реализация определяет только статическое свойство Books и набор статических методов для управления этим свойством.

class BookList {
    # Static property to hold the list of books
    static [System.Collections.Generic.List[Book]] $Books
    # Static method to initialize the list of books. Called in the other
    # static methods to avoid needing to explicit initialize the value.
    static [void] Initialize()             { [BookList]::Initialize($false) }
    static [bool] Initialize([bool]$force) {
        if ([BookList]::Books.Count -gt 0 -and -not $force) {
            return $false
        }

        [BookList]::Books = [System.Collections.Generic.List[Book]]::new()

        return $true
    }
    # Ensure a book is valid for the list.
    static [void] Validate([book]$Book) {
        $Prefix = @(
            'Book validation failed: Book must be defined with the Title,'
            'Author, and PublishDate properties, but'
        ) -join ' '
        if ($null -eq $Book) { throw "$Prefix was null" }
        if ([string]::IsNullOrEmpty($Book.Title)) {
            throw "$Prefix Title wasn't defined"
        }
        if ([string]::IsNullOrEmpty($Book.Author)) {
            throw "$Prefix Author wasn't defined"
        }
        if ([datetime]::MinValue -eq $Book.PublishDate) {
            throw "$Prefix PublishDate wasn't defined"
        }
    }
    # Static methods to manage the list of books.
    # Add a book if it's not already in the list.
    static [void] Add([Book]$Book) {
        [BookList]::Initialize()
        [BookList]::Validate($Book)
        if ([BookList]::Books.Contains($Book)) {
            throw "Book '$Book' already in list"
        }

        $FindPredicate = {
            param([Book]$b)

            $b.Title -eq $Book.Title -and
            $b.Author -eq $Book.Author -and
            $b.PublishDate -eq $Book.PublishDate
        }.GetNewClosure()
        if ([BookList]::Books.Find($FindPredicate)) {
            throw "Book '$Book' already in list"
        }

        [BookList]::Books.Add($Book)
    }
    # Clear the list of books.
    static [void] Clear() {
      [BookList]::Initialize()
      [BookList]::Books.Clear()
    }
    # Find a specific book using a filtering scriptblock.
    static [Book] Find([scriptblock]$Predicate) {
        [BookList]::Initialize()
        return [BookList]::Books.Find($Predicate)
    }
    # Find every book matching the filtering scriptblock.
    static [Book[]] FindAll([scriptblock]$Predicate) {
        [BookList]::Initialize()
        return [BookList]::Books.FindAll($Predicate)
    }
    # Remove a specific book.
    static [void] Remove([Book]$Book) {
        [BookList]::Initialize()
        [BookList]::Books.Remove($Book)
    }
    # Remove a book by property value.
    static [void] RemoveBy([string]$Property, [string]$Value) {
        [BookList]::Initialize()
        $Index = [BookList]::Books.FindIndex({
            param($b)
            $b.$Property -eq $Value
        }.GetNewClosure())
        if ($Index -ge 0) {
            [BookList]::Books.RemoveAt($Index)
        }
    }
}

Теперь, когда BookList определен, книгу из предыдущего примера можно добавить в список.

$null -eq [BookList]::Books

[BookList]::Add($Book)

[BookList]::Books
True

Title       : The Hobbit
Author      : J.R.R. Tolkien
Synopsis    :
Publisher   : George Allen & Unwin
PublishDate : 9/21/1937 12:00:00 AM
PageCount   : 310
Tags        : {Fantasy, Adventure}

Следующий фрагмент кода вызывает статические методы для класса .

[BookList]::Add([Book]::new(@{
    Title       = 'The Fellowship of the Ring'
    Author      = 'J.R.R. Tolkien'
    Publisher   = 'George Allen & Unwin'
    PublishDate = '1954-07-29'
    PageCount   = 423
    Tags        = @('Fantasy', 'Adventure')
}))

[BookList]::Find({
    param ($b)

    $b.PublishDate -gt '1950-01-01'
}).Title

[BookList]::FindAll({
    param($b)

    $b.Author -match 'Tolkien'
}).Title

[BookList]::Remove($Book)
[BookList]::Books.Title

[BookList]::RemoveBy('Author', 'J.R.R. Tolkien')
"Titles: $([BookList]::Books.Title)"

[BookList]::Add($Book)
[BookList]::Add($Book)
The Fellowship of the Ring

The Hobbit
The Fellowship of the Ring

The Fellowship of the Ring

Titles:

Exception:
Line |
  84 |              throw "Book '$Book' already in list"
     |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list

Пример 4. Параллельное выполнение, повреждающее пространство выполнения

Метод ShowRunspaceId()[UnsafeClass] сообщает разные идентификаторы потоков, но один и тот же идентификатор пространства выполнения. В конечном итоге состояние сеанса повреждено, что приводит к ошибке, например Global scope cannot be removed.

# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
    static [object] ShowRunspaceId($val) {
        return [PSCustomObject]@{
            ThreadId   = [Threading.Thread]::CurrentThread.ManagedThreadId
            RunspaceId = [runspace]::DefaultRunspace.Id
        }
    }
}

$unsafe = [UnsafeClass]::new()

while ($true) {
    1..10 | ForEach-Object -Parallel {
        Start-Sleep -ms 100
        ($using:unsafe)::ShowRunspaceId($_)
    }
}

Примечание

Этот пример выполняется в бесконечном цикле. Введите CTRL+C , чтобы остановить выполнение.

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

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

Дополнительные сведения см. в разделе about_Classes_Properties.

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

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

Дополнительные сведения см. в разделе about_Classes_Methods.

Конструкторы классов

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

Дополнительные сведения см . в разделе about_Classes_Constructors.

Скрытые ключевое слово

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

Ключевое слово hidden применяется только к членам класса, а не к самому классу.

Скрытые члены класса:

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

Примечание

При скрытии перегрузки для метода этот метод удаляется из IntelliSense, результаты завершения и выходные данные по умолчанию для Get-Member. При скрытии конструктора new() параметр удаляется из IntelliSense и результатов завершения.

Дополнительные сведения о ключевое слово см. в разделе about_Hidden. Дополнительные сведения о скрытых свойствах см . в разделе about_Classes_Properties. Дополнительные сведения о скрытых методах см. в разделе about_Classes_Methods. Дополнительные сведения о скрытых конструкторах см. в разделе about_Classes_Constructors.

Static - ключевое слово

Ключевое слово static определяет свойство или метод, который существует в классе и не нуждается в экземпляре .

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

Ключевое слово static применяется только к членам класса, а не к самому классу.

Дополнительные сведения о статических свойствах см . в разделе about_Classes_Properties. Дополнительные сведения о статических методах см. в разделе about_Classes_Methods. Дополнительные сведения о статических конструкторах см. в разделе about_Classes_Constructors.

Наследование в классах PowerShell

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

PowerShell не поддерживает множественное наследование. Классы не могут наследовать непосредственно от нескольких классов.

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

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

Сходство пространств выполнения

Пространство выполнения — это операционная среда для команд, вызываемых PowerShell. Эта среда включает команды и данные, которые присутствуют в настоящее время, а также все языковые ограничения, которые применяются в настоящее время.

Класс PowerShell связан с пространством Runspace , где он создан. Использование класса PowerShell в ForEach-Object -Parallel небезопасно. Вызовы методов в классе маршалируются обратно в пространство Runspace , где он был создан, что может повредить состояние runspace или вызвать взаимоблокировку.

Иллюстрация того, как сходство пространств выполнения может вызвать ошибки, см. в примере 4.

Экспорт классов с помощью ускорителей типов

По умолчанию модули PowerShell не экспортируют автоматически классы и перечисления, определенные в PowerShell. Пользовательские типы недоступны за пределами модуля без вызова инструкции using module .

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

Примечание

При добавлении ускорителей типов в сеанс используется внутренний (не открытый) API. Использование этого API может привести к конфликтам. Описанный ниже шаблон выдает ошибку, если при импорте модуля уже существует ускоритель типов с таким же именем. Он также удаляет ускорители типов при удалении модуля из сеанса.

Этот шаблон гарантирует, что типы будут доступны в сеансе. Это не влияет на intelliSense или завершение при создании файла скрипта в VS Code. Чтобы получить предложения IntelliSense и завершения для пользовательских типов в VS Code, необходимо добавить using module оператор в начало скрипта.

В следующем шаблоне показано, как можно зарегистрировать классы и перечисления PowerShell в качестве ускорителей типов в модуле. Добавьте фрагмент кода в модуль корневого скрипта после любых определений типов. Убедитесь, $ExportableTypes что переменная содержит все типы, которые вы хотите сделать доступными для пользователей при импорте модуля. Другой код не требует редактирования.

# Define the types to export with type accelerators.
$ExportableTypes =@(
    [DefinedTypeName]
)
# Get the internal TypeAccelerators class to use its static methods.
$TypeAcceleratorsClass = [psobject].Assembly.GetType(
    'System.Management.Automation.TypeAccelerators'
)
# Ensure none of the types would clobber an existing type accelerator.
# If a type accelerator with the same name exists, throw an exception.
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get
foreach ($Type in $ExportableTypes) {
    if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
        $Message = @(
            "Unable to register type accelerator '$($Type.FullName)'"
            'Accelerator already exists.'
        ) -join ' - '

        throw [System.Management.Automation.ErrorRecord]::new(
            [System.InvalidOperationException]::new($Message),
            'TypeAcceleratorAlreadyExists',
            [System.Management.Automation.ErrorCategory]::InvalidOperation,
            $Type.FullName
        )
    }
}
# Add type accelerators for every exportable type.
foreach ($Type in $ExportableTypes) {
    $TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
# Remove type accelerators when the module is removed.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
    foreach($Type in $ExportableTypes) {
        $TypeAcceleratorsClass::Remove($Type.FullName)
    }
}.GetNewClosure()

Когда пользователи импортируют модуль, все типы, добавленные в ускорители типов для сеанса, сразу же становятся доступными для IntelliSense и завершения. Когда модуль удаляется, то же самое относятся к ускорителям типов.

Импорт классов вручную из модуля PowerShell

Import-Module#requires и инструкция импортирует только функции, псевдонимы и переменные модуля, как определено модулем. Классы не импортируются.

Если модуль определяет классы и перечисления, но не добавляет ускорители типов для этих типов, используйте инструкцию using module для их импорта.

Оператор using module импортирует классы и перечисления из корневого модуля (ModuleToProcess) модуля скрипта или двоичного модуля. Он не последовательно импортирует классы, определенные во вложенных модулях, или классы, определенные в скриптах, которые являются точками источника в корневом модуле. Определите классы, которые должны быть доступны пользователям за пределами модуля непосредственно в корневом модуле.

Дополнительные сведения об инструкции см. в usingразделе about_Using.

Загрузка нового измененного кода во время разработки

Во время разработки модуля скрипта обычно вносятся изменения в код, а затем загружают новую версию модуля с помощью Import-Module параметра Force . Перезагрузка модуля работает только для изменений функций в корневом модуле. Import-Module не перезагружает вложенные модули. Кроме того, невозможно загрузить обновленные классы.

Чтобы убедиться, что вы используете последнюю версию, необходимо запустить новый сеанс. Классы и перечисления, определенные в PowerShell и импортированные с помощью using инструкции , не могут быть выгружены.

Другой распространенной практикой разработки является разделение кода на разные файлы. Если в одном файле есть функция, которая использует классы, определенные в другом модуле, следует использовать инструкцию using module , чтобы убедиться, что функции имеют необходимые определения классов.

Тип PSReference не поддерживается членами класса

Ускоритель [ref] типов является сокращенным для класса PSReference . Использование [ref] для приведения к типу члена класса завершается сбоем без уведомления. API, использующие [ref] параметры, нельзя использовать с членами класса. Класс PSReference предназначен для поддержки COM-объектов. COM-объекты имеют случаи, когда необходимо передать значение в по ссылке.

Дополнительные сведения см. в разделе Класс PSReference.

Ограничения

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

Общие ограничения

  • Члены класса не могут использовать PSReference в качестве типа.

    Обходных решений: нет.

  • Классы PowerShell нельзя выгрузить или перезагрузить в сеансе.

    Обходной путь. Запустите новый сеанс.

  • Классы PowerShell, определенные в модуле, не импортируются автоматически.

    Решение. Добавьте определенные типы в список ускорителей типов в корневом модуле. Это делает типы доступными при импорте модуля.

  • Ключевые hidden слова и static применяются только к членам класса, но не к определению класса.

    Обходных решений: нет.

  • Классы PowerShell небезопасны для параллельного выполнения в пространствах выполнения. При вызове методов в классе PowerShell маршалирует вызовы обратно в пространство Runspace , где был создан класс, что может повредить состояние Runspace или вызвать взаимоблокировку.

    Обходных решений: нет.

Ограничения конструктора

  • Цепочка конструкторов не реализована.

    Обходное решение. Определите скрытые Init() методы и вызовите их из конструкторов.

  • Параметры конструктора не могут использовать атрибуты, включая атрибуты проверки.

    Обходной путь. Переназначьте параметры в теле конструктора с помощью атрибута проверки.

  • Параметры конструктора не могут определять значения по умолчанию. Параметры всегда являются обязательными.

    Обходных решений: нет.

  • Если какая-либо перегрузка конструктора скрыта, каждая перегрузка для конструктора также рассматривается как скрытая.

    Обходных решений: нет.

Ограничения методов

  • Параметры метода не могут использовать атрибуты, включая атрибуты проверки.

    Обходной путь. Переназначьте параметры в теле метода с помощью атрибута проверки или определите метод в статическом конструкторе с помощью командлета Update-TypeData .

  • Параметры метода не могут определять значения по умолчанию. Параметры всегда являются обязательными.

    Обходной путь. Определите метод в статическом конструкторе с помощью командлета Update-TypeData .

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

    Обходных решений: нет.

  • Если какая-либо перегрузка метода скрыта, то каждая перегрузка для этого метода тоже считается скрытой.

    Обходных решений: нет.

Ограничения свойств

  • Статические свойства всегда изменяются. Классы PowerShell не могут определять неизменяемые статические свойства.

    Обходных решений: нет.

  • Свойства не могут использовать атрибут ValidateScript , так как аргументы атрибута свойства класса должны быть константами.

    Обходной путь. Определите класс, наследуемый от типа ValidateArgumentsAttribute , и используйте этот атрибут.

  • Непосредственно объявленные свойства не могут определять пользовательские реализации метода получения и задания.

    Обходной путь. Определите скрытое свойство и используйте Update-TypeData для определения видимой логики метода получения и задания.

  • Свойства не могут использовать атрибут Alias . Атрибут применяется только к параметрам, командлетам и функциям.

    Обходной путь. Используйте Update-TypeData командлет для определения псевдонимов в конструкторах классов.

  • При преобразовании класса PowerShell в JSON с помощью командлета ConvertTo-Json выходные данные JSON включают все скрытые свойства и их значения.

    Обходной путь: нет

Ограничения на наследование

  • PowerShell не поддерживает определение интерфейсов в коде скрипта.

    Обходной путь. Определите интерфейсы в C# и сослаться на сборку, которая определяет интерфейсы.

  • Классы PowerShell могут наследоваться только от одного базового класса.

    Обходной путь. Наследование классов является транзитивным. Производный класс может наследовать от другого производного класса, чтобы получить свойства и методы базового класса.

  • При наследовании от универсального класса или интерфейса параметр типа для универсального класса уже должен быть определен. Класс не может определить себя в качестве параметра типа для класса или интерфейса.

    Обходной путь. Чтобы наследовать от универсального базового класса или интерфейса, определите пользовательский тип в другом .psm1 файле и используйте инструкцию using module для загрузки типа. При наследовании от универсального типа пользовательский тип не может использовать себя в качестве параметра типа.

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