0

1: Generic.List of tuples
I can create a [Generic.List] of Strings with [System.Collections.Generic.List[String]]::new(), but I can't get a [Generic.List] of Tuples with [System.Collections.Generic.List[System.Tuple]]::new(), I have to use [System.Collections.Generic.List[Object]]::new(). But what is weird is it doesn't fail here, it fails when I add. So this

$queue = [System.Collections.Generic.List[System.Tuple]]::new()
$tuple = [System.Tuple]::Create('One', 'Two', 'Three')
$queue.Add($tuple)
$queue

fails with Cannot find an overload for "Add" and the argument count: "1". at line 3. But a list of objects works fine. I would rather not use [Object] since that allows me to add anything to my list and I would rather be a bit more regorous with my typing. But more importantly I want to understand WHY [System.Collections.Generic.List[System.Tuple]] doesn't work.
EDIT: As seen below, if I type the members of the tuple, and then cast the values in the tuple Create() it works. But that still begs the question, why does an untyped tuple not work? I think this is basically academic, since I would prefer to type the values of the tuple as well as typing the members of the List. Rigor means complete rigor, not sloppy, half baked rigor.

2: Typed tuple
This blog shows the use of New-Object to create a Tuple with typed members using $tuple = New-Object “tuple[String,DateTime,Int]” “Joel Bennett”, “July 1, 2014”, 6. I wonder if there is a .NET constructor approach that I just can't find? This shows var tuple2 = new Tuple<string, double>("New York", 32.68); but I have yet to figure out how to do that in PowerShell.
EDIT: So it seems the issue here is that you can't depend on PS automatic typing inside .Create(), which makes sense. So, cast the values and it works.

$queue = [System.Collections.Generic.List[System.Tuple[String, DateTime, Int]]]::new()
$tuple = [System.Tuple]::Create([String]“Joel Bennett”, [DateTime]“July 1, 2014”, [Int]8)
$queue.Add($tuple)
$queue

3: Named Tuple members
This talks about the ability of VB to control the names of the Tuple members, since 'Item#' is both not very helpful and lame that these start at 1 while the Tuple is 0 indexed, which just annoys me. :) The question is, is this a VB only feature of Tuples? Or does a .NET tuple offer this functionality too, and in a way that is accessible from PS?

Gordon
  • 6,257
  • 6
  • 36
  • 89
  • I am not sure if where you ask for is a good thing to do in PowerShell (knowing that PowerShell is indented to be a loosely scripting language). But if you want to be strict (and lose the ability to add tuples of different sizes/contents), I guess that this is what you actually want to do: `$queue = [System.Collections.Generic.List[System.Tuple[String, String, String]]]::new()` – iRon Jul 26 '22 at 07:34
  • @iRon I tried that, or a variation where I specified `[String, DateTime, Int]` and again, the `$queue =` line doesn't throw an error, nor does the `$tuple =` line, but when I try to add the tuple to `$queue` it does. FWIW, this is in PS 5.1, so perhaps PSCore addresses this? – Gordon Jul 26 '22 at 07:41
  • As for the typing, this is for a large program, rather than a small script, and I find that I prefer to be more strict with large programs. Strict typing, classes to control type of return values and mitigate pipeline pollution problems, full names not aliases, etc. – Gordon Jul 26 '22 at 07:43
  • 1
    Aha! If I do all strings in the constructor, and actually put quotes around the integer, it works. So that got me thinking, since everything in `.Create()` is NOT PowerShell, I may not be able to depend on PS automatic typing. So I tried `$tuple = [System.Tuple]::Create([String]“Joel Bennett”, [DateTime]“July 1, 2014”, [Int]8)` and that DOES work. So woot. One down, two to go. – Gordon Jul 26 '22 at 07:48
  • "*mitigate pipeline pollution problems*". Not sure if you mention the [PowerShell pipeline](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_pipeline) but .Net Classes as a generic list actually withhold you from (correctly) using the PowerShell pipeline causing the pipeline to choke and every item to be piled up in memory, were the PowerShell pipeline passes every item directly onto the next cmdlet. Something in the middle of PowerShell and what you are doing is a [`DataTable`](https://docs.microsoft.com/dotnet/api/system.data.datatable) which easily unrolls. – iRon Jul 26 '22 at 08:20
  • @iRon My goal here is to actually not use the pipeline, the vast majority of the time. If I am stringing cmdlets together, yes. But I HATE the fact that PowerShell functions also use the pipeline for return values, because it causes all sorts of issues where something sneaks into the pipeline and suddenly a function is broken. I like that class methods have a typed return value and that anything that sneaks into the pipeline doesn't break the code. Perhaps I end up with a memory leak of sorts, and I would love to have a way to just SEE what's in the pipeline, to ensure it stays empty. – Gordon Jul 26 '22 at 08:24
  • Agree, see related PowerShell issues: [`#17701` PowerShell should be better able to reveal developer information on an unknown object](https://github.com/PowerShell/PowerShell/issues/17701) and [`#15781` Strict Write-Output](https://github.com/PowerShell/PowerShell/issues/15781) – iRon Jul 26 '22 at 09:41
  • Needless to say, [many of the .Net techniques are not idiomatic PowerShell and may reduce the readability of a PowerShell script. Script authors are advised to use idiomatic PowerShell unless performance dictates otherwise.](https://learn.microsoft.com/powershell/scripting/dev-cross-plat/performance/script-authoring-considerations). If you give the PowerShell syntax a change you will see that you get a different mindset where this "pipeline leakage" doesn't happen that often as suggested. – iRon Jul 26 '22 at 09:41
  • In my case, idiomatic PowerShell is definitely not a concern. I am not producing cmdlets for others to use, nor indeed code for others to use or edit. I just need to worry about future me, which is problematic enough. That said, I have run into performance issues solved by PowerShell as glue code for .NET methods on numerous occasions, and pipeline pollution has accounted for probably half of my debugging time. Combined with a data structure that so far seems MUCH easier to handle in Classes and I think my decision is made. It will be interesting to see how many cmdlets are left in my code. – Gordon Jul 26 '22 at 09:52

1 Answers1

3

Part 1 - Tuple vs Tuple<T1, T2, T3>

You're creating a list that can contain System.Tuples:

$queue = [System.Collections.Generic.List[System.Tuple]]::new()

$queue.GetType().FullName
# System.Collections.Generic.List`1[
#    [System.Tuple, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]
# ]

but then trying to store a generic Tuple<T1, T2, T3> in it:

$tuple = [System.Tuple]::Create('One', 'Two', 'Three')

$tuple.GetType().FullName
# System.Tuple`3[
#     [System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]
# ]

System.Tuple is a static utility class for working with generic tuples, and isn’t interchangeable with specific instances of the generic classes (which themselves aren’t interchangeable with each other unless they have the exact same generic type parameters).

See https://learn.microsoft.com/en-us/dotnet/api/system.tuple?view=net-6.0#definition

Tuple Class

Provides static methods for creating tuple objects.

versus https://learn.microsoft.com/en-us/dotnet/api/system.tuple-3?view=net-6.0

Tuple<T1,T2,T3> Class

Represents a 3-tuple, or triple.

If you create a list of tuples with the desired generic type parameters you’ll be able to store your item in it:

$queue = [System.Collections.Generic.List[
    System.Tuple[string, string, string]
]]::new()

Part 2 - new Tuple<T1, T2, T3>

PowerShell will do some type inference for you on the generic type parameters, but we need to cast a datetime string to a proper [DateTime] if we want the second generic type parameter to be a [DateTime] instead of a string:

$tuple = [System.Tuple]::Create("Joel Bennett", [DateTime] "July 1, 2014", 8)

$tuple.GetType().FullName
# System.Tuple`3[
#     [System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.DateTime, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]
# ]

If you specifically want to use the ::new syntax you can get a reference to the Tuple<T1, T2, T3> type with the desired generic type parameters and call ::new on that - PowerShell will then happily do its usual type coercion to try to pass the appropriate parameters. E.g:

$tuple = [System.Tuple[string, DateTime, int]]::new("Joel Bennett", "July 1, 2014", 8)

$tuple.GetType().FullName
# System.Tuple`3[
#     [System.String, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.DateTime, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],
#     [System.Int32, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]
# ]

Part 3 - named fields

Sorry - not sure there's an answer for this one.

I found a GitHub issue that discusses named properties briefly though, fwiw:

https://github.com/PowerShell/PowerShell/issues/10502#issue-491157520

mclayton
  • 8,025
  • 2
  • 21
  • 26
  • Hmm, so a `System.Tuple[string, string, string]` is not actually also a generic `System.Tuple` with untyped values. Interesting. It makes sense, a typed THING is not the same as an Untyped THING. As I mentioned above, I am going to want to type everything, so the first two issues are covered by the same solution. Hopefully there is an answer to the naming, but even that isn't the end of the world. – Gordon Jul 26 '22 at 08:17
  • @Gordon - for info - answer updated, and added ```Tuple[T1, T2, T3]::new()``` example. – mclayton Jul 26 '22 at 08:59
  • Yeah, if named fields where possible, I would move to tuples over hash tables for complex function return values. But I guess with functions you just have to choose between names and strict types, and if you want both you need to use classes. For my current use case tuples will work since I only need two values. Now to decide between `System.Tuple` and `System.ValueTuple`, which I just learned about. :) – Gordon Jul 26 '22 at 09:08
  • You could alternatively use ```pscustomobject```s with ```$ErrorActionPreference = "Stop"``` - that still uses magic strings for properties, but you'll at least be able to use names instead of ```Item1..N```, and ```$ErrorActionPreference = "Stop"``` will throw an exception if the property doesn't exist (or you renamed it somewhere and didn't update all references). Or ```Add-Type``` with a C# struct... – mclayton Jul 26 '22 at 09:10
  • I had looked at the psCustomObject option, but considering that pipeline pollution is also something I want to address, and various OOP features will simplify my code a LOT, I think moving to Classes just solves so many issues at once. Unnamed tuples will addresses my currently pressing need, and then I can start to really focus on a massive refactor to classes. Wooo! – Gordon Jul 26 '22 at 09:17
  • And, more interesting is this. https://code-maze.com/csharp-valuetuple-vs-tuple/ Which seems to suggest `ValueTuple` CAN be named. Though I need to figure out how to convert `(int lastYear, int currentYear) namedValueTupleOne = (2021, 2022);` to PowerShell. – Gordon Jul 26 '22 at 09:26
  • 1
    Or not, since https://stackoverflow.com/questions/43565738/name-valuetuple-properties-when-creating-with-new makes clear the naming functionality is compiler based, so no bueno in PowerShell. – Gordon Jul 26 '22 at 09:30