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


about_Classes_Inheritance

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

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

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

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

PowerShell поддерживает только одно наследование. Класс может наследовать только от одного класса. Но зато поддерживается транзитивное наследование, которое позволяет определить иерархию наследования для набора типов. Иными словами, тип D может наследоваться от типа C, который наследует от типа B, который наследует от типа базового класса A. Поскольку наследование является транзитивным, члены типа A доступны для типа D.

Производные классы не наследуют все члены базового класса. Следующие члены не наследуются:

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

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

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

Некоторые операторы PowerShell зависят от класса, реализующий определенный интерфейс. Например, -eq оператор проверяет равенство ссылок, только если класс не реализует интерфейс System.IEquatable . Операторы -le, -lt, -geи -gt работают только с классами, реализующими интерфейс System.IComparable .

Производный класс использует : синтаксис для расширения базового класса или реализации интерфейсов. Производный класс всегда должен быть крайне левым в объявлении класса.

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

Class Derived : Base {...}

В этом примере показано наследование с объявлением интерфейса после базового класса.

Class Derived : Base, Interface {...}

Синтаксис

Наследование классов использует следующий синтаксис:

Синтаксис одной строки

class <derived-class-name> : <base-class-or-interface-name>[, <interface-name>...] {
    <derived-class-body>
}

Пример:

# Base class only
class Derived : Base {...}
# Interface only
class Derived : System.IComparable {...}
# Base class and interface
class Derived : Base, System.IComparable {...}

Многостроковый синтаксис

class <derived-class-name> : <base-class-or-interface-name>[,
    <interface-name>...] {
    <derived-class-body>
}

Пример:

class Derived : Base,
                System.IComparable,
                System.IFormattable,
                System.IConvertible {
    # Derived class definition
}

Примеры

Пример 1. Наследование и переопределение из базового класса

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

Определение базового класса

Первый блок кода определяет PublishedWork как базовый класс. Он имеет два статических свойства: List и Artists. Затем он определяет статический RegisterWork() метод для добавления работ в свойство static List , а художников — в свойство Artists , записывая сообщение для каждой новой записи в списках.

Класс определяет три свойства экземпляра, описывающие опубликованную работу. Наконец, он определяет методы экземпляра Register() и ToString() .

class PublishedWork {
    static [PublishedWork[]] $List    = @()
    static [string[]]        $Artists = @()

    static [void] RegisterWork([PublishedWork]$Work) {
        $wName   = $Work.Name
        $wArtist = $Work.Artist
        if ($Work -notin [PublishedWork]::List) {
            Write-Verbose "Adding work '$wName' to works list"
            [PublishedWork]::List += $Work
        } else {
            Write-Verbose "Work '$wName' already registered."
        }
        if ($wArtist -notin [PublishedWork]::Artists) {
            Write-Verbose "Adding artist '$wArtist' to artists list"
            [PublishedWork]::Artists += $wArtist
        } else {
            Write-Verbose "Artist '$wArtist' already registered."
        }
    }

    static [void] ClearRegistry() {
        Write-Verbose "Clearing PublishedWork registry"
        [PublishedWork]::List    = @()
        [PublishedWork]::Artists = @()
    }

    [string] $Name
    [string] $Artist
    [string] $Category

    [void] Init([string]$WorkType) {
        if ([string]::IsNullOrEmpty($this.Category)) {
            $this.Category = "${WorkType}s"
        }
    }

    PublishedWork() {
        $WorkType = $this.GetType().FullName
        $this.Init($WorkType)
        Write-Verbose "Defined a published work of type [$WorkType]"
    }

    PublishedWork([string]$Name, [string]$Artist) {
        $WorkType    = $this.GetType().FullName
        $this.Name   = $Name
        $this.Artist = $Artist
        $this.Init($WorkType)

        Write-Verbose "Defined '$Name' by $Artist as a published work of type [$WorkType]"
    }

    PublishedWork([string]$Name, [string]$Artist, [string]$Category) {
        $WorkType    = $this.GetType().FullName
        $this.Name   = $Name
        $this.Artist = $Artist
        $this.Init($WorkType)

        Write-Verbose "Defined '$Name' by $Artist ($Category) as a published work of type [$WorkType]"
    }

    [void]   Register() { [PublishedWork]::RegisterWork($this) }
    [string] ToString() { return "$($this.Name) by $($this.Artist)" }
}

Определение производного класса без переопределений

Первый производный класс — Album. Он не переопределяет какие-либо свойства или методы. Он добавляет новое свойство экземпляра Genres, которое не существует в базовом классе.

class Album : PublishedWork {
    [string[]] $Genres   = @()
}

В следующем блоке кода показано поведение производного класса Album . Во-первых $VerbosePreference , он задает таким образом, чтобы сообщения из методов класса выводиться в консоль. Он создает три экземпляра класса , отображает их в таблице, а затем регистрирует их с помощью унаследованного статического RegisterWork() метода. Затем он вызывает тот же статический метод в базовом классе напрямую.

$VerbosePreference = 'Continue'
$Albums = @(
    [Album]@{
        Name   = 'The Dark Side of the Moon'
        Artist = 'Pink Floyd'
        Genres = 'Progressive rock', 'Psychedelic rock'
    }
    [Album]@{
        Name   = 'The Wall'
        Artist = 'Pink Floyd'
        Genres = 'Progressive rock', 'Art rock'
    }
    [Album]@{
        Name   = '36 Chambers'
        Artist = 'Wu-Tang Clan'
        Genres = 'Hip hop'
    }
)

$Albums | Format-Table
$Albums | ForEach-Object { [Album]::RegisterWork($_) }
$Albums | ForEach-Object { [PublishedWork]::RegisterWork($_) }
VERBOSE: Defined a published work of type [Album]
VERBOSE: Defined a published work of type [Album]
VERBOSE: Defined a published work of type [Album]

Genres                               Name                      Artist       Category
------                               ----                      ------       --------
{Progressive rock, Psychedelic rock} The Dark Side of the Moon Pink Floyd   Albums
{Progressive rock, Art rock}         The Wall                  Pink Floyd   Albums
{Hip hop}                            36 Chambers               Wu-Tang Clan Albums

VERBOSE: Adding work 'The Dark Side of the Moon' to works list
VERBOSE: Adding artist 'Pink Floyd' to artists list
VERBOSE: Adding work 'The Wall' to works list
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Adding work '36 Chambers' to works list
VERBOSE: Adding artist 'Wu-Tang Clan' to artists list

VERBOSE: Work 'The Dark Side of the Moon' already registered.
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Work 'The Wall' already registered.
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Work '36 Chambers' already registered.
VERBOSE: Artist 'Wu-Tang Clan' already registered.

Обратите внимание, что хотя класс Album не определяет значение для Category или каких-либо конструкторов, свойство было определено конструктором базового класса по умолчанию.

В подробном сообщении второй вызов RegisterWork() метода сообщает, что работы и художники уже зарегистрированы. Хотя первый вызов RegisterWork() был выполнен для производного класса Album , он использовал наследуемый статический метод из базового класса PublishedWork . Этот метод обновил статические свойства List и Artist в базовом классе, который производный класс не переопределил.

Следующий блок кода очищает реестр и вызывает метод экземпляра Register() для объектов Album .

[PublishedWork]::ClearRegistry()
$Albums.Register()
VERBOSE: Clearing PublishedWork registry

VERBOSE: Adding work 'The Dark Side of the Moon' to works list
VERBOSE: Adding artist 'Pink Floyd' to artists list
VERBOSE: Adding work 'The Wall' to works list
VERBOSE: Artist 'Pink Floyd' already registered.
VERBOSE: Adding work '36 Chambers' to works list
VERBOSE: Adding artist 'Wu-Tang Clan' to artists list

Метод экземпляра для объектов Album имеет тот же эффект, что и вызов статического метода в производном или базовом классе.

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

[pscustomobject]@{
    '[PublishedWork]::List'    = [PublishedWork]::List -join ",`n"
    '[Album]::List'            = [Album]::List -join ",`n"
    '[PublishedWork]::Artists' = [PublishedWork]::Artists -join ",`n"
    '[Album]::Artists'         = [Album]::Artists -join ",`n"
    'IsSame::List'             = (
        [PublishedWork]::List.Count -eq [Album]::List.Count -and
        [PublishedWork]::List.ToString() -eq [Album]::List.ToString()
    )
    'IsSame::Artists'          = (
        [PublishedWork]::Artists.Count -eq [Album]::Artists.Count -and
        [PublishedWork]::Artists.ToString() -eq [Album]::Artists.ToString()
    )
} | Format-List
[PublishedWork]::List    : The Dark Side of the Moon by Pink Floyd,
                           The Wall by Pink Floyd,
                           36 Chambers by Wu-Tang Clan
[Album]::List            : The Dark Side of the Moon by Pink Floyd,
                           The Wall by Pink Floyd,
                           36 Chambers by Wu-Tang Clan
[PublishedWork]::Artists : Pink Floyd,
                           Wu-Tang Clan
[Album]::Artists         : Pink Floyd,
                           Wu-Tang Clan
IsSame::List             : True
IsSame::Artists          : True

Определение производного класса с переопределениями

Следующий блок кода определяет класс Illustration , наследуемый от базового класса PublishedWork . Новый класс расширяет базовый класс, определяя свойство экземпляра Medium со значением Unknownпо умолчанию .

В отличие от производного класса Album , Illustration переопределяет следующие свойства и методы:

  • Он переопределяет свойство static Artists . Определение совпадает, но класс Illustration объявляет его напрямую.
  • Он переопределяет свойство экземпляра Category , задавая значение Illustrationsпо умолчанию .
  • Он переопределяет метод экземпляра ToString() таким образом, что строковое представление иллюстрации включает среду, с помощью которую он был создан.

Класс также определяет статический RegisterIllustration() метод, который сначала вызывает метод базового класса RegisterWork() , а затем добавляет его в переопределенное статическое свойство Artists в производном классе.

Наконец, класс переопределяет все три конструктора:

  1. Конструктор по умолчанию пуст, за исключением подробного сообщения, указывающего, что он создал иллюстрацию.
  2. Следующий конструктор принимает два строковых значения для имени и исполнителя, создавшего иллюстрацию. Вместо реализации логики для задания свойств Name и Artist конструктор вызывает соответствующий конструктор из базового класса.
  3. Последний конструктор принимает три строковых значения для имени, исполнителя и носителя иллюстрации. Оба конструктора пишут подробное сообщение, указывающее, что они создали иллюстрацию.
class Illustration : PublishedWork {
    static [string[]] $Artists = @()

    static [void] RegisterIllustration([Illustration]$Work) {
        $wArtist = $Work.Artist

        [PublishedWork]::RegisterWork($Work)

        if ($wArtist -notin [Illustration]::Artists) {
            Write-Verbose "Adding illustrator '$wArtist' to artists list"
            [Illustration]::Artists += $wArtist
        } else {
            Write-Verbose "Illustrator '$wArtist' already registered."
        }
    }

    [string] $Category = 'Illustrations'
    [string] $Medium   = 'Unknown'

    [string] ToString() {
        return "$($this.Name) by $($this.Artist) ($($this.Medium))"
    }

    Illustration() {
        Write-Verbose 'Defined an illustration'
    }

    Illustration([string]$Name, [string]$Artist) : base($Name, $Artist) {
        Write-Verbose "Defined '$Name' by $Artist ($($this.Medium)) as an illustration"
    }

    Illustration([string]$Name, [string]$Artist, [string]$Medium) {
        $this.Name = $Name
        $this.Artist = $Artist
        $this.Medium = $Medium

        Write-Verbose "Defined '$Name' by $Artist ($Medium) as an illustration"
    }
}

В следующем блоке кода показано поведение производного класса Illustration . Он создает три экземпляра класса , отображает их в таблице, а затем регистрирует их с помощью унаследованного статического RegisterWork() метода. Затем он вызывает тот же статический метод в базовом классе напрямую. Наконец, он записывает сообщения со списком зарегистрированных исполнителей для базового и производного классов.

$Illustrations = @(
    [Illustration]@{
        Name   = 'The Funny Thing'
        Artist = 'Wanda Gág'
        Medium = 'Lithography'
    }
    [Illustration]::new('Millions of Cats', 'Wanda Gág')
    [Illustration]::new(
      'The Lion and the Mouse',
      'Jerry Pinkney',
      'Watercolor'
    )
)

$Illustrations | Format-Table
$Illustrations | ForEach-Object { [Illustration]::RegisterIllustration($_) }
$Illustrations | ForEach-Object { [PublishedWork]::RegisterWork($_) }
"Published work artists: $([PublishedWork]::Artists -join ', ')"
"Illustration artists: $([Illustration]::Artists -join ', ')"
VERBOSE: Defined a published work of type [Illustration]
VERBOSE: Defined an illustration
VERBOSE: Defined 'Millions of Cats' by Wanda Gág as a published work of type [Illustration]
VERBOSE: Defined 'Millions of Cats' by Wanda Gág (Unknown) as an illustration
VERBOSE: Defined a published work of type [Illustration]
VERBOSE: Defined 'The Lion and the Mouse' by Jerry Pinkney (Watercolor) as an illustration

Category      Medium      Name                   Artist
--------      ------      ----                   ------
Illustrations Lithography The Funny Thing        Wanda Gág
Illustrations Unknown     Millions of Cats       Wanda Gág
Illustrations Watercolor  The Lion and the Mouse Jerry Pinkney

VERBOSE: Adding work 'The Funny Thing' to works list
VERBOSE: Adding artist 'Wanda Gág' to artists list
VERBOSE: Adding illustrator 'Wanda Gág' to artists list
VERBOSE: Adding work 'Millions of Cats' to works list
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Illustrator 'Wanda Gág' already registered.
VERBOSE: Adding work 'The Lion and the Mouse' to works list
VERBOSE: Adding artist 'Jerry Pinkney' to artists list
VERBOSE: Adding illustrator 'Jerry Pinkney' to artists list

VERBOSE: Work 'The Funny Thing' already registered.
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Work 'Millions of Cats' already registered.
VERBOSE: Artist 'Wanda Gág' already registered.
VERBOSE: Work 'The Lion and the Mouse' already registered.
VERBOSE: Artist 'Jerry Pinkney' already registered.

Published work artists: Pink Floyd, Wu-Tang Clan, Wanda Gág, Jerry Pinkney

Illustration artists: Wanda Gág, Jerry Pinkney

Подробные сообщения при создании экземпляров показывают, что:

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

Подробные сообщения от RegisterWork() метода указывают, что работы и художники уже зарегистрированы. Это связано с тем, RegisterIllustration() что метод вызывает RegisterWork() метод внутренне.

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

Последний блок кода вызывает ToString() метод для записей статического свойства List базового класса.

[PublishedWork]::List | ForEach-Object -Process { $_.ToString() }
The Dark Side of the Moon by Pink Floyd
The Wall by Pink Floyd
36 Chambers by Wu-Tang Clan
The Funny Thing by Wanda Gág (Lithography)
Millions of Cats by Wanda Gág (Unknown)
The Lion and the Mouse by Jerry Pinkney (Watercolor)

Экземпляры Album возвращают только имя и исполнителя в строке. Экземпляры Illustration также включали среду в круглые скобки, так как этот класс переопределяет ToString() метод .

Пример 2. Реализация интерфейсов

В следующем примере показано, как класс может реализовать один или несколько интерфейсов. В примере расширяется определение класса Temperature для поддержки дополнительных операций и поведения.

Начальное определение класса

Перед реализацией каких-либо интерфейсов класс Temperature определяется с двумя свойствами: Degrees и Scale. Он определяет конструкторы и три метода экземпляра для возврата экземпляра в виде степеней определенного масштаба.

Класс определяет доступные шкалы с помощью перечисления TemperatureScale .

class Temperature {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale   = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius    { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5/9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5/9 }
            Kelvin     { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius    { return $this.Degrees * 9/5 + 32 }
            Kelvin     { return $this.Degrees * 9/5 - 459.67 }
        }
        return $this.Degrees
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

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

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new([TemperatureScale]::Fahrenheit)
$Kelvin     = [Temperature]::new(0, 'Kelvin')

$Celsius, $Fahrenheit, $Kelvin

"The temperatures are: $Celsius, $Fahrenheit, $Kelvin"

[Temperature]::new() -eq $Celsius

$Celsius -gt $Kelvin
Degrees      Scale
-------      -----
   0.00    Celsius
   0.00 Fahrenheit
   0.00     Kelvin

The temperatures are: Temperature, Temperature, Temperature

False

InvalidOperation:
Line |
  11 |  $Celsius -gt $Kelvin
     |  ~~~~~~~~~~~~~~~~~~~~
     | Cannot compare "Temperature" because it is not IComparable.

Выходные данные показывают, что экземпляры Temperature:

  • Не отображаются правильно в виде строк.
  • Не удается проверить правильность на эквивалентность.
  • Невозможно сравнить.

Эти три проблемы можно решить, реализовав интерфейсы для класса .

Реализация IFormattable

Первым интерфейсом для класса Temperature является System.IFormattable. Этот интерфейс позволяет отформатировать экземпляр класса в виде разных строк. Чтобы реализовать интерфейс, класс должен наследовать от System.IFormattable и определить метод экземпляра ToString() .

Метод ToString() экземпляра должен иметь следующую сигнатуру:

[string] ToString(
    [string]$Format,
    [System.IFormatProvider]$FormatProvider
) {
    # Implementation
}

Подпись, требуемая интерфейсу, указана в справочной документации.

Для Temperature класс должен поддерживать три формата: C возвращать экземпляр по Цельсию, F возвращать его по шкале Fahrenheit и K возвращать в Кельвине. Для любого другого формата метод должен вызывать исключение System.FormatException.

[string] ToString(
    [string]$Format,
    [System.IFormatProvider]$FormatProvider
) {
    # If format isn't specified, use the defined scale.
    if ([string]::IsNullOrEmpty($Format)) {
        $Format = switch ($this.Scale) {
            Celsius    { 'C' }
            Fahrenheit { 'F' }
            Kelvin     { 'K' }
        }
    }
    # If format provider isn't specified, use the current culture.
    if ($null -eq $FormatProvider) {
        $FormatProvider = [CultureInfo]::CurrentCulture
    }
    # Format the temperature.
    switch ($Format) {
        'C' {
            return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
        }
        'F' {
            return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
        }
        'K' {
            return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
        }
    }
    # If we get here, the format is invalid.
    throw [System.FormatException]::new(
        "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
    )
}

В этой реализации метод по умолчанию использует масштаб экземпляра для формата и текущий язык и региональные параметры при форматировании самого значения числовой степени. Он использует методы экземпляра To<Scale>() для преобразования градусов, форматирования их до двух десятичных разрядов и добавления соответствующего символа степени в строку.

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

[string] ToString([string]$Format) {
    return $this.ToString($Format, $null)
}

[string] ToString() {
    return $this.ToString($null, $null)
}

В следующем коде показано обновленное определение параметра Temperature:

class Temperature : System.IFormattable {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

Выходные данные для перегрузок метода показаны в следующем блоке.

$Temp = [Temperature]::new()
"The temperature is $Temp"
$Temp.ToString()
$Temp.ToString('K')
$Temp.ToString('F', $null)
The temperature is 0.00°C

0.00°C

273.15°K

32.00°F

Реализация IEquatable

Теперь, когда класс Temperature можно отформатировать для удобства чтения, пользователи должны иметь возможность проверка, равны ли два экземпляра класса. Для поддержки этого теста класс должен реализовать интерфейс System.IEquatable .

Чтобы реализовать интерфейс, класс должен наследовать от System.IEquatable и определить метод экземпляра Equals() . Метод Equals() должен иметь следующую сигнатуру:

[bool] Equals([object]$Other) {
    # Implementation
}

Подпись, требуемая интерфейсу, указана в справочной документации.

Для Temperature класс должен поддерживать только сравнение двух экземпляров класса . Для любого другого значения или типа, включая $null, он должен возвращать $false. При сравнении двух температур метод должен преобразовать оба значения в Кельвин, так как температура может быть эквивалентной даже при различных шкалах.

[bool] Equals([object]$Other) {
    # If the other object is null, we can't compare it.
    if ($null -eq $Other) {
        return $false
    }

    # If the other object isn't a temperature, we can't compare it.
    $OtherTemperature = $Other -as [Temperature]
    if ($null -eq $OtherTemperature) {
        return $false
    }

    # Compare the temperatures as Kelvin.
    return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
}

После реализации метода интерфейса обновленное определение temperature :

class Temperature : System.IFormattable, System.IEquatable[object] {
    [float]            $Degrees
    [TemperatureScale] $Scale

    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }

    [bool] Equals([object]$Other) {
        # If the other object is null, we can't compare it.
        if ($null -eq $Other) {
            return $false
        }

        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            return $false
        }

        # Compare the temperatures as Kelvin.
        return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

В следующем блоке показано поведение обновленного класса:

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new(32, 'Fahrenheit')
$Kelvin     = [Temperature]::new([TemperatureScale]::Kelvin)

@"
Temperatures are: $Celsius, $Fahrenheit, $Kelvin
`$Celsius.Equals(`$Fahrenheit) = $($Celsius.Equals($Fahrenheit))
`$Celsius -eq `$Fahrenheit     = $($Celsius -eq $Fahrenheit)
`$Celsius -ne `$Kelvin         = $($Celsius -ne $Kelvin)
"@
Temperatures are: 0.00°C, 32.00°F, 0.00°K

$Celsius.Equals($Fahrenheit) = True
$Celsius -eq $Fahrenheit     = True
$Celsius -ne $Kelvin         = True

Реализация IComparable

Последний интерфейс, реализуемый для класса Temperature , — System.IComparable. Когда класс реализует этот интерфейс, пользователи могут использовать -ltоператоры , -le, -gtи -ge для сравнения экземпляров класса .

Чтобы реализовать интерфейс, класс должен наследовать от System.IComparable и определить метод экземпляра Equals() . Метод Equals() должен иметь следующую сигнатуру:

[int] CompareTo([Object]$Other) {
    # Implementation
}

Подпись, требуемая интерфейсу, указана в справочной документации.

Для Temperature класс должен поддерживать только сравнение двух экземпляров класса . Так как базовый тип для свойства Degrees , даже при преобразовании в другой масштаб, является числом с плавающей запятой, метод может полагаться на базовый тип для фактического сравнения.

[int] CompareTo([object]$Other) {
    # If the other object's null, consider this instance "greater than" it
    if ($null -eq $Other) {
        return 1
    }
    # If the other object isn't a temperature, we can't compare it.
    $OtherTemperature = $Other -as [Temperature]
    if ($null -eq $OtherTemperature) {
        throw [System.ArgumentException]::new(
            "Object must be of type 'Temperature'."
        )
    }
    # Compare the temperatures as Kelvin.
    return $this.ToKelvin().CompareTo($OtherTemperature.ToKelvin())
}

Окончательное определение класса Temperature :

class Temperature : System.IFormattable,
                    System.IComparable,
                    System.IEquatable[object] {
    # Instance properties
    [float]            $Degrees
    [TemperatureScale] $Scale

    # Constructors
    Temperature() {}
    Temperature([float] $Degrees)          { $this.Degrees = $Degrees }
    Temperature([TemperatureScale] $Scale) { $this.Scale = $Scale }
    Temperature([float] $Degrees, [TemperatureScale] $Scale) {
        $this.Degrees = $Degrees
        $this.Scale = $Scale
    }

    [float] ToKelvin() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees + 273.15 }
            Fahrenheit { return ($this.Degrees + 459.67) * 5 / 9 }
        }
        return $this.Degrees
    }
    [float] ToCelsius() {
        switch ($this.Scale) {
            Fahrenheit { return ($this.Degrees - 32) * 5 / 9 }
            Kelvin { return $this.Degrees - 273.15 }
        }
        return $this.Degrees
    }
    [float] ToFahrenheit() {
        switch ($this.Scale) {
            Celsius { return $this.Degrees * 9 / 5 + 32 }
            Kelvin { return $this.Degrees * 9 / 5 - 459.67 }
        }
        return $this.Degrees
    }

    [string] ToString(
        [string]$Format,
        [System.IFormatProvider]$FormatProvider
    ) {
        # If format isn't specified, use the defined scale.
        if ([string]::IsNullOrEmpty($Format)) {
            $Format = switch ($this.Scale) {
                Celsius    { 'C' }
                Fahrenheit { 'F' }
                Kelvin     { 'K' }
            }
        }
        # If format provider isn't specified, use the current culture.
        if ($null -eq $FormatProvider) {
            $FormatProvider = [CultureInfo]::CurrentCulture
        }
        # Format the temperature.
        switch ($Format) {
            'C' {
                return $this.ToCelsius().ToString('F2', $FormatProvider) + '°C'
            }
            'F' {
                return $this.ToFahrenheit().ToString('F2', $FormatProvider) + '°F'
            }
            'K' {
                return $this.ToKelvin().ToString('F2', $FormatProvider) + '°K'
            }
        }
        # If we get here, the format is invalid.
        throw [System.FormatException]::new(
            "Unknown format: '$Format'. Valid Formats are 'C', 'F', and 'K'"
        )
    }

    [string] ToString([string]$Format) {
        return $this.ToString($Format, $null)
    }

    [string] ToString() {
        return $this.ToString($null, $null)
    }

    [bool] Equals([object]$Other) {
        # If the other object is null, we can't compare it.
        if ($null -eq $Other) {
            return $false
        }
        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            return $false
        }
        # Compare the temperatures as Kelvin.
        return $this.ToKelvin() -eq $OtherTemperature.ToKelvin()
    }
    [int] CompareTo([object]$Other) {
        # If the other object's null, consider this instance "greater than" it
        if ($null -eq $Other) {
            return 1
        }
        # If the other object isn't a temperature, we can't compare it.
        $OtherTemperature = $Other -as [Temperature]
        if ($null -eq $OtherTemperature) {
            throw [System.ArgumentException]::new(
                "Object must be of type 'Temperature'."
            )
        }
        # Compare the temperatures as Kelvin.
        return $this.ToKelvin().CompareTo($OtherTemperature.ToKelvin())
    }
}

enum TemperatureScale {
    Celsius    = 0
    Fahrenheit = 1
    Kelvin     = 2
}

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

$Celsius    = [Temperature]::new()
$Fahrenheit = [Temperature]::new(32, 'Fahrenheit')
$Kelvin     = [Temperature]::new([TemperatureScale]::Kelvin)

@"
Temperatures are: $Celsius, $Fahrenheit, $Kelvin
`$Celsius.Equals(`$Fahrenheit)    = $($Celsius.Equals($Fahrenheit))
`$Celsius.Equals(`$Kelvin)        = $($Celsius.Equals($Kelvin))
`$Celsius.CompareTo(`$Fahrenheit) = $($Celsius.CompareTo($Fahrenheit))
`$Celsius.CompareTo(`$Kelvin)     = $($Celsius.CompareTo($Kelvin))
`$Celsius -lt `$Fahrenheit        = $($Celsius -lt $Fahrenheit)
`$Celsius -le `$Fahrenheit        = $($Celsius -le $Fahrenheit)
`$Celsius -eq `$Fahrenheit        = $($Celsius -eq $Fahrenheit)
`$Celsius -gt `$Kelvin            = $($Celsius -gt $Kelvin)
"@
Temperatures are: 0.00°C, 32.00°F, 0.00°K
$Celsius.Equals($Fahrenheit)    = True
$Celsius.Equals($Kelvin)        = False
$Celsius.CompareTo($Fahrenheit) = 0
$Celsius.CompareTo($Kelvin)     = 1
$Celsius -lt $Fahrenheit        = False
$Celsius -le $Fahrenheit        = True
$Celsius -eq $Fahrenheit        = True
$Celsius -gt $Kelvin            = True

Пример 3. Наследование от универсального базового класса

В этом примере показано, как можно наследовать от универсального класса , например System.Collections.Generic.List.

Использование встроенного класса в качестве параметра типа

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

class ExampleStringList : System.Collections.Generic.List[string] {}

$List = [ExampleStringList]::New()
$List.AddRange([string[]]@('a','b','c'))
$List.GetType() | Format-List -Property Name, BaseType
$List
Name     : ExampleStringList
BaseType : System.Collections.Generic.List`1[System.String]

a
b
c

Использование пользовательского класса в качестве параметра типа

В следующем блоке кода сначала определяется новый класс ExampleItem с одним свойством экземпляра и методом ToString() . Затем он определяет класс ExampleItemList , наследующий от базового класса System.Collections.Generic.List с ExampleItem в качестве параметра типа.

Скопируйте весь блок кода и запустите его как одну инструкцию.

class ExampleItem {
    [string] $Name
    [string] ToString() { return $this.Name }
}
class ExampleItemList : System.Collections.Generic.List[ExampleItem] {}
ParentContainsErrorRecordException: An error occurred while creating the pipeline.

Выполнение всего блока кода вызывает ошибку, так как PowerShell еще не загрузил класс ExampleItem в среду выполнения. Вы пока не можете использовать имя класса в качестве параметра типа для базового класса System.Collections.Generic.List .

Выполните следующие блоки кода в определенном порядке.

class ExampleItem {
    [string] $Name
    [string] ToString() { return $this.Name }
}
class ExampleItemList : System.Collections.Generic.List[ExampleItem] {}

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

$List = [ExampleItemList]::New()
$List.AddRange([ExampleItem[]]@(
    [ExampleItem]@{ Name = 'Foo' }
    [ExampleItem]@{ Name = 'Bar' }
    [ExampleItem]@{ Name = 'Baz' }
))
$List.GetType() | Format-List -Property Name, BaseType
$List
Name     : ExampleItemList
BaseType : System.Collections.Generic.List`1[ExampleItem]

Name
----
Foo
Bar
Baz

Наследование универсального типа с помощью настраиваемого параметра типа в модуле

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

Сохраните следующий блок кода как GenericExample.psd1.

@{
    RootModule        = 'GenericExample.psm1'
    ModuleVersion     = '0.1.0'
    GUID              = '2779fa60-0b3b-4236-b592-9060c0661ac2'
}

Сохраните следующий блок кода как GenericExample.InventoryItem.psm1.

class InventoryItem {
    [string] $Name
    [int]    $Count

    InventoryItem() {}
    InventoryItem([string]$Name) {
        $this.Name = $Name
    }
    InventoryItem([string]$Name, [int]$Count) {
        $this.Name  = $Name
        $this.Count = $Count
    }

    [string] ToString() {
        return "$($this.Name) ($($this.Count))"
    }
}

Сохраните следующий блок кода как GenericExample.psm1.

using namespace System.Collections.Generic
using module ./GenericExample.InventoryItem.psm1

class Inventory : List[InventoryItem] {}

# Define the types to export with type accelerators.
$ExportableTypes =@(
    [InventoryItem]
    [Inventory]
)
# 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()

Совет

Корневой модуль добавляет пользовательские типы в ускорители типов PowerShell. Этот шаблон позволяет пользователям модуля немедленно получать доступ к IntelliSense и автозавершение для пользовательских типов без необходимости использовать инструкцию using module .

Дополнительные сведения об этом шаблоне см. в разделе "Экспорт с помощью ускорителей типов" статьи about_Classes.

Импортируйте модуль и проверьте выходные данные.

Import-Module ./GenericExample.psd1

$Inventory = [Inventory]::new()
$Inventory.GetType() | Format-List -Property Name, BaseType

$Inventory.Add([InventoryItem]::new('Bucket', 2))
$Inventory.Add([InventoryItem]::new('Mop'))
$Inventory.Add([InventoryItem]@{ Name = 'Broom' ; Count = 4 })
$Inventory
Name     : Inventory
BaseType : System.Collections.Generic.List`1[InventoryItem]

Name   Count
----   -----
Bucket     2
Mop        0
Broom      4

Модуль загружается без ошибок, так как класс InventoryItem определен в файле модуля, отличном от класса Inventory . Оба класса доступны пользователям модуля.

Наследование базового класса

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

Если базовый класс определен в .NET, а не в PowerShell, обратите внимание на следующее:

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

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

Конструкторы производных классов

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

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

Чтобы вызвать базовый конструктор, отличный от параметров, добавьте : base(<parameters>) после параметров конструктора и перед блоком основного текста.

class <derived-class> : <base-class> {
    <derived-class>(<derived-parameters>) : <base-class>(<base-parameters>) {
        # initialization code
    }
}

При определении конструктора, который вызывает конструктор базового класса, параметры могут быть любым из следующих элементов:

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

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

Методы производного класса

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

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

В отличие от конструкторов, методы не могут использовать : base(<parameters>) синтаксис для вызова перегрузки базового класса для метода . Переопределенная перегрузка в производном классе полностью заменяет перегрузку, определенную базовым классом. Чтобы вызвать метод базового класса для экземпляра, приведите переменную экземпляра ($this) к базовому классу перед вызовом метода .

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

class BaseClass {
    [bool] IsTrue() { return $true }
}
class DerivedClass : BaseClass {
    [bool] IsTrue()     { return $false }
    [bool] BaseIsTrue() { return ([BaseClass]$this).IsTrue() }
}

@"
[BaseClass]::new().IsTrue()        = $([BaseClass]::new().IsTrue())
[DerivedClass]::new().IsTrue()     = $([DerivedClass]::new().IsTrue())
[DerivedClass]::new().BaseIsTrue() = $([DerivedClass]::new().BaseIsTrue())
"@
[BaseClass]::new().IsTrue()        = True
[DerivedClass]::new().IsTrue()     = False
[DerivedClass]::new().BaseIsTrue() = True

Расширенный пример, показывающий, как производный класс может переопределять наследуемые методы, см. в разделе Класс Illustration в примере 1.

Свойства производного класса

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

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

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

В примере 1 показано, как производные классы наследуют, расширяют и переопределяют свойства базового класса.

Наследование от универсальных шаблонов

Если класс является производным от универсального, параметр типа уже должен быть определен, прежде чем PowerShell будет анализировать производный класс. Если параметр типа для универсального является классом Или перечислением PowerShell, определенным в том же файле или блоке кода, PowerShell выдает ошибку.

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

Пример, демонстрирующий наследование от универсального базового класса, см. в примере 3.

Полезные классы для наследования

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

  • System.Attribute — производные классы для определения атрибутов, которые можно использовать для переменных, параметров, определений классов и перечислений и многого другого.
  • System.Management.Automation.ArgumentTransformationAttribute — производные классы для обработки преобразования входных данных для переменной или параметра в определенный тип данных.
  • System.Management.Automation.ValidateArgumentsAttribute — производные классы для применения пользовательской проверки к переменным, параметрам и свойствам класса.
  • System.Collections.Generic.List — производные классы, упрощающие создание списков определенного типа данных и управление ими.
  • System.Exception — производные классы для определения пользовательских ошибок.

Реализация интерфейсов

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

Примечание

PowerShell не поддерживает объявление новых интерфейсов в скрипте PowerShell. Вместо этого интерфейсы должны объявляться в коде .NET и добавляться в сеанс с помощью командлета Add-Type или инструкции using assembly .

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

Пример реализации интерфейсов см. в примере 2.

Полезные интерфейсы для реализации

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

  • System.IEquatable — этот интерфейс позволяет пользователям сравнивать два экземпляра класса . Если класс не реализует этот интерфейс, PowerShell проверяет эквивалентность между двумя экземплярами с помощью равенства ссылок. Иными словами, экземпляр класса равен только самому себе, даже если значения свойств в двух экземплярах одинаковы.
  • System.IComparable — этот интерфейс позволяет пользователям сравнивать экземпляры класса с операторами -leсравнения , -lt, -geи -gt . Если класс не реализует этот интерфейс, эти операторы вызывают ошибку.
  • System.IFormattable — этот интерфейс позволяет пользователям форматировать экземпляры класса в различные строки. Это полезно для классов, имеющих несколько стандартных строковых представлений, таких как бюджетные элементы, списки литературы и температуры.
  • System.IConvertible — этот интерфейс позволяет пользователям преобразовывать экземпляры класса в другие типы среды выполнения. Это полезно для классов, которые имеют базовое числовое значение или могут быть преобразованы в одно.

Ограничения

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

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

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

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

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

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

См. также: