如何在Powershell中嵌套自定义.NET类型?

时间:2015-04-13 16:51:04

标签: c# .net powershell types

Powershell允许使用一些C#作为Add-Type Cmdlet的参数创建自定义.NET类型。这是一个例子:

Add-Type @'
    public class MyType1
    {
        public string a { get; set; }
        public string b { get; set; }
    }
'@

$obj1 = New-Object MyType1
$obj1.a = 'my a'
$obj1.b = 'my b'

PS C:\> $obj1 | fl *
a : my a
b : my b

假设我现在想要创建另一个类型,它本身具有类型MyType1的属性。最明显的方法是使用Add-Type创建另一个自定义类型:

Add-Type @'
    public class MyType2
    {
        public string  c       { get; set; }
        public MyType1 subObj  { get; set; }
    }
'@

然而,这会导致以下错误:

The type or namespace name 'MyType1' could not be found (are you 
missing a using directive or an assembly reference?)

如何在Powershell中创建嵌套的自定义.NET类型以便在Powershell中使用?


注意:我知道您可以使用New-Object PSObjectAdd-Member创建嵌套对象。这些Cmdlet使用Powershell's Extended Type System并生成PSObjects类型的对象。我正在使用.NET API,因此我需要创建特定的真正的 .NET对象。

2 个答案:

答案 0 :(得分:1)

正如@ arco444所指出的,当您在Add-Type的单个调用中定义父对象和子对象时,创建自定义嵌套.NET类型会起作用。这是什么样的:

Add-Type @'
    public class MyType3a
        {
            public string a { get; set; }
            public string b { get; set; }
        }

    public class MyType3
    {
        public string  c       { get; set; }
        public MyType3a subObj  { get; set; }
    }
'@

$obj3a = New-Object MyType3a
$obj3a.a = 'my a'
$obj3a.b = 'my b'

$obj3 = New-Object MyType3
$obj3.subObj = $obj3a

PS C:\> $obj3.subObj | fl 

a : my a
b : my b

答案 1 :(得分:1)

根据评论,我设法使用临时DLL程序集来实现这一点。这很难看,而且我确信有人能够更好地理解“引擎盖下”的内容,可以让我直截了当地改善答案。这是:

$TypeDef1= @'
    namespace Mynamespace
    {     
        public class MyType1
        {
            public string a { get; set; }
            public string b { get; set; }
        }
    }
'@ 

$type1 = Add-Type $TypeDef1 -PassThru -OutputAssembly "c:\temp\my.dll"

$obj1 = New-Object Mynamespace.MyType1
$obj1.a = 'my a'
$obj1.b = 'my b'

$TypeDef2 = @'
    namespace Mynamespace
    {     
        public class MyType2
        {
            public string  c       { get; set; }
            public Mynamespace.MyType1 subObj  { get; set; }
        }
    }   
'@

Add-Type -TypeDefinition $TypeDef2 -ReferencedAssemblies "c:\temp\my.dll"

$obj2 = New-Object Mynamespace.MyType2
$obj2.subObj = $obj1

基本上,第一次编译的结果(add-type)保存在DLL中,并且此DLL作为引用的程序集传递给第二个add类型语句。

据我所知,已经有一个由add-type语句创建的临时DLL,可以在$ type1.Module中看到,但我找不到在第二个type-add命令中引用它的方法。


修改

在试图弄清楚如何使这不那么“丑陋”时,我遇到了其他人试图在C#本地完成类似的任务。

In C#, how do you reference types from one in-memory assembly inside another?

C# - Referencing a type in a dynamically generated assembly

第二个链接指出了一种方法,可能只是一点点.NETish。

默认情况下,PowerShell Add-Type 命令执行.NET编译器,并将 GenerateInMemory 选项设置为 $ true

这会编译代码并将生成的类型加载到内存中,而不会保留已编译程序集的实际副本。需要一个程序集的副本来编译引用原始类型的其他类型。

解决这个问题的一种方法是编写我们自己的 New-Type 函数。这是 Add-Type cmdlet的简化版本,它使用 GenerateInMemory = $ false 执行编译器,并返回对已编译程序集的引用。 然后可以使用此引用来编译后续类型。

仍在磁盘上生成临时文件,但编译器至少会对进程和位置进行模糊处理。

以下是代码:

function New-Type {
   param([string]$TypeDefinition,[string[]]$ReferencedAssemblies)


   $CodeProvider = New-Object Microsoft.CSharp.CSharpCodeProvider
   # Location for System.Management.Automation DLL
   $dllName = [PsObject].Assembly.Location
   $Parameters = New-Object System.CodeDom.Compiler.CompilerParameters
   $RefAssemblies = @("System.dll", $dllName)
   $Parameters.ReferencedAssemblies.AddRange($RefAssemblies)
   if($ReferencedAssemblies) { 
      $Parameters.ReferencedAssemblies.AddRange($ReferencedAssemblies) 
   }
   $Parameters.IncludeDebugInformation = $true
   $Parameters.GenerateInMemory = $false # Do not compile in memory (generates a temp DLL file)

   $Results = $CodeProvider.CompileAssemblyFromSource($Parameters, $TypeDefinition) #compile
   if($Results.Errors.Count -gt 0) {
     $Results.Errors | % { Write-Error ("{0}:`t{1}" -f $_.Line,$_.ErrorText) }
   }
   return $Results.CompiledAssembly # return info for the assembly
}


$TypeDef1= @'
    public class MyType1
    {
        public string a { get; set; }
        public string b { get; set; }
    }
'@ 

$Asembly1 = New-Type $TypeDef1

$obj1 = New-Object MyType1
$obj1.a = 'my a'
$obj1.b = 'my b'

$TypeDef2 = @'
    public class MyType2
    {
        public string  c       { get; set; }
        public MyType1 subObj  { get; set; }
    } 
'@

$Asembly2 = New-Type -TypeDefinition $TypeDef2 -ReferencedAssemblies $Asembly1.Location

$obj2 = New-Object MyType2
$obj2.subObj = $obj1