2

I need to create a Dictionary with Tuple key in Powershell. Exactly like I can do in C# like this:

var test = new Dictionary<(int, bool), int>();

// Add
test.Add((1, false), 5);

// Get
int a = test[(1, false)];

(Taken from Hashtable with MultiDimensional Key in C#)

Is it possible? (I am running Powershell version 5.1.18362.145.)

Thanks!

user736570
  • 469
  • 4
  • 15
  • 2
    You can explicitly create a `Tuple` or `ValueTuple` in PowerShell (`$x = @{ [System.ValueTuple]::Create(1, $false) = 5 }; $x[[System.ValueTuple]::Create(1, $false)]`), but since there's no syntactic sugar for it and PowerShell's support for generics is a little spotty, it's not a particularly nice experience. I suppose you could wrap the `Create` in a local function with a short name. Note that PowerShell's own custom objects act "weird" when used as keys since PowerShell doesn't automatically add proper hashing and equality support, so those aren't attractive alternatives either. – Jeroen Mostert Jan 15 '20 at 11:51
  • Thanks @JeroenMostert, I'll try it out! The note about hashing and equality support I _think_ will be no problem for me, because the resulting Dictonary will be sent to an API with C# code. – user736570 Jan 15 '20 at 12:03
  • 2
    Hashing and equality support is always relevant, regardless of language, because `Dictionary` (and `Hashtable`, which PowerShell uses) use it to look up and store keys. `ValueTuple` has support for this, so they are suitable as dictionary keys, but PowerShell's custom objects do not (not by default, anyway). – Jeroen Mostert Jan 15 '20 at 12:24

1 Answers1

4

To add to Jeroen Mostert's excellent comments on the question:

The following is a direct translation of your C# code to PowerShell v5.1+ code:

using namespace System.Collections.Generic

# Construct
$test = [Dictionary[[ValueTuple[int, bool]], int]]::new()

# Add
$test.Add([ValueTuple[int, bool]]::new(1, $false), 5)

# Get
$test[[ValueTuple[int, bool]]::new(1, $false)]
  • using namespace is a PSv5+ feature akin to C#'s using construct: it allows you to refer to the types in the specified namespace by their mere names, without namespace qualification.

  • As Jeroen points out, PowerShell has no syntactic sugar for value-tuple instances, so that C# tuple literal (1, false) must be represented as an explicit constructor call:
    [ValueTuple[int, bool]]::new(1, $false).

    • The alternative is to use the static Create method on the non-generic System.ValueType base type, in which case the tuple component types are inferred:
      [ValueTuple]::Create(1, $false)

Given that PowerShell exposes a type's constructors via the static ::new() method on the type itself, you can simplify the code by instantiating the specific tuple type once and reusing it via a variable (ignore the broken syntax highlighting):

using namespace System.Collections.Generic

# Instantiate the concrete tuple type (type arguments locked in).
$tupleType = [ValueTuple[int, bool]]

# Construct the dictionary with the tuple type as the key.
# See explanation below.
$test = [Dictionary`2].MakeGenericType($tupleType, [int])::new()

#`# Add
$test.Add($tupleType::new(1, $false), 5)

# Get
$test[$tupleType::new(1, $false)]

The downside is that the dictionary construction becomes more awkward, because PowerShell type literals such as [Dictionary[[ValueTuple[int, bool]], int]] must not have non-literal components. To work around that, System.Type.MakeGenericType is used to construct the closed generic type from the dynamically specified type arguments; note the need to specify the arity (`2) of the open generic type on which .MakeGenericType() is invoked.

mklement0
  • 382,024
  • 64
  • 607
  • 775