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


о_Классах_Наследование

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

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

Длинное описание

Классы 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() для добавления работ в статическое свойство 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)" }
}

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

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

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.

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

В развернутом обмене сообщениями второй вызов метода RegisterWork() сообщает, что работы и художники уже зарегистрированы. Несмотря на то что первый вызов RegisterWork() был для производного класса Альбом, он использовал унаследованный статический метод из базового класса 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.

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

  • Переопределяется статическое свойство Artists. Определение совпадает, но класс иллюстрации объявляет его напрямую.
  • Он переопределяет свойство экземпляра категории , задав значение по умолчанию .
  • Он переопределяет метод экземпляра 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"
    }
}

В следующем блоке кода показано поведение производного класса Иллюстрации. Он создает три экземпляра класса, отображает их в таблице, а затем регистрирует их с помощью наследуемого статического 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 для базового класса и производного класса значения отличаются. Свойство "Исполнители" для производного класса включает только иллюстраторов, а не исполнителей альбомов. Переопределение свойства 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)

Экземпляры в альбоме возвращают только имя и исполнителя в своей строке. Экземпляры иллюстрации также включали средний в скобках, так как этот класс переопределяет метод .

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

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

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

Перед реализацией интерфейсов класс температуры определяется двумя свойствами, градусов и Масштабирование. Он определяет конструкторы и три метода экземпляра для возврата экземпляра в виде степени определенного масштаба.

Класс определяет доступные масштабы, используя перечисление 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.

Результаты показывают, что экземпляры температуры ,:

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

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

Реализация IFormattable

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

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

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

Сигнатура, которую требует интерфейс, указана в справочной документации .

Для температурыкласс должен поддерживать три формата: C, чтобы вернуть экземпляр в Цельсиях, F, чтобы вернуть его в Фаренгейтах, и 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)
}

Следующий код показывает обновленное определение для температуры:

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
}

Сигнатура, которую требует интерфейс, указана в справочной документации .

Для температурыкласс должен поддерживать только сравнение двух экземпляров класса. Для любого другого значения или типа, включая $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()
}

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

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

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

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

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

Сигнатура, которую требует интерфейс, указана в справочной документации .

Для температурыкласс должен поддерживать только сравнение двух экземпляров класса. Так как базовый тип для свойства 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())
}

Окончательное определение для класса температуры :

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. Наследование от универсального базового класса

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

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

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

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

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

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

Иллюстрация в примере 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

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

См. также