如何实现自我验证其键的自定义字典类型

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

假设我想要一个可以自我验证其键的字典类型,因此只允许某些字符串值。

我尝试过这样的事情:

class MyDict : Collections.DictionaryBase, ICloneable {
    [void] OnValidate($key, $value) {
        $validKeyValues = 'a','b','c'
        if ( $key -notin $validKeyValues ) {
            throw "Key $key failed validation, must be one of $validKeyValues."
        }
    }

    [object] Clone() {
        $clonedDict = [MyDict]::new()
        $this.GetEnumerator() | ForEach-Object { $clonedDict.Add($_.Key, $_.Value) }
        return $clonedDict
    }
}

function StackOverflow {
    param(
        [MyDict]$Dictionary
    )
    $Dictionary
}

$dict = [MyDict]::new()
$dict.a = 'x'

StackOverflow $dict

这似乎适用于会员访问设置和获取,例如,

$dict.a = 'x'
有效。但是,在设置新值时,我无法再在字典中建立索引,因此
$dict['key'] = 'value'
不起作用,但通过
$dict['a']
获取确实有效,并且使用上面的示例将返回
x

无法通过索引进行设置似乎不是一个强大的实现,所以我担心我可能会错过更多细节。

问题

  • 上述方法是我在 PowerShell 中最接近创建验证其自身密钥的自定义字典类型的方法吗?
  • 我可以通过 PowerShell 中的索引问题修复设置吗?
  • 这总体上是一种有效的方法吗?还是有其他我错过的严重缺点和/或在 PowerShell 中实现此目的的更好方法?

note我确实尝试继承泛型

Dictionary<TKey,TValue>
类而不是
DictionaryBase
,但是这个泛型类似乎没有使用
OnValidate($key, $value)
,所以我无法实现自我验证。我还省略了
ISerializable
IDeserializationOnCallback
以简化问题的范围。

PowerShell 7.4.5

powershell
1个回答
0
投票

您所看到的 行为可以说是一个 bug,至少存在于 PowerShell (Core) 7 v7.4.x:

如果使用索引表示法适用于获取条目的值(例如

$dict['a']
),那么它应该同样适用于设置(例如
$dict['a'] = 'y'
),特别是考虑到这两个操作都适用于属性表示法。

问题在于,索引表示法支持所需的参数化

.Item
属性是显式接口实现的一部分,
IDictionary.Item
位于您派生的基类中,
System.Collections.DictionaryBase

虽然这对于 getting 来说不是问题(除了在 Windows PowerShell 中,这是旧版的 Windows 附带的、仅限 Windows 的 PowerShell 版本,其最新且最后一个版本是 5.1),但它在 上失败设置,即使使用替代属性表示法时获取和设置都成功。

考虑到 PowerShell 通常会显示显式接口实现,就好像它们是类型本机成员一样,这同样适用于此处。

请参阅

GitHub 问题 #24537 进行讨论。


解决方法

  • 显式调用索引器底层的

    .Item

     成员,而不是使用索引表示法:

    • 注意:这可能是不可接受的,并且比使用属性表示法更加晦涩和麻烦,但我为了完整性而提及它。

    • 例如,

      $dict.Item('a') = 'y'

      有效。

  • 通过

    Add-Type

    使用嵌入式C#代码,这允许您表面type-native索引器(参数化Item
    属性)
    ,其内部遵循显式接口实现,如下所示。

Add-Type @' using System.Collections; public class MyDict : DictionaryBase { protected override void OnValidate(object key, object value) { var validKeyValues = new string[] { "a", "b", "c" }; if (!((IList)validKeyValues).Contains(key)) { throw new System.ArgumentException(string.Format("Key {0} failed validation, must be one of {1}.", key, string.Join(' ', validKeyValues))); } } // Expose a type-native indexer that defers to the explicit interface implementation. public object this[object key] { get => ((IDictionary)this)[key]; set => ((IDictionary)this)[key] = value; } } '@ $d = [MyDict]::new() # This works now, due to a type-native indexer being present. $d['a'] = 'works'
    
© www.soinside.com 2019 - 2024. All rights reserved.