PowerShell 类:入门指南

PowerShell是一种面向对象的语言。当你运行命令时,在屏幕上看到的输出是对象。对象并非凭空出现;开发人员通过类来创建它们,更具体地说,是通过实例化类来创建它们。PowerShell类表示这些对象的定义或模式。

虽然你可能熟悉使用诸如New-Objectpscustomobject类型加速器的命令来创建对象,但这些并不是“新”对象。这些方法生成的对象属于特定的类型。PowerShell类定义了类型

在本教程中,你将学习如何开始使用PowerShell类。你将创建你的第一个带有构造函数的类,了解如何从你的类创建对象,并为你的类添加属性和方法。

关于对象、属性和方法等术语的背景,请查阅博文回归基础:理解PowerShell对象

先决条件

创建你的第一个类和对象

在你深入学习PowerShell类的各个方面之前,你应该先创建一个简单的类。更高级的主题将在后面介绍。

创建你的第一个类会感觉有点像创建一个函数。基本语法是相同的。一个类是从定义中创建的,就像一个函数一样。不像函数那样,第一行不是以function开头,后跟函数名,而是以class开头,后跟你的对象类型的名称。

下面你会看到一个叫做student的类的基本结构。

class student {

}

类具有看起来像描述该类的属性的参数。下面的示例显示了一个名为student的类,具有两个属性:FirstNameLastName

当你定义一个属性时,应该总是定义一个类型,以设置属性值可以保存的特定“模式”。在下面的示例中,这两个属性都被定义为字符串

你应该总是定义属性类型。稍后你会看到为什么。

class student {
    [string]$FirstName
    [string]$LastName
}

定义了类之后,可以从中创建对象或实例化对象。有多种实例化对象的方式;一种常见的方式是使用类型加速器,比如[student],它表示该类,后跟每个类都带有的一个默认方法,名为new()

使用类型加速器快捷方式与使用New-Object命令创建对象是相同的。

New-Object -TypeName student

一旦您从该类创建了一个对象,然后为属性分配值。下面的示例是为FirstNameLastName属性分配值为TylerMuir

class student {
    [string]$FirstName
    [string]$LastName
}
$student1 = [student]::new()
$student1.FirstName = 'Tyler'
$student1.LastName = 'Muir'
$student1

一旦您创建了对象并为属性分配了值,通过调用分配对象的变量来检查对象,如下所示。

Creating an object from the student class.

现在您从类创建了一个对象,就像在PowerShell中对待任何其他对象一样,将该对象传递给Get-Member cmdlet进行检查。您可以在下面看到$student1变量中包含的对象的类型是student

类名将始终与对象类型对应。

注意,Get-Member返回四个方法和两个属性。属性可能看起来很熟悉,但方法确实不太一样。PowerShell默认添加了某些方法,但您可以添加自己的方法,甚至修改默认方法。

Showing members from custom student object.

创建方法

在上面的示例中,您看到对象上有一些默认方法,但很有可能您想创建自己的方法。为此,您必须在类定义内部定义一个或多个方法。

A method definition looks like below with an output type that defines what type of object is returned from this method, the name of the method, and the code to execute inside of a scriptblock.

[<output type>]<name>() {
	<code that runs when the method is executed>
}

请注意名称后面的括号()。这是您可以定义方法参数的地方(稍后会介绍)。方法参数允许您像函数参数一样更改方法的功能。

如果你以前编写并运行过PowerShell函数,那么方法脚本块应该会让你感到熟悉,但是有一些关于方法的特殊规则你应该知道。

return是强制的

PowerShell函数将通过在函数中的任何位置放置对象来返回对象,就像下面的例子一样。

function foo {
	$object = Get-Service
	$object ## 只是将对象发送到管道
}

然而,与函数不同的是,如果方法返回一个对象,你必须使用如下所示的return构造。

[string]GetName() {
	return 'foo'
}

使用$this变量

方法与函数的另一个区别是$this变量。在方法内部定义的$this变量引用当前对象的属性或其他方法。

下面是一个名为GetName()的方法的示例,它被添加到student类中,该方法连接FirstNameLastName属性的值并返回它们。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
}

现在你可以使用点表示法调用GetName()方法,如下所示。如果你之前为FirstNameLastName分配了值,GetName()将返回它们。

Creating a class with a method and showing to output.

方法添加参数

在上面的例子中,当你运行$student1.GetName()这一行时,你是按原样调用GetName()方法的。在括号内,你可以像函数一样定义参数。

GetName()方法只返回FirstNameLastName属性设置的任何值。但如果你更愿意有一个方法来设置 GetName() 然后可以获取的属性值呢?在这种情况下,您需要定义方法参数。

通过在方法参数括号中包含一个或多个用逗号分隔的参数来定义方法参数,如下所示。

注意[void]输出类型。每当方法不输出任何内容时,您不需要return结构,并且应该将输出类型定义为[void]以告诉 PowerShell 该方法不返回任何内容。

[void]SetName([string]$Name) {

}

例如,也许SetName()方法接受一个完整的姓名(名字和姓氏)。如果是这样,在脚本块中,您可以拆分此字符串并以这种方式分配名字和姓氏。

通过将SetName()方法插入student类,以下是它的样子。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
}

现在您可以将一个完整的姓名作为参数传递给SetName()方法,这将设置当前对象的FirstNameLastName属性。

Creating a class with a void method and showing the output when it is run.

方法重载

也许您想为一个方法定义不同的参数集。类似于在函数和 cmdlet 中工作的参数集,您可以定义不同的参数“上下文”或方法签名。

也许您想通过将一个完整的姓名传递给`SetName()`方法或分别传递名字和姓氏来设置`FirstName`和`LastName`参数。您不必选择; 您可以使用方法签名同时定义它们。

当您在类中定义多个方法签名时,这被称为重载。

重复使用前一节中的示例,您可以为`SetName()`方法创建一个重载,以接受两个字符串而不是一个。当您传递两个字符串而不是一个时,`SetName()`方法假定第一个字符串是`FirstName`,第二个字符串是`LastName`。有了这个重载,类会像下面这样。

class student {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
    
    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}
Showing that the method overload works by providing either one or two strings.

类构造函数

每当您使用`new()`方法或其他方式实例化一个对象时,您可以告诉PowerShell运行一些用户定义的代码,称为构造函数。构造函数类似于方法,但它们在PowerShell实例化对象时会自动运行。

每个类都有一个默认构造函数。这个默认构造函数并不做太多事情; 它只负责实例化对象。您可以通过查看`New`方法的输出来看到默认构造函数。您可以在下面看到这行返回一个单独的`new()`方法。

[student]::New
Default PowerShell constructor

构造函数重载

也许您想在创建对象时立即为FirstNameLastName属性设置值,而不是使用典型的点表示法。在这种情况下,您可以创建一个带有参数的构造函数,然后调用SetName()

以下是student类的构造函数示例。请注意,构造函数没有特定的名称,也没有输出类型。构造函数总是使用与类相同的名称。

在构造函数中调用现有方法使我们能够重用我们已经编写的方法来处理设置变量。

student([string]$Name) {
	$this.SetName($Name)
}

下面您将看到构造函数添加到student类中。

class student {
    [string]$FirstName
    [string]$LastName
    
    student([string]$Name) {
        $this.SetName($Name)
    }

    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

当您实例化一个新的student对象并传入一个字符串参数时,对象属性将立即具有预期的值。

Showing the output from using the constructor with a string

现在您可以再次使用带有[student]::New的重载构造函数。现在请注意,新的重载构造函数定义了一个Name参数。

Overloaded constructor

定义默认和重载构造函数

现在您在student类上有了一个重载的构造函数,PowerShell会覆盖默认构造函数。但是,您可以通过手动创建一个不带参数的构造函数来重新获取它。

student() {}

您可以在下面的student类中看到它是什么样子的。

class student {
    [string]$FirstName
    [string]$LastName

    student([string]$Name) {
        $this.SetName($Name)
    }

    student() {}

    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }
}

现在再次检查构造函数。您现在将看到两个构造函数都显示出来。

[student]::New
Creating a default and custom constructor

类继承

与所有其他面向对象的语言一样,您可以使用多个类在PowerShell中按层次结构构建类。 每个类都可以有“父”和“子”类,从不太具体、更通用的目的开始,并增加特异性。

例如,我们的student类代表一个大学生(子/具体)。该大学生是一个人(父/通用)。这两个概念是相关的,并形成了一个层次结构。

A child class can inherit a parent class which means it can hold all properties and methods (members) defined via a parent class. We know that a person class may have properties like eye_color, height, and weight and perhaps a method called SetHeight().

如果一个学生是一个人,那么这个学生仍然具有那些属性和方法。在student类上实现person类已经具有的相同成员将会产生重复的工作。您可以定义类继承来自动在student类上定义person类的所有成员。

如果现在这些都不合理,那么当我们演示时将会清楚。

类继承演示

首先,复制您之前创建的student类,删除构造函数并将其重命名为person类。您的person类应该像下面这个类一样。

A student, of course, has a first name and last name, but the class can be described more accurately by labeling it as a person. When creating a more “generic” class like this, you can create more specific “child” classes from it.

class person {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

现在,创建几个代表人但具有更具体角色的类。例如,在下面的代码片段中,您现在有一个teacher和一个student类。

class teacher {
    [int]$EmployeeId
}

class student {
    [int]$StudentId
}

目前,teacherstudent类与person类相互独立。它们之间没有关系,但不能继承person类的任何类成员。让我们改变这一点。

现在通过将teacherstudent类定义为person类的“子”类来定义该层次结构。您可以通过在类名后附加冒号(:)并跟随父类的名称来定义继承,如下所示。

class teacher : person {
    [int]$EmployeeId
}

class student : person {
    [int]$StudentId
}

您的整个类脚本现在应该如下所示:

class person {
    [string]$FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }
}

class teacher : person {
    [int]$EmployeeId
}

class student : person {
    [int]$StudentId
}

在这一点上,当您实例化teacherstudent类的对象时,这两个类将具有与person类相同的成员。

Class Inheritance Demo

使用构造函数继承

如上所示,类方法通过类继承而继承。这种行为可能会让您认为构造函数会遵循相同的逻辑,但您会错。构造函数不会被继承,所有子类的构造函数必须在每个子类中单独定义。

例如,也许您刚刚为person类定义了一个重载的构造函数,但未为teacher类定义构造函数,如下所示。

class person {
    [string]hidden $FirstName
    [string]$LastName
    
    [string]GetName() {
        return "$($this.FirstName) $($this.LastName)"
    }
    
    [void]SetName([string]$Name) {
        $this.FirstName = ($Name -split ' ')[0]
        $this.LastName = ($Name -split ' ')[1]
    }

    [void]SetName([string]$FirstName,[string]$LastName) {
        $this.FirstName = $FirstName
        $this.LastName = $LastName
    }

		person([string]$Name) {
			$this.SetName($Name)
		}
}

然后,您定义了一个子类,例如teacher,并尝试从中创建一个没有参数的对象,如下所示。请注意,PowerShell返回错误,因为teacher类内未定义无参数的构造函数。

No class defined for child class

A constructor is not necessary in a child class if you’re only using it as a template. Alternatively, if you want to use the parent class as its own standalone class and as a parent class you can include constructors. But you have to make sure that the parent class has a constructor that matches the ones in the child classes.

类成员属性

就像PowerShell命令参数一样,类可以具有成员属性。这些属性修改每个成员的行为。

隐藏成员

如果您仅将类成员用于内部目的,并且不希望用户读取或写入它,则将成员设置为隐藏。例如,也许您有一个仅由其他方法使用的方法。没有必要向用户公开该方法。

要定义隐藏成员,请使用如下所示的hidden属性。

class teacher {
    [int]hidden $EmployeeId
}

现在,当您使用Get-Member检查所有对象成员时,该属性不会显示出来。

Class Member Attributes

将类成员设置为隐藏不会限制对值的访问;它只是将其隐藏起来。您不应隐藏属性以存储敏感数据。

静态成员

回想一下之前;本教程使用术语“实例化”来描述从类创建对象。当您实例化一个对象时,该对象继承了类定义的所有属性和方法。但情况并非总是如此。

有时,您不需要实例化整个对象的开销。相反,您需要快速引用单个类成员。在这种情况下,您可以将类成员设置为静态

hidden属性一样,使用下面示例中的static关键字将类成员定义为静态。

class student {
	[int]static $MaxClassCount = 7
}

与典型的类成员不同,PowerShell不会根据静态类成员创建属性和方法。当您将类成员定义为静态时,就像隐藏成员一样,当您使用Get-Member时它不会显示出来。

Static Members

例如,也许您想将大学课程名称与student类相关联,并定义学生可以参加的大学课程的最大数量。为此,您创建一个Classes数组成员和一个MaxClassCount成员。

由于用户很少需要更改MaxClassCount成员,您决定将其定义为静态的。

最后,您创建一个AddClass()方法来添加一个课程到学生的课程表中,但只有当它小于MaxClassCount时才会添加。

class student {
    [string[]]$Classes = @()
    [int]static $MaxClassCount = 7
    
    [void]AddClass ([string]$Name) {
        if ($this.Classes.Count -lt [student]::MaxClassCount) {
            $this.Classes += $Name
        }
    }
}

现在,当您尝试创建一个新的student对象并为其分配过多的大学课程时,PowerShell将只分配最大数量,即七个。

$student1 = [student]::new()
'PE','English','Math','History','Computer Science','French','Wood Working','Cooking' | ForEach-Object {
	$student1.AddClass($_)
}
$student1.Classes
Class Count

您可以随时更改静态成员的值。例如,如果您想将MaxClassCount成员改为5而不是7,您可以使用[student]::MaxClassCount = 5来更改该值。在此示例中,更改该值不会反向移除超出限制的课程。

结论

PowerShell类模糊了脚本语言和编程语言之间的界限。类是定义对象关系、添加与对象交互和格式化的方式的良好方法,这些方式通常只能通过编写专门的函数来实现。

Source:
https://adamtheautomator.com/powershell-classes/