Compartilhar via


Sobre as aulas

Descrição curta

Descreve como você pode usar classes para criar seus próprios tipos personalizados.

Descrição longa

O PowerShell 5.0 adiciona uma sintaxe formal para definir classes e outros tipos definidos pelo usuário. A adição de classes permite que desenvolvedores e profissionais de TI adotem o PowerShell para uma gama mais ampla de casos de uso. Ele simplifica o desenvolvimento de artefatos do PowerShell e acelera a cobertura de superfícies de gerenciamento.

Uma declaração de classe é um blueprint usado para criar instâncias de objetos em tempo de execução. Quando você define uma classe, o nome da classe é o nome do tipo. Por exemplo, se você declarar uma classe chamada Device e inicializar uma variável $dev para uma nova instância do Device, $dev será um objeto ou uma instância do tipo Device. Cada instância do dispositivo pode ter valores diferentes em suas propriedades.

Cenários com suporte

  • Defina tipos personalizados no PowerShell usando semântica de programação orientada a objetos familiar, como classes, propriedades, métodos, herança etc.
  • Depurar tipos usando a linguagem do PowerShell.
  • Gere e trate exceções usando mecanismos formais.
  • Defina os recursos de DSC e seus tipos associados usando a linguagem do PowerShell.

Sintaxe

As classes são declaradas usando a seguinte sintaxe:

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

As classes são instanciadas usando uma das seguintes sintaxes:

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

Observação

Ao usar a sintaxe [<class-name>]::new(, os colchetes ao redor do nome da classe são obrigatórios. Os colchetes sinalizam uma definição de tipo para o PowerShell.

Exemplo de sintaxe e uso

Este exemplo mostra a sintaxe mínima necessária para criar uma classe utilizável.

class Device {
    [string]$Brand
}

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

Propriedades de classe

Propriedades são variáveis declaradas no escopo da classe. Uma propriedade pode ser de qualquer tipo interno ou uma instância de outra classe. As classes não têm restrição no número de propriedades que possuem.

Classe de exemplo com propriedades 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

Exemplos de tipos complexos em propriedades de classe

Este exemplo define uma classe Rack vazia usando a classe Device . Os exemplos, a seguir deste, mostram como adicionar dispositivos ao rack e como começar com um rack pré-carregado.

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 classe

Os métodos definem as ações que uma classe pode executar. Os métodos podem usar parâmetros que fornecem dados de entrada. Os métodos podem retornar a saída. Os dados retornados por um método podem ser qualquer tipo de dados definido.

Exemplo de classe simples com propriedades e métodos

Estendendo a classe Rack para adicionar e remover dispositivos dela.

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

Saída em métodos de classe

Os métodos devem ter um tipo de retorno definido. Se um método não retornar saída, o tipo de saída deverá ser [void].

Nos métodos de classe, nenhum objeto é enviado para o pipeline, exceto aqueles mencionados na return instrução. Não há saída acidental para o pipeline do código.

Observação

Isso é fundamentalmente diferente de como as funções do PowerShell lidam com a saída, em que tudo vai para o pipeline.

Saída do método

Este exemplo não demonstra nenhuma saída acidental para o pipeline de métodos de classe, exceto na return instrução.

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

Construtor

Os construtores permitem definir valores padrão e validar a lógica do objeto no momento da criação da instância da classe. Construtores têm o mesmo nome da classe. Os construtores podem ter argumentos para inicializar os membros de dados do novo objeto.

A classe pode ter zero ou mais construtores definidos. Se nenhum construtor for definido, a classe recebe um construtor sem parâmetros padrão. Esse construtor inicializa todos os membros para seus valores padrão. Tipos de objeto e cadeias de caracteres recebem valores nulos. Quando você define o construtor, nenhum construtor sem parâmetro padrão é criado. Crie um construtor sem parâmetros se for necessário.

Sintaxe básica do construtor

Neste exemplo, a classe Device é definida com propriedades e um construtor. Para usar essa classe, o usuário deve fornecer valores para os parâmetros listados no construtor.

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

Exemplo com vários construtores

Neste exemplo, a classe Device é definida com propriedades, um construtor padrão e um construtor para inicializar a instância.

O construtor padrão define a marca como Undefined e deixa model e vendor-sku com valores nulos.

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

O hidden atributo torna uma propriedade ou método menos visível. A propriedade ou o método ainda está acessível ao usuário e está disponível em todos os escopos em que o objeto está disponível. Os membros ocultos ficam ocultos do cmdlet e não podem ser exibidos usando o Get-Member preenchimento de tabulação ou o IntelliSense fora da definição de classe.

Exemplo usando atributos ocultos

Quando um objeto Rack é criado, o número de slots para dispositivos é um valor fixo que não deve ser alterado em nenhum momento. Esse valor é conhecido no momento da criação.

O uso do atributo oculto permite que o desenvolvedor mantenha o número de slots ocultos e evita alterações não intencionais no tamanho do rack.

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 a propriedade Slots não é mostrada na $r1 saída. No entanto, o tamanho foi alterado pelo construtor.

Atributo estático

O static atributo define uma propriedade ou um método que existe na classe e não precisa de instância.

Uma propriedade estática está sempre disponível, independente da instanciação de classe. Uma propriedade estática é compartilhada em todas as instâncias da classe. Um método estático está sempre disponível. Todas as propriedades estáticas são dinâmicas para todo o intervalo de sessão.

Exemplo usando atributos e métodos estáticos

Suponha que os racks instanciados aqui existam em seu data center. Portanto, você gostaria de acompanhar os racks em seu 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))
        }
    }
}

Testando a propriedade estática e o método existem

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 o número de racks aumenta cada vez que você executa este exemplo.

Atributos de validação de propriedade

Os atributos de validação permitem testar os valores fornecidos às propriedades que atendem aos requisitos definidos. A validação é disparada no momento em que o valor é atribuído. Veja about_functions_advanced_parameters.

Exemplo usando atributos de validação

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

Herança em classes do PowerShell

Você pode estender uma classe criando uma nova classe que deriva de uma classe existente. A classe derivada herda as propriedades da classe base. Você pode adicionar ou substituir métodos e propriedades conforme necessário.

O PowerShell não dá suporte a várias heranças. As classes não podem herdar de mais de uma classe. No entanto, você pode usar interfaces para essa finalidade.

A implementação da herança é definida pelo operador; o : que significa estender essa classe ou implementar essas interfaces. A classe derivada sempre deve ser a mais à esquerda na declaração de classe.

Exemplo usando sintaxe de herança simples

Este exemplo mostra a sintaxe simples de herança de classe do PowerShell.

Class Derived : Base {...}

Este exemplo mostra a herança com uma declaração de interface após a classe base.

Class Derived : Base.Interface {...}

Exemplo de herança simples em classes do PowerShell

Neste exemplo, as classes Rack e Device usadas nos exemplos anteriores são melhor definidas para: evitar repetições de propriedades, alinhar melhor as propriedades comuns e reutilizar a lógica de negócios comum.

A maioria dos objetos no data center são ativos da empresa, o que faz sentido começar a rastreá-los como ativos. Os tipos de dispositivo são definidos pela enumeração, consulte DeviceType para obter detalhes sobre enumerações.

Em nosso exemplo, estamos apenas definindo Rack e ComputeServer; ambas as extensões para a Device classe.

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

Chamando construtores de classe base

Para invocar um construtor de classe base de uma subclasse, adicione a base palavra-chave.

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 classe base

Para substituir métodos existentes em subclasses, declare métodos usando o mesmo nome e assinatura.

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

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

2

Para chamar métodos de classe base de implementações substituídas, converta para a classe base ([baseclass]$this) na invocação.

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

A sintaxe para declarar interfaces é semelhante ao C#. Você pode declarar interfaces após tipos base ou imediatamente após dois-pontos (:) quando não houver nenhum tipo base especificado. Separe todos os nomes de tipo com vírgulas.

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

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

Importando classes de um módulo do PowerShell

Import-Module e a instrução #requires importam apenas as funções, os aliases e as variáveis do módulo, conforme definido pelo módulo. As classes não são importadas. A using module instrução importa as classes definidas no módulo. Se o módulo não for carregado na sessão atual, a using instrução falhará.

Consulte também