Acerca de las clases

Descripción breve

Describe cómo puede usar clases para crear sus propios tipos personalizados.

Descripción larga

PowerShell 5.0 agrega una sintaxis formal para definir clases y otros tipos definidos por el usuario. La adición de clases permite a los desarrolladores y profesionales de TI adoptar PowerShell para una gama más amplia de casos de uso. Simplifica el desarrollo de artefactos de PowerShell y acelera la cobertura de superficies de administración.

Una declaración de clase es un plano técnico que se usa para crear instancias de objetos en tiempo de ejecución. Al definir una clase, el nombre de la clase es el nombre del tipo. Por ejemplo, si declara una clase denominada Device e inicializa una variable $dev en una nueva instancia de Device, $dev es un objeto o instancia de tipo Device. Cada instancia de Device puede tener valores diferentes en sus propiedades.

Escenarios admitidos

  • Defina tipos personalizados en PowerShell con una semántica de programación familiar orientada a objetos, como clases, propiedades, métodos, herencia, etc.
  • Depurar tipos mediante el lenguaje de PowerShell.
  • Genere y controle excepciones mediante mecanismos formales.
  • Defina los recursos de DSC y sus tipos asociados mediante el lenguaje de PowerShell.

Syntax

Las clases se declaran mediante la sintaxis siguiente:

class <class-name> [: [<base-class>][,<interface-list]] {
    [[<attribute>] [hidden] [static] <property-definition> ...]
    [<class-name>([<constructor-argument-list>])
      {<constructor-statement-list>} ...]
    [[<attribute>] [hidden] [static] <method-definition> ...]
}

Se crean instancias de clases mediante cualquiera de las sintaxis siguientes:

[$<variable-name> =] New-Object -TypeName <class-name> [
  [-ArgumentList] <constructor-argument-list>]
[$<variable-name> =] [<class-name>]::new([<constructor-argument-list>])

Nota

Al usar la [<class-name>]::new( sintaxis, los corchetes alrededor del nombre de clase son obligatorios. Los corchetes indican una definición de tipo para PowerShell.

Ejemplo de sintaxis y uso

En este ejemplo se muestra la sintaxis mínima necesaria para crear una clase utilizable.

class Device {
    [string]$Brand
}

$dev = [Device]::new()
$dev.Brand = "Microsoft"
$dev
Brand
-----
Microsoft

Propiedades de clase

Las propiedades son variables declaradas en el ámbito de clase. Una propiedad puede ser de cualquier tipo integrado o de una instancia de otra clase. Las clases no tienen ninguna restricción en el número de propiedades que tienen.

Clase de ejemplo con propiedades simples

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

$device = [Device]::new()
$device.Brand = "Microsoft"
$device.Model = "Surface Pro 4"
$device.VendorSku = "5072641000"

$device
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Tipos complejos de ejemplo en las propiedades de clase

En este ejemplo se define una clase Rack vacía mediante la clase Device . En los ejemplos siguientes se muestra cómo agregar dispositivos al bastidor y cómo empezar con un bastidor cargado previamente.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
}

class Rack {
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new(8)

}

$rack = [Rack]::new()

$rack

Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, $null, $null...}


Métodos de clase

Los métodos definen las acciones que una clase puede realizar. Los métodos pueden tomar parámetros que proporcionan datos de entrada. Los métodos pueden devolver la salida. Los datos devueltos por un método pueden ser cualquier tipo de datos definido.

Ejemplo de clase simple con propiedades y métodos

Extender la clase Rack para agregar y quitar dispositivos hacia o desde ella.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    [string]ToString(){
        return ("{0}|{1}|{2}" -f $this.Brand, $this.Model, $this.VendorSku)
    }
}

class Rack {
    [int]$Slots = 8
    [string]$Brand
    [string]$Model
    [string]$VendorSku
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void]RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }

    [int[]] GetAvailableSlots(){
        [int]$i = 0
        return @($this.Devices.foreach{ if($_ -eq $null){$i}; $i++})
    }
}

$rack = [Rack]::new()

$surface = [Device]::new()
$surface.Brand = "Microsoft"
$surface.Model = "Surface Pro 4"
$surface.VendorSku = "5072641000"

$rack.AddDevice($surface, 2)

$rack
$rack.GetAvailableSlots()

Slots     : 8
Brand     :
Model     :
VendorSku :
AssetId   :
Devices   : {$null, $null, Microsoft|Surface Pro 4|5072641000, $null...}

0
1
3
4
5
6
7

Salida en métodos de clase

Los métodos deben tener definido un tipo de valor devuelto. Si un método no devuelve la salida, el tipo de salida debe ser [void].

En los métodos de clase, no se envían objetos a la canalización excepto los mencionados en la return instrucción . No hay ninguna salida accidental en la canalización desde el código.

Nota

Esto es fundamentalmente diferente de cómo las funciones de PowerShell controlan la salida, donde todo va a la canalización.

Salida del método

En este ejemplo se muestra ninguna salida accidental a la canalización de métodos de clase, excepto en la return instrucción .

class FunWithIntegers
{
    [int[]]$Integers = 0..10

    [int[]]GetOddIntegers(){
        return $this.Integers.Where({ ($_ % 2) })
    }

    [void] GetEvenIntegers(){
        # this following line doesn't go to the pipeline
        $this.Integers.Where({ ($_ % 2) -eq 0})
    }

    [string]SayHello(){
        # this following line doesn't go to the pipeline
        "Good Morning"

        # this line goes to the pipeline
        return "Hello World"
    }
}

$ints = [FunWithIntegers]::new()

$ints.GetOddIntegers()

$ints.GetEvenIntegers()

$ints.SayHello()
1
3
5
7
9
Hello World

Constructor

Los constructores permiten establecer valores predeterminados y validar la lógica de objetos en el momento de crear la instancia de la clase. Los constructores tienen el mismo nombre que la clase . Los constructores pueden tener argumentos para inicializar los miembros de datos del nuevo objeto.

La clase puede tener cero o más constructores definidos. Si no se define ningún constructor, la clase recibe un constructor sin parámetros predeterminado. Este constructor inicializa todos los miembros en sus valores predeterminados. Los tipos de objeto y las cadenas tienen valores NULL. Al definir el constructor, no se crea ningún constructor sin parámetros predeterminado. Create un constructor sin parámetros si es necesario.

Sintaxis básica del constructor

En este ejemplo, la clase Device se define con propiedades y un constructor. Para usar esta clase, el usuario es necesario para proporcionar valores para los parámetros enumerados en el constructor.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$surface
Brand     Model         VendorSku
-----     -----         ---------
Microsoft Surface Pro 4 5072641000

Ejemplo con varios constructores

En este ejemplo, la clase Device se define con propiedades, un constructor predeterminado y un constructor para inicializar la instancia.

El constructor predeterminado establece la marca en Undefined y deja el modelo y la SKU del proveedor con valores NULL.

class Device {
    [string]$Brand
    [string]$Model
    [string]$VendorSku

    Device(){
        $this.Brand = 'Undefined'
    }

    Device(
        [string]$b,
        [string]$m,
        [string]$vsk
    ){
        $this.Brand = $b
        $this.Model = $m
        $this.VendorSku = $vsk
    }
}

[Device]$somedevice = [Device]::new()
[Device]$surface = [Device]::new("Microsoft", "Surface Pro 4", "5072641000")

$somedevice
$surface
Brand       Model           VendorSku
-----       -----           ---------
Undefined
Microsoft   Surface Pro 4   5072641000

Atributo oculto

El hidden atributo hace que una propiedad o un método sean menos visibles. La propiedad o el método siguen siendo accesibles para el usuario y están disponibles en todos los ámbitos en los que el objeto está disponible. Los miembros ocultos están ocultos del Get-Member cmdlet y no se pueden mostrar mediante la finalización de tabulación o IntelliSense fuera de la definición de clase.

Ejemplo de uso de atributos ocultos

Cuando se crea un objeto Rack , el número de ranuras para dispositivos es un valor fijo que no se debe cambiar en ningún momento. Este valor se conoce en el momento de la creación.

El uso del atributo oculto permite al desarrollador mantener el número de ranuras ocultas y evita cambios involuntarios en el tamaño del bastidor.

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    [int] hidden $Slots = 8
    [string]$Brand
    [string]$Model
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)
    }
}

[Rack]$r1 = [Rack]::new("Microsoft", "Surface Pro 4", 16)

$r1
$r1.Devices.Length
$r1.Slots
Brand     Model         Devices
-----     -----         -------
Microsoft Surface Pro 4 {$null, $null, $null, $null...}
16
16

Observe que la propiedad Slots no se muestra en $r1 la salida. Sin embargo, el constructor cambió el tamaño.

Atributo estático

El static atributo define una propiedad o un método que existe en la clase y no necesita ninguna instancia.

Una propiedad estática siempre está disponible, independientemente de la creación de instancias de clase. Una propiedad estática se comparte en todas las instancias de la clase . Un método estático siempre está disponible. Todas las propiedades estáticas residen para todo el intervalo de sesión.

Ejemplo de uso de atributos y métodos estáticos

Supongamos que los bastidores creados aquí existen en el centro de datos. Por lo tanto, le gustaría realizar un seguimiento de los bastidores en el código.

class Device {
    [string]$Brand
    [string]$Model
}

class Rack {
    hidden [int] $Slots = 8
    static [Rack[]]$InstalledRacks = @()
    [string]$Brand
    [string]$Model
    [string]$AssetId
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack ([string]$b, [string]$m, [string]$id, [int]$capacity){
        ## argument validation here

        $this.Brand = $b
        $this.Model = $m
        $this.AssetId = $id
        $this.Slots = $capacity

        ## reset rack size to new capacity
        $this.Devices = [Device[]]::new($this.Slots)

        ## add rack to installed racks
        [Rack]::InstalledRacks += $this
    }

    static [void]PowerOffRacks(){
        foreach ($rack in [Rack]::InstalledRacks) {
            Write-Warning ("Turning off rack: " + ($rack.AssetId))
        }
    }
}

Existen métodos y propiedades estáticas de prueba

PS> [Rack]::InstalledRacks.Length
0

PS> [Rack]::PowerOffRacks()

PS> (1..10) | ForEach-Object {
>>   [Rack]::new("Adatum Corporation", "Standard-16",
>>     $_.ToString("Std0000"), 16)
>> } > $null

PS> [Rack]::InstalledRacks.Length
10

PS> [Rack]::InstalledRacks[3]
Brand              Model       AssetId Devices
-----              -----       ------- -------
Adatum Corporation Standard-16 Std0004 {$null, $null, $null, $null...}

PS> [Rack]::PowerOffRacks()
WARNING: Turning off rack: Std0001
WARNING: Turning off rack: Std0002
WARNING: Turning off rack: Std0003
WARNING: Turning off rack: Std0004
WARNING: Turning off rack: Std0005
WARNING: Turning off rack: Std0006
WARNING: Turning off rack: Std0007
WARNING: Turning off rack: Std0008
WARNING: Turning off rack: Std0009
WARNING: Turning off rack: Std0010

Observe que el número de bastidores aumenta cada vez que se ejecuta este ejemplo.

Atributos de validación de propiedades

Los atributos de validación permiten probar que los valores proporcionados a las propiedades cumplen los requisitos definidos. La validación se desencadena en el momento en que se asigna el valor. Consulte about_functions_advanced_parameters.

Ejemplo de uso de atributos de validación

class Device {
    [ValidateNotNullOrEmpty()][string]$Brand
    [ValidateNotNullOrEmpty()][string]$Model
}

[Device]$dev = [Device]::new()

Write-Output "Testing dev"
$dev

$dev.Brand = ""
Testing dev

Brand Model
----- -----

Exception setting "Brand": "The argument is null or empty. Provide an
argument that is not null or empty, and then try the command again."
At C:\tmp\Untitled-5.ps1:11 char:1
+ $dev.Brand = ""
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], SetValueInvocationException
    + FullyQualifiedErrorId : ExceptionWhenSetting

Herencia en clases de PowerShell

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

PowerShell no admite varias herencias. Las clases no pueden heredar de más de una clase. Sin embargo, puede usar interfaces para ese propósito.

La implementación de herencia se define mediante el : operador , lo que significa extender esta clase o implementar estas interfaces. La clase derivada siempre debe estar más a la izquierda en la declaración de clase.

Ejemplo de uso de la sintaxis de herencia simple

En este ejemplo se muestra la sintaxis simple 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 {...}

Ejemplo de herencia simple en clases de PowerShell

En este ejemplo, las clases Rack y Device usadas en los ejemplos anteriores están mejor definidas para: evitar repeticiones de propiedades, alinear mejor las propiedades comunes y reutilizar la lógica de negocios común.

La mayoría de los objetos del centro de datos son activos de la empresa, lo que tiene sentido empezar a realizar un seguimiento de ellos como activos. Los tipos de dispositivo se definen mediante la DeviceType enumeración, consulte about_Enum para obtener más información sobre las enumeraciones.

En nuestro ejemplo, solo Rack definimos y ComputeServer; ambas extensiones a la Device clase .

enum DeviceType {
    Undefined = 0
    Compute = 1
    Storage = 2
    Networking = 4
    Communications = 8
    Power = 16
    Rack = 32
}

class Asset {
    [string]$Brand
    [string]$Model
}

class Device : Asset {
    hidden [DeviceType]$devtype = [DeviceType]::Undefined
    [string]$Status

    [DeviceType] GetDeviceType(){
        return $this.devtype
    }
}

class ComputeServer : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Compute
    [string]$ProcessorIdentifier
    [string]$Hostname
}

class Rack : Device {
    hidden [DeviceType]$devtype = [DeviceType]::Rack
    hidden [int]$Slots = 8

    [string]$Datacenter
    [string]$Location
    [Device[]]$Devices = [Device[]]::new($this.Slots)

    Rack (){
        ## Just create the default rack with 8 slots
    }

    Rack ([int]$s){
        ## Add argument validation logic here
        $this.Devices = [Device[]]::new($s)
    }

    [void] AddDevice([Device]$dev, [int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $dev
    }

    [void] RemoveDevice([int]$slot){
        ## Add argument validation logic here
        $this.Devices[$slot] = $null
    }
}

$FirstRack = [Rack]::new(16)
$FirstRack.Status = "Operational"
$FirstRack.Datacenter = "PNW"
$FirstRack.Location = "F03R02.J10"

(0..15).ForEach({
    $ComputeServer = [ComputeServer]::new()
    $ComputeServer.Brand = "Fabrikam, Inc."       ## Inherited from Asset
    $ComputeServer.Model = "Fbk5040"              ## Inherited from Asset
    $ComputeServer.Status = "Installed"           ## Inherited from Device
    $ComputeServer.ProcessorIdentifier = "x64"    ## ComputeServer
    $ComputeServer.Hostname = ("r1s" + $_.ToString("000")) ## ComputeServer
    $FirstRack.AddDevice($ComputeServer, $_)
  })

$FirstRack
$FirstRack.Devices
Datacenter : PNW
Location   : F03R02.J10
Devices    : {r1s000, r1s001, r1s002, r1s003...}
Status     : Operational
Brand      :
Model      :

ProcessorIdentifier : x64
Hostname            : r1s000
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

ProcessorIdentifier : x64
Hostname            : r1s001
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

<... content truncated here for brevity ...>

ProcessorIdentifier : x64
Hostname            : r1s015
Status              : Installed
Brand               : Fabrikam, Inc.
Model               : Fbk5040

Llamar a constructores de clase base

Para invocar un constructor de clase base desde una subclase, agregue la base palabra clave .

class Person {
    [int]$Age

    Person([int]$a)
    {
        $this.Age = $a
    }
}

class Child : Person
{
    [string]$School

    Child([int]$a, [string]$s ) : base($a) {
        $this.School = $s
    }
}

[Child]$littleone = [Child]::new(10, "Silver Fir Elementary School")

$littleone.Age

10

Invocar métodos de clase base

Para invalidar los métodos existentes en subclases, declare los métodos con el mismo nombre y la misma firma.

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
}

[ChildClass1]::new().days()

2

Para llamar a métodos de clase base desde implementaciones invaliddas, convierta a la clase base ([baseclass]$this) en la invocación.

class BaseClass
{
    [int]days() {return 1}
}
class ChildClass1 : BaseClass
{
    [int]days () {return 2}
    [int]basedays() {return ([BaseClass]$this).days()}
}

[ChildClass1]::new().days()
[ChildClass1]::new().basedays()

2
1

Interfaces

La sintaxis para declarar interfaces es similar a C#. Puede declarar interfaces después de los tipos base o inmediatamente después de dos puntos (:) cuando no se especifica ningún tipo base. Separe todos los nombres de tipo con comas.

class MyComparable : system.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

class MyComparableBar : bar, system.IComparable
{
    [int] CompareTo([object] $obj)
    {
        return 0;
    }
}

Importación de clases desde un módulo de PowerShell

Import-Module y la #requires instrucción solo importan las funciones, alias y variables del módulo, tal como se define en el módulo. Las clases no se importan. La using module instrucción importa las clases definidas en el módulo. Si el módulo no se carga en la sesión actual, se produce un error en la using instrucción .

Consulte también