Share via


about_Classes_Inheritance

Descripción breve

Describe cómo puede definir clases que extienden otros tipos.

Descripción larga

Las clases de PowerShell admiten la herencia, lo que permite definir una clase secundaria que reutiliza (hereda), amplía o modifica el comportamiento de una clase primaria. La clase cuyos miembros son heredados se conoce como clase base. La clase que hereda los miembros de la clase base se conoce como clase derivada.

PowerShell solo admite la herencia única. Una clase solo puede heredar de una sola clase. Sin embargo, la herencia es transitiva, lo que le permite definir una jerarquía de herencia para un conjunto de tipos. En otras palabras, el tipo D puede heredar del tipo C, que hereda del tipo B, que hereda del tipo base A. Dado que la herencia es transitiva, los miembros del tipo A están disponibles para el tipo D.

Las clases derivadas no heredan todos los miembros de la clase base. Los miembros siguientes no se heredan:

  • Constructores estáticos, que inicializan los datos estáticos de una clase.
  • Constructores de instancias, a los que se llama para crear una nueva instancia de la clase. Cada clase debe definir sus propios constructores.

Puede extender una clase mediante la creación de una nueva clase que derive de una clase existente. La clase derivada hereda las propiedades y los métodos de la clase base. Puede agregar o invalidar los miembros de clase base según sea necesario.

Las clases también pueden heredar de interfaces, que definen un contrato. Una clase que hereda de una interfaz debe implementar ese contrato. Cuando lo hace, la clase se puede usar como cualquier otra clase que implemente esa interfaz. Si una clase hereda de una interfaz, pero no implementa la interfaz, PowerShell genera un error de análisis para la clase .

Algunos operadores de PowerShell dependen de una clase que implementa una interfaz específica. Por ejemplo, el -eq operador solo comprueba la igualdad de referencia a menos que la clase implemente la interfaz System.IEquatable . Los -leoperadores , -lt, -gey -gt solo funcionan en clases que implementan la interfaz System.IComparable .

Una clase derivada usa la : sintaxis para extender una clase base o implementar interfaces. La clase derivada siempre debe estar más a la izquierda en la declaración de clase.

En este ejemplo se muestra la sintaxis básica de herencia de clases de PowerShell.

Class Derived : Base {...}

En este ejemplo se muestra la herencia con una declaración de interfaz que viene después de la clase base.

Class Derived : Base, Interface {...}

Sintaxis

La herencia de clases usa las sintaxis siguientes:

Sintaxis de una línea

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

Por ejemplo:

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

Sintaxis multilínea

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

Por ejemplo:

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

Ejemplos

Ejemplo 1: Heredar e invalidar de una clase base

En el ejemplo siguiente se muestra el comportamiento de las propiedades heredadas con y sin invalidar. Ejecute los bloques de código en orden después de leer su descripción.

Definición de la clase base

El primer bloque de código define PublishedWork como una clase base. Tiene dos propiedades estáticas, List y Artists. A continuación, define el método estático RegisterWork() para agregar obras a la propiedad Static List y a los artistas a la propiedad Artists , escribiendo un mensaje para cada nueva entrada de las listas.

La clase define tres propiedades de instancia que describen un trabajo publicado. Por último, define los Register() métodos de instancia y 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)" }
}

Definición de una clase derivada sin invalidaciones

La primera clase derivada es Album. No invalida ninguna propiedad ni método. Agrega una nueva propiedad de instancia, Genres, que no existe en la clase base.

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

El siguiente bloque de código muestra el comportamiento de la clase Album derivada. En primer lugar, establece para $VerbosePreference que los mensajes de los métodos de clase emitan a la consola. Crea tres instancias de la clase , las muestra en una tabla y, a continuación, las registra con el método estático RegisterWork() heredado. A continuación, llama directamente al mismo método estático en la clase base.

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

Observe que aunque la clase Album no definió un valor para Category o ningún constructor, la propiedad se definió mediante el constructor predeterminado de la clase base.

En la mensajería detallada, la segunda llamada al RegisterWork() método informa de que los trabajos y artistas ya están registrados. Aunque la primera llamada a RegisterWork() era para la clase Album derivada, usó el método estático heredado de la clase PublishedWork base. Ese método actualizó las propiedades estáticas List y Artist en la clase base, que la clase derivada no invalidó.

El siguiente bloque de código borra el registro y llama al Register() método de instancia en los objetos 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

El método de instancia de los objetos Album tiene el mismo efecto que llamar al método estático en la clase derivada o base.

El siguiente bloque de código compara las propiedades estáticas de la clase base y la clase derivada, mostrando que son iguales.

[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

Definición de una clase derivada con invalidaciones

El siguiente bloque de código define la clase Illustration que hereda de la clase PublishedWork base. La nueva clase extiende la clase base definiendo la propiedad de instancia media con un valor predeterminado de Unknown.

A diferencia de la clase Album derivada, Illustration invalida las siguientes propiedades y métodos:

  • Invalida la propiedad static Artists . La definición es la misma, pero la clase Illustration la declara directamente.
  • Invalida la propiedad de instancia Category , estableciendo el valor predeterminado en Illustrations.
  • Invalida el ToString() método de instancia para que la representación de cadena de una ilustración incluya el medio con el que se creó.

La clase también define el método estático RegisterIllustration() para llamar primero al método de clase RegisterWork() base y, a continuación, agregar el artista a la propiedad estática De artista invalidada en la clase derivada.

Por último, la clase invalida los tres constructores:

  1. El constructor predeterminado está vacío, excepto para un mensaje detallado que indica que creó una ilustración.
  2. El siguiente constructor toma dos valores de cadena para el nombre y el artista que creó la ilustración. En lugar de implementar la lógica para establecer las propiedades Name y Artist , el constructor llama al constructor adecuado desde la clase base.
  3. El último constructor toma tres valores de cadena para el nombre, el artista y el medio de la ilustración. Ambos constructores escriben un mensaje detallado que indica que crearon una ilustración.
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"
    }
}

El siguiente bloque de código muestra el comportamiento de la clase Ilustración derivada. Crea tres instancias de la clase , las muestra en una tabla y, a continuación, las registra con el método estático RegisterWork() heredado. A continuación, llama directamente al mismo método estático en la clase base. Por último, escribe mensajes que muestran la lista de artistas registrados para la clase base y la clase derivada.

$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

La mensajería detallada a partir de la creación de las instancias muestra que:

  • Al crear la primera instancia, se llamó al constructor predeterminado de clase base antes del constructor predeterminado de clase derivada.
  • Al crear la segunda instancia, se llamó al constructor heredado explícitamente para la clase base antes del constructor de clase derivada.
  • Al crear la tercera instancia, se llamó al constructor predeterminado de clase base antes del constructor de clase derivada.

Los mensajes detallados del RegisterWork() método indican que las obras y artistas ya estaban registradas. Esto se debe a que el RegisterIllustration() método llamó al RegisterWork() método internamente.

Sin embargo, al comparar el valor de la propiedad static Artist para la clase base y la clase derivada, los valores son diferentes. La propiedad Artists para la clase derivada solo incluye ilustradores, no los artistas del álbum. Redefinir la propiedad Artist de la clase derivada impide que la clase devuelva la propiedad estática en la clase base.

El bloque de código final llama al ToString() método en las entradas de la propiedad List estática en la clase base.

[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)

Las instancias de Album solo devuelven el nombre y el artista en su cadena. Las instancias de Ilustración también incluían el medio entre paréntesis, porque esa clase sobrescribía el ToString() método .

Ejemplo 2: Implementación de interfaces

En el ejemplo siguiente se muestra cómo una clase puede implementar una o varias interfaces. En el ejemplo se amplía la definición de una clase Temperature para admitir más operaciones y comportamientos.

Definición de clase inicial

Antes de implementar cualquier interfaz, la clase Temperature se define con dos propiedades, Degrees y Scale. Define constructores y tres métodos de instancia para devolver la instancia como grados de una escala determinada.

La clase define las escalas disponibles con la enumeración 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
}

Sin embargo, en esta implementación básica, hay algunas limitaciones, como se muestra en la salida de ejemplo siguiente:

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

La salida muestra que las instancias de Temperature:

  • No se muestre correctamente como cadenas.
  • No se puede comprobar correctamente la equivalencia.
  • No se puede comparar.

Estos tres problemas se pueden solucionar mediante la implementación de interfaces para la clase .

Implementación de IFormattable

La primera interfaz que se va a implementar para la clase Temperature es System.IFormattable. Esta interfaz permite dar formato a una instancia de la clase como cadenas diferentes. Para implementar la interfaz, la clase debe heredar de System.IFormattable y definir el ToString() método de instancia.

El ToString() método de instancia debe tener la firma siguiente:

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

La firma que requiere la interfaz se muestra en la documentación de referencia.

Para Temperature, la clase debe admitir tres formatos: C para devolver la instancia en Celsius, F devolverla en Fahrenheit y K devolverla en Kelvin. Para cualquier otro formato, el método debe iniciar una excepción 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'"
    )
}

En esta implementación, el método tiene como valor predeterminado la escala de instancia para el formato y la referencia cultural actual al dar formato al propio valor numérico de grado. Usa los To<Scale>() métodos de instancia para convertir los grados, los da formato a dos decimales y anexa el símbolo de grado adecuado a la cadena.

Con la firma necesaria implementada, la clase también puede definir sobrecargas para facilitar la devolución de la instancia con formato.

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

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

El código siguiente muestra la definición actualizada para 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
}

La salida de las sobrecargas del método se muestra en el siguiente bloque.

$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

Implementación de IEquatable

Ahora que se puede dar formato a la clase Temperature para mejorar la legibilidad, los usuarios deben poder comprobar si dos instancias de la clase son iguales. Para admitir esta prueba, la clase debe implementar la interfaz System.IEquatable .

Para implementar la interfaz, la clase debe heredar de System.IEquatable y definir el Equals() método de instancia. El Equals() método debe tener la firma siguiente:

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

La firma que requiere la interfaz se muestra en la documentación de referencia.

Para Temperature, la clase solo debe admitir la comparación de dos instancias de la clase . Para cualquier otro valor o tipo, incluido $null, debe devolver $false. Al comparar dos temperaturas, el método debe convertir ambos valores en Kelvin, ya que las temperaturas pueden ser equivalentes incluso con diferentes escalas.

[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()
}

Con el método de interfaz implementado, la definición actualizada para Temperature es:

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
}

En el bloque siguiente se muestra cómo se comporta la clase actualizada:

$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

Implementación de IComparable

La última interfaz que se va a implementar para la clase Temperature es System.IComparable. Cuando la clase implementa esta interfaz, los usuarios pueden usar los -ltoperadores , -le, -gty -ge para comparar instancias de la clase .

Para implementar la interfaz, la clase debe heredar de System.IComparable y definir el Equals() método de instancia. El Equals() método debe tener la firma siguiente:

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

La firma que requiere la interfaz se muestra en la documentación de referencia.

Para Temperature, la clase solo debe admitir la comparación de dos instancias de la clase . Dado que el tipo subyacente de la propiedad Degrees , incluso cuando se convierte en una escala diferente, es un número de punto flotante, el método puede confiar en el tipo subyacente para la comparación real.

[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())
}

La definición final de la clase Temperature es:

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
}

Con la definición completa, los usuarios pueden dar formato y comparar instancias de la clase en PowerShell como cualquier tipo integrado.

$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

Ejemplo 3: Heredar de una clase base genérica

En este ejemplo se muestra cómo se puede derivar de una clase genérica como System.Collections.Generic.List.

Uso de una clase integrada como parámetro de tipo

Ejecute el siguiente bloque de código. Muestra cómo una nueva clase puede heredar de un tipo genérico siempre que el parámetro de tipo ya esté definido en tiempo de análisis.

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

Uso de una clase personalizada como parámetro de tipo

El siguiente bloque de código define primero una nueva clase, ExampleItem, con una sola propiedad de instancia y el ToString() método . A continuación, define la clase ExampleItemList que hereda de la clase base System.Collections.Generic.List con ExampleItem como parámetro de tipo.

Copie todo el bloque de código y ejecútelo como una sola instrucción.

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

Al ejecutar todo el bloque de código se produce un error porque PowerShell aún no ha cargado la clase ExampleItem en el entorno de ejecución. Todavía no puede usar el nombre de clase como parámetro de tipo para la clase base System.Collections.Generic.List .

Ejecute los siguientes bloques de código en el orden en que se definen.

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

Esta vez, PowerShell no genera ningún error. Ahora se definen ambas clases. Ejecute el siguiente bloque de código para ver el comportamiento de la nueva clase.

$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

Derivar un genérico con un parámetro de tipo personalizado en un módulo

Los siguientes bloques de código muestran cómo definir una clase que hereda de una clase base genérica que usa un tipo personalizado para el parámetro de tipo.

Guarde el siguiente bloque de código como GenericExample.psd1.

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

Guarde el siguiente bloque de código como 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))"
    }
}

Guarde el siguiente bloque de código como 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()

Sugerencia

El módulo raíz agrega los tipos personalizados a los aceleradores de tipos de PowerShell. Este patrón permite a los usuarios del módulo acceder inmediatamente a IntelliSense y autocompletar para los tipos personalizados sin necesidad de usar primero la using module instrucción .

Para obtener más información sobre este patrón, vea la sección "Exportar con aceleradores de tipos" de about_Classes.

Importe el módulo y compruebe la salida.

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

El módulo se carga sin errores porque la clase InventoryItem se define en un archivo de módulo diferente al de la clase Inventory . Ambas clases están disponibles para los usuarios del módulo.

Heredar una clase base

Cuando una clase hereda de una clase base, hereda las propiedades y los métodos de la clase base. No hereda directamente los constructores de clase base, pero puede llamarlos.

Cuando la clase base se define en .NET en lugar de PowerShell, tenga en cuenta que:

  • Las clases de PowerShell no pueden heredar de clases selladas.
  • Al heredar de una clase base genérica, el parámetro de tipo de la clase genérica no puede ser la clase derivada. El uso de la clase derivada como parámetro de tipo genera un error de análisis.

Para ver cómo funciona la herencia y la invalidación para las clases derivadas, consulte el ejemplo 1.

Constructores de clases derivadas

Las clases derivadas no heredan directamente los constructores de la clase base. Si la clase base define un constructor predeterminado y la clase derivada no define ningún constructor, las nuevas instancias de la clase derivada usan el constructor predeterminado de clase base. Si la clase base no define un constructor predeterminado, la clase derivada debe definir explícitamente al menos un constructor.

Los constructores de clase derivadas pueden invocar un constructor desde la clase base con la base palabra clave . Si la clase derivada no invoca explícitamente un constructor de la clase base, invoca el constructor predeterminado para la clase base en su lugar.

Para invocar un constructor base no predeterminado, agregue : base(<parameters>) después de los parámetros del constructor y antes del bloque body.

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

Al definir un constructor que llama a un constructor de clase base, los parámetros pueden ser cualquiera de los siguientes elementos:

  • Variable de cualquier parámetro en el constructor de clase derivada.
  • Cualquier valor estático.
  • Cualquier expresión que se evalúe como un valor del tipo de parámetro.

La clase Illustration de ejemplo 1 muestra cómo una clase derivada puede usar los constructores de clase base.

Métodos de clase derivados

Cuando una clase se deriva de una clase base, hereda los métodos de la clase base y sus sobrecargas. Las sobrecargas de método definidas en la clase base, incluidos los métodos ocultos, están disponibles en la clase derivada.

Una clase derivada puede invalidar una sobrecarga de método heredado redefinirla en la definición de clase. Para invalidar la sobrecarga, los tipos de parámetro deben ser los mismos que para la clase base. El tipo de salida de la sobrecarga puede ser diferente.

A diferencia de los constructores, los métodos no pueden usar la : base(<parameters>) sintaxis para invocar una sobrecarga de clase base para el método . La sobrecarga redefinida de la clase derivada reemplaza completamente la sobrecarga definida por la clase base. Para llamar al método de clase base para una instancia, convierta la variable de instancia ($this) en la clase base antes de llamar al método .

En el fragmento de código siguiente se muestra cómo una clase derivada puede llamar al método de clase base.

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

Para obtener un ejemplo extendido en el que se muestra cómo una clase derivada puede invalidar los métodos heredados, vea la clase Ilustración del ejemplo 1.

Propiedades de clase derivadas

Cuando una clase deriva de una clase base, hereda las propiedades de la clase base. Las propiedades definidas en la clase base, incluidas las propiedades ocultas, están disponibles en la clase derivada.

Una clase derivada puede invalidar una propiedad heredada redefinindola en la definición de clase. La propiedad de la clase derivada usa el tipo redefinido y el valor predeterminado, si existe. Si la propiedad heredada definió un valor predeterminado y la propiedad redefinida no, la propiedad heredada no tiene ningún valor predeterminado.

Si una clase derivada no invalida una propiedad estática, el acceso a la propiedad estática a través de la clase derivada tiene acceso a la propiedad estática de la clase base. Al modificar el valor de propiedad a través de la clase derivada, se modifica el valor de la clase base. Cualquier otra clase derivada que no invalide la propiedad estática también usa el valor de la propiedad en la clase base. Actualizar el valor de una propiedad estática heredada en una clase que no invalida la propiedad podría tener efectos no deseados para las clases derivadas de la misma clase base.

En el ejemplo 1 se muestra cómo las clases derivadas heredan, extienden e invalidan las propiedades de clase base.

Derivación de genéricos

Cuando una clase se deriva de un genérico, el parámetro de tipo ya debe definirse antes de que PowerShell analice la clase derivada. Si el parámetro de tipo para el genérico es una clase o enumeración de PowerShell definida en el mismo archivo o bloque de código, PowerShell genera un error.

Para derivar una clase de una clase base genérica con un tipo personalizado como parámetro de tipo, defina la clase o enumeración para el parámetro de tipo en un archivo o módulo diferente y use la using module instrucción para cargar la definición de tipo.

Para obtener un ejemplo en el que se muestra cómo heredar de una clase base genérica, vea Ejemplo 3.

Clases útiles para heredar

Hay algunas clases que pueden ser útiles para heredar al crear módulos de PowerShell. En esta sección se enumeran algunas clases base y para qué se puede usar una clase derivada de ellas.

  • System.Attribute : derive clases para definir atributos que se pueden usar para variables, parámetros, definiciones de clase y enumeración, etc.
  • System.Management.Automation.ArgumentTransformationAttribute : derive clases para controlar la conversión de la entrada de una variable o parámetro en un tipo de datos específico.
  • System.Management.Automation.ValidateArgumentsAttribute : derive clases para aplicar la validación personalizada a variables, parámetros y propiedades de clase.
  • System.Collections.Generic.List : derive clases para facilitar la creación y administración de listas de un tipo de datos específico.
  • System.Exception : derive clases para definir errores personalizados.

Implementación de interfaces

Una clase de PowerShell que implemente una interfaz debe implementar todos los miembros de esa interfaz. Si se omiten los miembros de la interfaz de implementación, se produce un error en tiempo de análisis en el script.

Nota:

PowerShell no admite la declaración de nuevas interfaces en el script de PowerShell. En su lugar, las interfaces deben declararse en código .NET y agregarse a la sesión con el Add-Type cmdlet o la using assembly instrucción .

Cuando una clase implementa una interfaz, se puede usar como cualquier otra clase que implemente esa interfaz. Algunos comandos y operaciones limitan sus tipos admitidos a clases que implementan una interfaz específica.

Para revisar una implementación de ejemplo de interfaces, vea Ejemplo 2.

Interfaces útiles para implementar

Hay algunas clases de interfaz que pueden ser útiles para heredar al crear módulos de PowerShell. En esta sección se enumeran algunas clases base y para qué se puede usar una clase derivada de ellas.

  • System.IEquatable : esta interfaz permite a los usuarios comparar dos instancias de la clase . Cuando una clase no implementa esta interfaz, PowerShell comprueba la equivalencia entre dos instancias mediante la igualdad de referencia. En otras palabras, una instancia de la clase solo es igual a sí misma, aunque los valores de propiedad de dos instancias sean los mismos.
  • System.IComparable : esta interfaz permite a los usuarios comparar instancias de la clase con los -leoperadores de comparación , -lt, -gey -gt . Cuando una clase no implementa esta interfaz, esos operadores generan un error.
  • System.IFormattable : esta interfaz permite a los usuarios dar formato a instancias de la clase en cadenas diferentes. Esto es útil para las clases que tienen más de una representación de cadena estándar, como elementos presupuestarios, bibliografías y temperaturas.
  • System.IConvertible : esta interfaz permite a los usuarios convertir instancias de la clase en otros tipos en tiempo de ejecución. Esto es útil para las clases que tienen un valor numérico subyacente o se pueden convertir en uno.

Limitaciones

  • PowerShell no admite la definición de interfaces en código de script.

    Solución alternativa: defina interfaces en C# y haga referencia al ensamblado que define las interfaces.

  • Las clases de PowerShell solo pueden heredar de una clase base.

    Solución alternativa: la herencia de clases es transitiva. Una clase derivada puede heredar de otra clase derivada para obtener las propiedades y los métodos de una clase base.

  • Al heredar de una clase o interfaz genéricas, el parámetro de tipo para el genérico ya debe definirse. Una clase no puede definirse como parámetro de tipo para una clase o interfaz.

    Solución alternativa: para derivar de una interfaz o clase base genérica, defina el tipo personalizado en un archivo diferente .psm1 y use la using module instrucción para cargar el tipo. No hay ninguna solución alternativa para que un tipo personalizado se use como parámetro de tipo al heredar de un genérico.

Consulte también