如何在 powershell 中正确创建和修改元组哈希集?
How can you correctly create and modify a hashset of tuples in powershell?
我正在尝试在 PowerShell 中创建 2 元素(对)元组的哈希集:
$MySet = New-Object System.Collections.Generic.HashSet[System.Tuple]
哪个似乎有效:
$MySet.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True HashSet`1 System.Object
$MySet.Add
OverloadDefinitions
-------------------
bool Add(System.Tuple item)
void ICollection[Tuple].Add(System.Tuple item)
bool ISet[Tuple].Add(System.Tuple item)
但是如果我创建一个元组并将其添加到集合中:
$Tuple = [System.Tuple]::Create("Test", "Hello")
$MySet.Add($Tuple)
MethodException: Cannot find an overload for "Add" and the argument count: "1".
正确处理这个问题的方法是什么?
奖励:没有 threadsafe version of hashset - 那么有没有办法以线程安全的方式 modify/read 哈希集?
另一种(丑)的方法是
$MySet = [System.Collections.Generic.HashSet[[System.Tuple[string,string]]]]::new()
$Tuple = [System.Tuple[string,string]]::new("Test", "Hello")
[void]$MySet.Add($Tuple)
$MySet
结果:
Item1 Item2 Length
----- ----- ------
Test Hello 2
这里的问题是 [System.Tuple]::Create($a,$b)
不会 实际上创建 [System.Tuple]
的实例,而是 [System.Tuple[T1,T2]]
的实例,其中 T1
是$a
的类型,T2
是$b
的类型。
尽管它们共享部分名称,但 [System.Tuple[T1,T2]]
不可分配 给 [System.Tuple]
,因此我们必须为您的 [System.Tuple]
找到另一个类型参数[HashSet]
。
由于您的元组项值是字符串,因此请使用 [System.Tuple[string,string]]
:
$set = [System.Collections.Generic.HashSet[System.Tuple[string,string]]]::new()
$tuple = [System.Tuple]::Create("Hello", "World")
$set.Add($tuple)
奖金答案
如果你朝另一个方向走,只是为尽可能广泛的可分配性创建一个 HashSet[object]
,你可以通过包装你需要的 HashSet[object]
方法来创建一个线程安全的 ConcurrentSet
在自定义 powershell class 中公开,然后使用 ReaderWriteLockSlim
促进并发读取但独占写入:
using namespace System.Collections.Concurrent
using namespace System.Collections.Generic
using namespace System.Threading
# Custom IEqualityComparer based on [scriptblock]
# Use [PSComparer]::new({$args[0].Equals($args[1])}) to "restore" default comparer logic
class PSComparer : IEqualityComparer[object]
{
[scriptblock]
$Comparer
PSComparer()
{
$this.Comparer = {$args[0] -eq $args[1]}
}
PSComparer([scriptblock]$comparer)
{
$this.Comparer = $comparer
}
[bool]
Equals($a,$b)
{
return & $this.Comparer $a $b
}
[int]
GetHashCode($obj)
{
if($obj -is [object]){
return $obj.GetHashCode()
}
throw [System.ArgumentNullException]::new('obj')
}
}
class ConcurrentSet : IDisposable
{
hidden [ReaderWriterLockSlim]
$_lock
hidden [HashSet[object]]
$_set
ConcurrentSet()
{
# Default to PowerShell comparison logic, ie. `"1" -eq 1`
$this.Initialize([PSComparer]::new())
}
ConcurrentSet([IEqualityComparer[object]]$comparer)
{
$this.Initialize($comparer)
}
hidden
Initialize([IEqualityComparer[object]]$comparer)
{
$this._set = [HashSet[object]]::new($comparer)
$this._lock = [System.Threading.ReaderWriterLockSlim]::new()
}
[bool]
Add([object]$item)
{
$this._lock.EnterWriteLock()
try{
return $this._set.Add($item)
}
finally{
$this._lock.ExitWriteLock()
}
}
[bool]
Contains([object]$item)
{
$this._lock.EnterReadLock()
try{
return $this._set.Contains($item)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
Remove([object]$item)
{
$this._lock.EnterUpgradeableReadLock()
try{
if($this._set.Contains($item)){
$this._lock.EnterWriteLock()
try {
return $this._set.Remove($item)
}
finally {
$this._lock.ExitWriteLock()
}
}
return $false
}
finally{
$this._lock.ExitUpgradeableReadLock()
}
}
UnionWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.UnionWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
IntersectWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.IntersectWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
ExceptWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.ExceptWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
SymmetricExceptWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.SymmetricExceptWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
[bool]
IsSubsetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsSubsetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsSupersetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsSupersetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsProperSubsetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsProperSubsetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsProperSupersetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsProperSupersetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
Overlaps([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.Overlaps($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
SetEquals([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.SetEquals($other)
}
finally{
$this._lock.ExitReadLock()
}
}
hidden [int]
get_Count()
{
return $this._set.Count
}
Dispose()
{
if($this._lock -is [System.IDisposable])
{
$this._lock.Dispose()
}
}
}
我正在尝试在 PowerShell 中创建 2 元素(对)元组的哈希集:
$MySet = New-Object System.Collections.Generic.HashSet[System.Tuple]
哪个似乎有效:
$MySet.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True HashSet`1 System.Object
$MySet.Add
OverloadDefinitions
-------------------
bool Add(System.Tuple item)
void ICollection[Tuple].Add(System.Tuple item)
bool ISet[Tuple].Add(System.Tuple item)
但是如果我创建一个元组并将其添加到集合中:
$Tuple = [System.Tuple]::Create("Test", "Hello")
$MySet.Add($Tuple)
MethodException: Cannot find an overload for "Add" and the argument count: "1".
正确处理这个问题的方法是什么?
奖励:没有 threadsafe version of hashset - 那么有没有办法以线程安全的方式 modify/read 哈希集?
另一种(丑)的方法是
$MySet = [System.Collections.Generic.HashSet[[System.Tuple[string,string]]]]::new()
$Tuple = [System.Tuple[string,string]]::new("Test", "Hello")
[void]$MySet.Add($Tuple)
$MySet
结果:
Item1 Item2 Length ----- ----- ------ Test Hello 2
这里的问题是 [System.Tuple]::Create($a,$b)
不会 实际上创建 [System.Tuple]
的实例,而是 [System.Tuple[T1,T2]]
的实例,其中 T1
是$a
的类型,T2
是$b
的类型。
尽管它们共享部分名称,但 [System.Tuple[T1,T2]]
不可分配 给 [System.Tuple]
,因此我们必须为您的 [System.Tuple]
找到另一个类型参数[HashSet]
。
由于您的元组项值是字符串,因此请使用 [System.Tuple[string,string]]
:
$set = [System.Collections.Generic.HashSet[System.Tuple[string,string]]]::new()
$tuple = [System.Tuple]::Create("Hello", "World")
$set.Add($tuple)
奖金答案
如果你朝另一个方向走,只是为尽可能广泛的可分配性创建一个 HashSet[object]
,你可以通过包装你需要的 HashSet[object]
方法来创建一个线程安全的 ConcurrentSet
在自定义 powershell class 中公开,然后使用 ReaderWriteLockSlim
促进并发读取但独占写入:
using namespace System.Collections.Concurrent
using namespace System.Collections.Generic
using namespace System.Threading
# Custom IEqualityComparer based on [scriptblock]
# Use [PSComparer]::new({$args[0].Equals($args[1])}) to "restore" default comparer logic
class PSComparer : IEqualityComparer[object]
{
[scriptblock]
$Comparer
PSComparer()
{
$this.Comparer = {$args[0] -eq $args[1]}
}
PSComparer([scriptblock]$comparer)
{
$this.Comparer = $comparer
}
[bool]
Equals($a,$b)
{
return & $this.Comparer $a $b
}
[int]
GetHashCode($obj)
{
if($obj -is [object]){
return $obj.GetHashCode()
}
throw [System.ArgumentNullException]::new('obj')
}
}
class ConcurrentSet : IDisposable
{
hidden [ReaderWriterLockSlim]
$_lock
hidden [HashSet[object]]
$_set
ConcurrentSet()
{
# Default to PowerShell comparison logic, ie. `"1" -eq 1`
$this.Initialize([PSComparer]::new())
}
ConcurrentSet([IEqualityComparer[object]]$comparer)
{
$this.Initialize($comparer)
}
hidden
Initialize([IEqualityComparer[object]]$comparer)
{
$this._set = [HashSet[object]]::new($comparer)
$this._lock = [System.Threading.ReaderWriterLockSlim]::new()
}
[bool]
Add([object]$item)
{
$this._lock.EnterWriteLock()
try{
return $this._set.Add($item)
}
finally{
$this._lock.ExitWriteLock()
}
}
[bool]
Contains([object]$item)
{
$this._lock.EnterReadLock()
try{
return $this._set.Contains($item)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
Remove([object]$item)
{
$this._lock.EnterUpgradeableReadLock()
try{
if($this._set.Contains($item)){
$this._lock.EnterWriteLock()
try {
return $this._set.Remove($item)
}
finally {
$this._lock.ExitWriteLock()
}
}
return $false
}
finally{
$this._lock.ExitUpgradeableReadLock()
}
}
UnionWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.UnionWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
IntersectWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.IntersectWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
ExceptWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.ExceptWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
SymmetricExceptWith([IEnumerable[object]]$other)
{
$this._lock.EnterWriteLock()
try{
$this._set.SymmetricExceptWith($other)
}
finally{
$this._lock.ExitWriteLock()
}
}
[bool]
IsSubsetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsSubsetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsSupersetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsSupersetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsProperSubsetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsProperSubsetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
IsProperSupersetOf([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.IsProperSupersetOf($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
Overlaps([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.Overlaps($other)
}
finally{
$this._lock.ExitReadLock()
}
}
[bool]
SetEquals([IEnumerable[object]]$other)
{
$this._lock.EnterReadLock()
try{
return $this._set.SetEquals($other)
}
finally{
$this._lock.ExitReadLock()
}
}
hidden [int]
get_Count()
{
return $this._set.Count
}
Dispose()
{
if($this._lock -is [System.IDisposable])
{
$this._lock.Dispose()
}
}
}