有没有办法创建支持@{}对象初始化的不可变记录类型?

问题描述 投票:0回答:1

以下代码创建一个 C# 记录类型,使用哈希表对象初始化习惯用法将其实例化,然后修改记录的字段:

Add-Type -TypeDefinition @'
namespace n {
    public record r {
        public string s {get; init;}
    }
}
'@

$r = [n.r]@{s='value'}
$r
$r.s = 'new_value'
$r

字段

s
表面上在初始化后是只读的,但输出如下:

s
-
value
new_value

对应的C#

using System;

namespace n {
    public record r {
        public string s {get; init;}
    }
}
public class Program
{
    public static void Main()
    {
        var r = new n.r {s="value"};
        r.s = "new_value";
    }
}

确实在编译时强制执行错误的不变性

Compilation error (line 13, col 3): Init-only property or indexer 'r.s' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.

我正在寻找一种方法来创建具有以下功能的类型:

  • 初始化后属性是只读的
  • 可以使用简洁的named初始化语法(例如
    [n.r]@{s='value'}
    或类似的)来创建对象
  • 是 C# 编译器自然理解的惯用 .Net 类型
  • 可以编译成程序集,然后提供给
    Add-Type -ReferenceAssemblies

可以创建这样的类型吗?

不起作用的类型

下面的代码尝试使用我能想到的在 C# 中创建这样一个不可变记录类型的各种方法来完成此任务。 (我不是 C# 专家,所以我希望我可能错过了一些。)作为参考,它也尝试使用

New-Object -Property
进行相同的操作。 它输出以下内容:

OK cl init_error                     init_origi init_mod_error                 init_after new_error                      new_origin new_mod_error                  new_after_
   as                                nal                                       _mod                                      al                                        mod
   s
-- -- ----------                     ---------- --------------                 ---------- ---------                      ---------- -------------                  ----------
X  r1                                value                                     new_value                                 value                                     new_value
X  r2                                value                                     new_value                                 value                                     new_value
X  r3                                value                                     new_value                                 value                                     new_value
X  r4                                value                                     new_value                                 value                                     new_value
X  r5 Cannot create object of type …            The property 's' cannot be fo…            The value supplied is not val…            The property 's' cannot be fo…
X  r6 Cannot convert the "System.Co…            The property 's' cannot be fo…            A constructor was not found. …            The property 's' cannot be fo…
X  r7 Cannot convert the "System.Co…            The property 's' cannot be fo…            A constructor was not found. …            The property 's' cannot be fo…

这些方法要么不支持哈希表对象初始化,要么允许在创建后修改对象属性。

Add-Type -TypeDefinition @'
namespace n {
    public       record  r1 { public string s {get; init;} }
    public record class  r2 { public string s {get; init;} }
    public record struct r3 { public string s {get; init;} }
    public class         r4 { public string s {get; init;} }
    public class         r5 { public string s {get;} }
    public class  r6 {
        public readonly string s;
        public r6 (string s) { this.s = s; }
    }
    public class  r7 {
        public string s {get;}
        public r7 (string s) { this.s = s; }
    }
}
'@

$(foreach ($n in 'r1','r2','r3','r4','r5','r6','r7') {
    $r = $null
    [pscustomobject]@{
        class          = $n
        init_error     = $(try { $r = Invoke-Expression "[n.$n]@{s='value'}"}
                           catch { $_ })
        init_original  = $r.s
        init_mod_error = $(try { $r.s = 'new_value'}
                          catch { $_ }              )
        init_after_mod = $r.s
        new_error      = $(try { $r = New-Object "n.$n" -Property @{s='value'}}
                           catch {$_})
        new_original   = $r.s
        new_mod_error  = $(try { $r.s = 'new_value'}
                             catch { $_ }              )
        new_after_mod  = $r.s
    }
}) |
    Select-Object `
        -Property @{name='OK';
                    expression = {if ('value' -in $_.new_after_mod,
                                                  $_.init_after_mod    ) {'✓'}

                                  else                                   {'X'}}},
                  * |
    Format-Table `
        -Property @{e='OK'             ; width = 2},
                  @{e='class'          ; width = 2},
                  @{e='init_error'     ; width = 30},
                  @{e='init_original'  ; width = 10},
                  @{e='init_mod_error' ; width = 30},
                  @{e='init_after_mod' ; width = 10},
                  @{e='new_error'      ; width = 30},
                  @{e='new_original'   ; width = 10},
                  @{e='new_mod_error'  ; width = 30},
                  @{e='new_after_mod'  ; width = 10}
c# .net powershell initialization immutability
1个回答
0
投票
当使用该习惯用法时,

PowerShell 会调用带有参数

System.Collections.Hashtable
的构造函数。 使用只读属性指定这样的构造函数似乎可以实现普通旧 C# 对象的只读属性的简洁命名初始化。

代码

Add-Type -TypeDefinition @'
namespace n {
    public class r {
        public string s {get;}
        public r(System.Collections.Hashtable h) {
            if (h.ContainsKey("s") &&
                (null != (h["s"]))) {
                this.s = h["s"] as string;
            }
        }
    }
}
'@


$r = [n.r]@{s='value'}
$r
$r.s = 'new_value'
$r

输出

s
-
value
InvalidOperation:
Line |
  18 |  $r.s = 'new_value'
     |  ~~~~~~~~~~~~~~~~~~
     | 's' is a ReadOnly property.
value
© www.soinside.com 2019 - 2024. All rights reserved.