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 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 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 de 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.
Syntax
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 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 la 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 acceder 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 siguiente fragmento de código 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:
Exception:
Line |
84 | throw "Book '$Book' already in list"
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Book 'The Hobbit by J.R.R. Tolkien (1937)' already in list
Ejemplo 4: ejecución en paralelo que daña un espacio de ejecución
El ShowRunspaceId()
método de notifica distintos identificadores de [UnsafeClass]
subproceso, pero el mismo identificador de espacio de ejecución. Finalmente, el estado de sesión está dañado, lo que provoca un error, como Global scope cannot be removed
.
# Class definition with Runspace affinity (default behavior)
class UnsafeClass {
static [object] ShowRunspaceId($val) {
return [PSCustomObject]@{
ThreadId = [Threading.Thread]::CurrentThread.ManagedThreadId
RunspaceId = [runspace]::DefaultRunspace.Id
}
}
}
$unsafe = [UnsafeClass]::new()
while ($true) {
1..10 | ForEach-Object -Parallel {
Start-Sleep -ms 100
($using:unsafe)::ShowRunspaceId($_)
}
}
Nota
Este ejemplo se ejecuta en un bucle infinito. Escriba Ctrl+C para detener la ejecución.
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 número 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 conGet-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 a ellos, heredar y modificarlos. Ocultar un miembro no lo hace privado. Solo oculta el miembro como se describe en los puntos anteriores.
Nota
Al ocultar cualquier sobrecarga para un método, ese método se quita de IntelliSense, los resultados de finalización y la salida predeterminada de Get-Member
.
Al ocultar cualquier constructor, la new()
opción se quita de IntelliSense y de 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, vea about_Classes_Constructors.
Static (palabra clave)
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 . Un método estático siempre está disponible. 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 ampliar 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 la clase base según sea necesario.
PowerShell no admite varias herencias. Las clases no se 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 cómo derivar clases que heredan de una clase base o implementar interfaces, consulte about_Classes_Inheritance.
Afinidad de espacio de ejecución
Un espacio de ejecución es el entorno operativo para los comandos invocados por PowerShell. Este entorno incluye los comandos y los datos que están presentes actualmente y las restricciones de idioma que se aplican actualmente.
Una clase de PowerShell está afiliada al espacio de ejecución donde se crea. El uso de una clase de PowerShell en ForEach-Object -Parallel
no es seguro.
Las invocaciones de método en la clase se vuelven a serializar en el espacio de ejecución donde se creó, lo que puede dañar el estado del espacio de ejecución o provocar un interbloqueo.
Para obtener una ilustración de cómo la afinidad de espacio de ejecución puede causar errores, vea Ejemplo 4.
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
Al agregar aceleradores de tipos a la sesión, se 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 quiere 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, todos 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 del 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 que tienen el 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 , vea about_Using.
Carga de 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 . Volver a cargar el 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 ninguna manera 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 se importan 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 . El uso [ref]
de para convertir tipos de un miembro de clase produce un error en modo silencioso. 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 de módulos.
Las
hidden
palabras clave ystatic
solo se aplican a los miembros de clase, no a una definición de clase.Solución alternativa: ninguna.
Las clases de PowerShell no son seguras para su uso en la ejecución en paralelo entre espacios de ejecución. Al invocar métodos en una clase, PowerShell serializa las invocaciones al espacio de ejecución donde se creó la clase, lo que puede dañar el estado del espacio de ejecución o provocar un interbloqueo.
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 del 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 visible.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 el 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 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 se puede definir 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 lausing 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.