30

我有一个模块设置,就像其他一些脚本的库一样。我不知道如何将类声明放入脚本范围调用Import-Module中。我试图安排Export-Module一个-class参数,比如-function,但没有-class可用的。我只需要在每个脚本中声明类吗?

设置:

  • 〜\documents\windows\powershell\modules\holidays\中的holidays.psm1
  • 活动脚本调用import-module holidays
  • Holiday.psm1 中有另一个函数可以正确返回一个类对象,但我不知道如何在导入后从活动脚本创建类的新成员

下面是这个类的样子:

Class data_block
{
    $array
    $rows
    $cols
    data_block($a, $r, $c)
    {
        $this.array = $a
        $this.rows = $r
        $this.cols = $c
    }
}
4

9 回答 9

24

PSA:有一个已知问题是在内存中保留类的旧副本。如果你不知道的话,它会让使用类变得非常混乱。你可以在这里阅读。


using容易陷入陷阱

using关键字容易出现以下各种陷阱:

  • 除非您在语句中指定模块的完整路径,否则该using语句不适用于不在的模块。这是相当令人惊讶的,因为尽管通过语句可用的模块可能无法工作,具体取决于模块的加载方式。PSModulePathusingGet-Moduleusing
  • using语句只能在“脚本”的开头使用。没有任何组合[scriptblock]::Create()New-Module似乎可以克服这一点。传递给的字符串Invoke-Expression似乎充当了一种独立的脚本;using在这种字符串类型的工作开头的语句。也就是说,Invoke-Expression "using module $path"可以成功,但模块内容可用的范围似乎相当难以理解。例如,如果Invoke-Expression "using module $path"在 Pester 脚本块中使用 ,则模块内的类在同一个 Pester 脚本块中不可用。

以上陈述是基于这组测试

ScriptsToProcess防止访问私有模块功能

在模块清单引用的脚本中定义一个类,ScriptsToProcess乍一看似乎是从模块中导出类。但是,它不是导出类,而是“在全局 SessionState 而不是模块中创建类,因此它......无法访问私有函数”。据我所知,使用ScriptsToProcess就像以以下方式在模块外部定义类:

#  this is like defining c in class.ps1 and referring to it in ScriptsToProcess
class c {
    [string] priv () { return priv }
    [string] pub  () { return pub  }
}

# this is like defining priv and pub in module.psm1 and referring to it in RootModule
New-Module {
    function priv { 'private function' }
    function pub  { 'public function' }
    Export-ModuleMember 'pub'
} | Import-Module

[c]::new().pub()  # succeeds
[c]::new().priv() # fails

调用这会导致

public function
priv : The term 'priv' is not recognized ...
+         [string] priv () { return priv } ...

priv即使priv从导入该模块时定义的类调用模块函数,该类也无法访问该模块函数。这可能是您想要的,但我还没有找到它的用途,因为我发现类方法通常需要访问模块中我想保持私有的某些函数。

.NewBoundScriptBlock()似乎工作可靠

调用绑定到包含该类的模块的脚本块似乎可以可靠地导出类的实例,并且不会受到陷阱的影响using。考虑这个包含一个类并已导入的模块:

New-Module 'ModuleName' { class c {$p = 'some value'} } |
    Import-Module

[c]::new()在绑定到模块的脚本块内调用会生成一个类型为 的对象[c]

PS C:\> $c = & (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
PS C:\> $c.p
some value

惯用的替代方法.NewBoundScriptBlock()

似乎有一个更短的、惯用的替代.NewBoundScriptBlock(). 以下两行分别调用模块输出的会话状态中的脚本块Get-Module

& (Get-Module 'ModuleName').NewBoundScriptBlock({[c]::new()})
& (Get-Module 'ModuleName') {[c]::new()}}

后者的优点是当对象被写入管道时,它将向管道中间脚本块产生控制流。 .NewBoundScriptBlock()另一方面,收集写入管道的所有对象,并且仅在整个脚本块的执行完成后才产生。

于 2016-11-05T18:21:46.247 回答
18

我找到了一种无需“使用模块”即可加载类的方法。在您的 MyModule.psd1 文件中使用以下行:

ScriptsToProcess = @('Class.ps1')

然后将您的类放入 Class.ps1 文件中:

class MyClass {}

更新:尽管您不必在此方法中使用“使用模块 MyModule”,但您仍然必须:

  • 运行“使用模块 MyModule”
  • 或运行“Import-Module MyModule”
  • 或者调用你的模块中的任何函数(这样它会在途中自动导入你的模块)

更新 2:这会将类加载到当前范围,因此如果您从函数内导入模块,例如,类将无法在函数外部访问。可悲的是,我看到的唯一可靠的方法是用 C# 编写你的类并用Add-Type -Language CSharp -TypeDefinition 'MyClass...'.

于 2017-10-05T01:49:41.220 回答
15

根据herehere,您可以通过在PowerShell 5中执行以下操作来使用模块中定义的类:

using module holidays
于 2016-08-01T14:32:25.763 回答
5

如果它对你有用,那么using语句就是你要走的路。否则,这似乎也有效。

文件testclass.psm1

使用函数传递课程

class abc{
    $testprop = 'It Worked!'
    [int]testMethod($num){return $num * 5}
}

function new-abc(){
    return [abc]::new()
}

Export-ModuleMember -Function new-abc

文件someScript.ps1

Import-Module path\to\testclass.psm1
$testclass = new-abc
$testclass.testProp        # Returns 'It Worked!'
$testclass.testMethod(500) # Returns 2500


$testclass | gm


Name        MemberType Definition
----        ---------- ----------
Equals      Method     bool Equals(System.Object obj)
GetHashCode Method     int GetHashCode()
GetType     Method     type GetType()
testMethod  Method     int testMethod(System.Object num)
ToString    Method     string ToString()
testprop    Property   System.Object testprop {get;set;}
于 2018-02-07T17:00:00.470 回答
4

这肯定不会按预期工作。
PowerShell 5 中的想法是,您可以在扩展名为 .psm1 的单独文件中定义您的类。
然后您可以使用命令(例如)加载定义:

using module C:\classes\whatever\path\to\file.psm1

这必须是脚本中的第一行(注释之后)。

造成如此痛苦的原因是,即使从脚本调用类定义,模块也会为整个会话加载。你可以通过运行看到这一点:

Get-Module

您将看到您加载的文件的名称。无论您是否再次运行该脚本,它都不会重新加载类定义!(它甚至不会读取 psm1 文件。)这会导致咬牙切齿。

有时 -有时- 您可以在运行脚本之前运行此命令,这将使用刷新的类定义重新加载模块:

Remove-Module  file

其中 file 是没有路径或扩展名的名称。但是,为了保持理智,我建议重新启动 PowerShell 会话。这显然很麻烦;微软需要以某种方式清理它。

于 2017-02-23T23:53:16.170 回答
3

你几乎不能。根据about_Classes帮助:

类关键字

定义一个新类。这是真正的 .NET Framework 类型。类成员是公共的,但仅在模块范围内是公共的。您不能将类型名称引用为字符串(例如,New-Object 不起作用),并且在此版本中,您不能在脚本之外使用类型文字(例如,[MyClass])/定义类的模块文件。

这意味着,如果您想为自己获取一个data_block实例或使用操作这些类的函数,请创建一个函数,New-DataBlock并使其返回一个新data_block实例,然后您可以使用它来获取类方法和属性(可能包括静态的) .

于 2015-06-25T14:50:52.323 回答
3

我也遇到了有关 v5 中 PowerShell 类的多个问题。

我现在决定使用以下解决方法,因为它与 .NET 和 PowerShell 完全兼容:

Add-Type -Language CSharp -TypeDefinition @"
namespace My.Custom.Namespace {
    public class Example
    {
        public string Name { get; set; }
        public System.Management.Automation.PSCredential Credential { get; set; }
        // ...
    }
}
"@

好处是您不需要自定义程序集来添加类型定义。您可以在 PowerShell 脚本或模块中内联添加类定义。

唯一的缺点是您将需要创建一个新的运行时以在第一次加载类定义后重新加载类定义(就像在 C#/.NET 域中加载程序集一样)。

于 2017-01-05T16:21:21.697 回答
1

我解决此问题的方法是将您的自定义类定义移动到具有相同名称的空 .ps1 文件中(就像在 Java/C# 中一样),然后将其加载到模块定义和您的依赖代码中点源。我知道这不是很好,但对我来说,这比在多个文件中维护同一个类的多个定义要好......

于 2016-04-16T08:02:48.810 回答
1

要在开发时更新类定义,请选择类的代码,然后按F8运行选定的代码。它不像命令-Force上的选项那么干净。Import-Module

See asusing Module没有该选项并且Remove-Module充其量是零星的,这是我发现的最好的方法来开发一个类并查看结果,而无需关闭PowerShell ISE并重新启动它。

于 2017-10-12T17:50:32.437 回答