Reputation: 87
I'm trying to make a hashset of 2-element (pair) tuples in PowerShell thus:
$MySet = New-Object System.Collections.Generic.HashSet[System.Tuple]
Which seems to work:
$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)
But if I create a tuple and add it to the set:
$Tuple = [System.Tuple]::Create("Test", "Hello")
$MySet.Add($Tuple)
MethodException: Cannot find an overload for "Add" and the argument count: "1".
What's the way to go about this correctly?
Bonus: There's no threadsafe version of hashset - is there a way to modify/read hashsets in a threadsafe manner then?
Upvotes: 3
Views: 1606
Reputation: 174485
The problem here is that [System.Tuple]::Create($a,$b)
doesn't actually create an instance of [System.Tuple]
, but of [System.Tuple[T1,T2]]
where T1
is the type of $a
, and T2
is the type of $b
.
Although they share part of the name, [System.Tuple[T1,T2]]
is not assignable to [System.Tuple]
, so we'll have to find another type argument for your [HashSet]
.
Since your tuple item values are strings, go with [System.Tuple[string,string]]
:
$set = [System.Collections.Generic.HashSet[System.Tuple[string,string]]]::new()
$tuple = [System.Tuple]::Create("Hello", "World")
$set.Add($tuple)
If you go in the other direction and just create a HashSet[object]
for the widest possible assignability, you could create a thread-safe ConcurrentSet
by wrapping the HashSet[object]
methods you need to expose in a custom powershell class, and then use a ReaderWriteLockSlim
to facilitate concurrent reads but exclusive writes:
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()
}
}
}
Upvotes: 4
Reputation: 61068
Another (ugly) approach is
$MySet = [System.Collections.Generic.HashSet[[System.Tuple[string,string]]]]::new()
$Tuple = [System.Tuple[string,string]]::new("Test", "Hello")
[void]$MySet.Add($Tuple)
$MySet
Result:
Item1 Item2 Length ----- ----- ------ Test Hello 2
Upvotes: 1