假设我想要一个可以自我验证其键的字典类型,因此只允许某些字符串值。
我尝试过这样的事情:
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
。
无法通过索引进行设置似乎不是一个强大的实现,所以我担心我可能会错过更多细节。
note我确实尝试继承泛型
Dictionary<TKey,TValue>
类而不是DictionaryBase
,但是这个泛型类似乎没有使用OnValidate($key, $value)
,所以我无法实现自我验证。我还省略了 ISerializable
和 IDeserializationOnCallback
以简化问题的范围。
PowerShell 7.4.5
您所看到的 行为可以说是一个 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'
有效。
Item
属性),其内部遵循显式接口实现,如下所示。
ParameterizedProperty
条目:
System.Object Item(System.Object key) {get;set;}
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'