如何在 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()
        }
    }
}