Compartir a través de


about_Classes

Descripción breve

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

Descripción larga

A partir de la versión 5.0, PowerShell tiene 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.

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 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 mediante la semántica de programación orientada a objetos, como clases, propiedades, métodos, herencia, etc.
  • Defina los recursos de DSC y sus tipos asociados mediante el lenguaje powerShell.
  • Defina atributos personalizados para decorar variables, parámetros y definiciones de tipos personalizados.
  • Defina excepciones personalizadas que se puedan detectar por su nombre de tipo.

Sintaxis

Sintaxis de definición

Las definiciones de clase usan 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> ...]
}

Sintaxis de creación de instancias

Para crear instancias de una instancia de una clase, use una de las sintaxis siguientes:

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

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.

La sintaxis hashtable solo funciona para las clases que tienen un constructor predeterminado que no espera ningún parámetro. Crea una instancia de la clase con el constructor predeterminado y, a continuación, asigna los pares clave-valor a las propiedades de instancia. Si alguna clave de la tabla hash no es un nombre de propiedad válido, PowerShell genera un error.

Ejemplos

Ejemplo 1: Definición mínima

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

class Device {
    [string]$Brand
}

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

Ejemplo 2: clase con miembros de instancia

En este ejemplo se define una clase Book con varias propiedades, constructores y métodos. Cada miembro definido es un miembro de instancia , no un miembro estático. Solo se puede tener acceso a las propiedades y métodos a través de una instancia creada de la clase .

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

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

El fragmento de código siguiente crea una instancia de la clase y muestra cómo se comporta. Después de crear una instancia de la clase Book , en el ejemplo se usan los GetReadingTime() métodos y GetPublishedAge() para escribir un mensaje sobre el libro.

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

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

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

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

Ejemplo 3: clase con miembros estáticos

La clase BookList de este ejemplo se basa en la clase Book del ejemplo 2. Aunque la clase BookList no se puede marcar estáticamente, la implementación solo define la propiedad estática Books y un conjunto de métodos estáticos para administrar esa propiedad.

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

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

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

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

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

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

Ahora que se define BookList , el libro del ejemplo anterior se puede agregar a la lista.

$null -eq [BookList]::Books

[BookList]::Add($Book)

[BookList]::Books
True

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

El fragmento de código siguiente llama a los métodos estáticos para la clase .

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

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

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

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

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

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

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

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

The Hobbit
The Fellowship of the Ring

The Fellowship of the Ring

Titles:

Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
At C:\code\classes.examples.ps1:114 char:13
+             throw "Book '$Book' already in list"
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Book 'The Hobbi...alread
   y in list:String) [], RuntimeException
    + FullyQualifiedErrorId : Book 'The Hobbit by J.R.R. Tolkien (1937)'
   already in list

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 pueden tener cero o más propiedades. Las clases no tienen un recuento máximo de propiedades.

Para obtener más información, consulte about_Classes_Properties.

Métodos de clase

Los métodos definen las acciones que una clase puede realizar. Los métodos pueden tomar parámetros que especifican datos de entrada. Los métodos siempre definen un tipo de salida. Si un método no devuelve ninguna salida, debe tener el tipo de salida Void . Si un método no define explícitamente un tipo de salida, el tipo de salida del método es Void.

Para obtener más información, consulte about_Classes_Methods.

Constructores de clase

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 parámetros para inicializar los miembros de datos del nuevo objeto.

Para obtener más información, consulte about_Classes_Constructors.

Palabra clave Hidden

La hidden palabra clave oculta un miembro de clase. El miembro sigue siendo accesible para el usuario y está disponible 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.

La hidden palabra clave solo se aplica a los miembros de clase, no a una propia clase.

Los miembros de clase ocultos son:

  • No se incluye en la salida predeterminada de la clase .
  • No se incluye en la lista de miembros de clase devueltos por el Get-Member cmdlet . Para mostrar miembros ocultos con Get-Member, use el parámetro Force .
  • No se muestra en la finalización de tabulación o IntelliSense a menos que se produzca la finalización en la clase que define el miembro oculto.
  • Miembros públicos de la clase . Se puede acceder, heredar y modificar. Ocultar a un miembro no lo hace privado. Solo oculta el miembro como se describe en los puntos anteriores.

Nota:

Al ocultar cualquier sobrecarga de un método, ese método se quita de IntelliSense, los resultados de finalización y la salida predeterminada para Get-Member. Al ocultar cualquier constructor, la new() opción se quita de IntelliSense y los resultados de finalización.

Para obtener más información sobre la palabra clave , consulte about_Hidden. Para obtener más información sobre las propiedades ocultas, consulte about_Classes_Properties. Para obtener más información sobre los métodos ocultos, consulte about_Classes_Methods. Para obtener más información sobre los constructores ocultos, consulte about_Classes_Constructors.

Palabra clave static

La static palabra clave 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 . Siempre hay disponible un método estático. Todas las propiedades estáticas residen para todo el intervalo de sesión.

La static palabra clave solo se aplica a los miembros de clase, no a una propia clase.

Para obtener más información sobre las propiedades estáticas, consulte about_Classes_Properties. Para obtener más información sobre los métodos estáticos, consulte about_Classes_Methods. Para obtener más información sobre los constructores estáticos, consulte about_Classes_Constructors.

Herencia en clases de PowerShell

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.

PowerShell no admite varias herencias. Las clases no pueden heredar directamente de más de una clase.

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.

Para obtener más información sobre la derivación de clases que heredan de una clase base o implementan interfaces, consulte about_Classes_Inheritance.

Exportación de clases con aceleradores de tipos

De forma predeterminada, los módulos de PowerShell no exportan automáticamente clases y enumeraciones definidas en PowerShell. Los tipos personalizados no están disponibles fuera del módulo sin llamar a una using module instrucción .

Sin embargo, si un módulo agrega aceleradores de tipos, esos aceleradores de tipos están disponibles inmediatamente en la sesión después de que los usuarios importen el módulo.

Nota:

Agregar aceleradores de tipos a la sesión usa una API interna (no pública). El uso de esta API puede provocar conflictos. El patrón descrito a continuación produce un error si ya existe un acelerador de tipos con el mismo nombre al importar el módulo. También quita los aceleradores de tipos al quitar el módulo de la sesión.

Este patrón garantiza que los tipos están disponibles en una sesión. No afecta a IntelliSense ni a la finalización al crear un archivo de script en VS Code. Para obtener intelliSense y sugerencias de finalización para tipos personalizados en VS Code, debe agregar una using module instrucción a la parte superior del script.

El siguiente patrón muestra cómo puede registrar clases y enumeraciones de PowerShell como aceleradores de tipos en un módulo. Agregue el fragmento de código al módulo de script raíz después de cualquier definición de tipo. Asegúrese de que la $ExportableTypes variable contiene cada uno de los tipos que desea que estén disponibles para los usuarios cuando importen el módulo. El otro código no requiere ninguna edición.

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

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

Cuando los usuarios importan el módulo, los tipos agregados a los aceleradores de tipos para la sesión están disponibles inmediatamente para IntelliSense y la finalización. Cuando se quita el módulo, por lo que son los aceleradores de tipos.

Importación manual 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.

Si un módulo define clases y enumeraciones, pero no agrega aceleradores de tipos para esos tipos, use una using module instrucción para importarlas.

La using module instrucción importa clases y enumeraciones desde el módulo raíz (ModuleToProcess) de un módulo de script o un módulo binario. No importa de forma coherente las clases definidas en módulos anidados o clases definidas en scripts con origen de puntos en el módulo raíz. Defina las clases que desea que estén disponibles para los usuarios fuera del módulo directamente en el módulo raíz.

Para obtener más información sobre la using instrucción , consulte about_Using.

Carga del código recién cambiado durante el desarrollo

Durante el desarrollo de un módulo de script, es habitual realizar cambios en el código y, a continuación, cargar la nueva versión del módulo mediante Import-Module con el parámetro Force . La recarga del módulo solo funciona para los cambios en las funciones del módulo raíz. Import-Module no vuelve a cargar ningún módulo anidado. Además, no hay forma de cargar ninguna clase actualizada.

Para asegurarse de que ejecuta la versión más reciente, debe iniciar una nueva sesión. Las clases y enumeraciones definidas en PowerShell y importadas con una using instrucción no se pueden descargar.

Otra práctica de desarrollo común es separar el código en archivos diferentes. Si tiene una función en un archivo que usa clases definidas en otro módulo, debe usar la using module instrucción para asegurarse de que las funciones tienen las definiciones de clase necesarias.

El tipo PSReference no se admite con miembros de clase

El [ref] acelerador de tipos es abreviado para la clase PSReference . Si se usa [ref] para convertir tipos, se produce un error en un miembro de clase de forma silenciosa. Las API que usan [ref] parámetros no se pueden usar con miembros de clase. La clase PSReference se diseñó para admitir objetos COM. Los objetos COM tienen casos en los que es necesario pasar un valor por referencia.

Para obtener más información, vea CLASE PSReference.

Limitaciones

En las listas siguientes se incluyen limitaciones para definir clases de PowerShell y soluciones alternativas para esas limitaciones, si las hay.

Limitaciones generales

  • Los miembros de clase no pueden usar PSReference como su tipo.

    Solución alternativa: Ninguna.

  • Las clases de PowerShell no se pueden descargar ni volver a cargar en una sesión.

    Solución alternativa: inicie una nueva sesión.

  • Las clases de PowerShell definidas en un módulo no se importan automáticamente.

    Solución alternativa: agregue los tipos definidos a la lista de aceleradores de tipos en el módulo raíz. Esto hace que los tipos estén disponibles en la importación del módulo.

  • Las hidden palabras clave y static solo se aplican a los miembros de clase, no a una definición de clase.

    Solución alternativa: Ninguna.

Limitaciones del constructor

  • El encadenamiento de constructores no se implementa.

    Solución alternativa: defina métodos ocultos Init() y llámelos desde los constructores.

  • Los parámetros de constructor no pueden usar ningún atributo, incluidos los atributos de validación.

    Solución alternativa: reasignar los parámetros en el cuerpo del constructor con el atributo de validación.

  • Los parámetros del constructor no pueden definir valores predeterminados. Los parámetros siempre son obligatorios.

    Solución alternativa: Ninguna.

  • Si alguna sobrecarga de un constructor está oculta, todas las sobrecargas del constructor también se tratan como ocultas.

    Solución alternativa: Ninguna.

Limitaciones del método

  • Los parámetros de método no pueden usar ningún atributo, incluidos los atributos de validación.

    Solución alternativa: vuelva a asignar los parámetros en el cuerpo del método con el atributo de validación o defina el método en el constructor estático con el Update-TypeData cmdlet .

  • Los parámetros del método no pueden definir valores predeterminados. Los parámetros siempre son obligatorios.

    Solución alternativa: defina el método en el constructor estático con el Update-TypeData cmdlet .

  • Los métodos siempre son públicos, incluso cuando están ocultos. Se pueden invalidar cuando se hereda la clase .

    Solución alternativa: Ninguna.

  • Si alguna sobrecarga de un método está oculta, todas las sobrecargas de ese método también se tratan como ocultas.

    Solución alternativa: Ninguna.

Limitaciones de propiedades

  • Las propiedades estáticas siempre son mutables. Las clases de PowerShell no pueden definir propiedades estáticas inmutables.

    Solución alternativa: Ninguna.

  • Las propiedades no pueden usar el atributo ValidateScript , ya que los argumentos de atributo de propiedad de clase deben ser constantes.

    Solución alternativa: defina una clase que herede del tipo ValidateArgumentsAttribute y use ese atributo en su lugar.

  • Las propiedades declaradas directamente no pueden definir implementaciones personalizadas de captador y establecedor.

    Solución alternativa: defina una propiedad oculta y use Update-TypeData para definir la lógica de captador y establecedor visibles.

  • Las propiedades no pueden usar el atributo Alias . El atributo solo se aplica a parámetros, cmdlets y funciones.

    Solución alternativa: use el Update-TypeData cmdlet para definir alias en los constructores de clase.

  • Cuando una clase de PowerShell se convierte en JSON con el ConvertTo-Json cmdlet , el JSON de salida incluye todas las propiedades ocultas y sus valores.

    Solución alternativa: ninguna

Limitaciones de herencia

  • 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