Reputation: 73
In response to my previous question, I was given a working script that is based on a known-in-advance data type literally specified as [int]
.
Now I would like to change the data type dynamically. Is it possible?
Upvotes: 1
Views: 201
Reputation: 439193
The answer to your previous question uses type literals ([...]
), which require that all types be specified verbatim (by their literal names).
[Func[Data.DataRow, $columnType]]
does not work - it causes a syntax error.To generalize the linked answer based on dynamically determined (indirectly specified) types, two modifications are needed:
You must construct (instantiate) the closed generic types involved in the LINQ method call via the .MakeArrayType()
and .MakeGenericType()
methods.
You must use -as
, the conditional type conversion operator to cast the input objects / the transformation script block ({ ... }
) to those types.
# Create a sample data table...
[Data.DataTable] $dt = New-Object System.Data.DataTable
[Data.DataColumn] $column = New-Object System.Data.DataColumn "Id", ([int])
$dt.Columns.Add($column)
# ... and add data.
[Data.DataRow]$row = $dt.NewRow()
$row["Id"] = 1
$dt.Rows.Add($row)
$row = $dt.NewRow()
$row["Id"] = 2
$dt.Rows.Add($row)
# Using reflection, get the open definition of the relevant overload of the
# static [Linq.Enumerable]::Select() method.
# ("Open" means: its generic type parameters aren't yet bound, i.e. aren't
# yet instantiated with concrete types.)
$selectMethod = [Linq.Enumerable].GetMethods().Where({
$_.Name -eq 'Select' -and $_.GetParameters()[-1].ParameterType.Name -eq 'Func`2'
}, 'First')
# Dynamically set the name of the column to use in the projection.
$colName = 'Id'
# Dynamically set the in- and output types to use in the LINQ
# .Select() (projection) operation.
$inType = [Data.DataRow]
$outType = [int]
# Now derive the generic types required for the LINQ .Select() method
# from the types above:
# The array type to serve as the input enumerable.
# Note: As explained in the linked answer, the proper - but more cumbersome -
# solution would be to use reflection to obtain a closed instance of
# the generic .AsEnumerable() method.
$inArrayType = $inType.MakeArrayType()
# The type for the 'selector' argument, i.e. the delegate performing
# the transformation of each input object.
$closedFuncType = [Func`2].MakeGenericType($inType, $outType)
# Close the generic .Select() method with the given types
# and invoke it.
[int[]] $results = $selectMethod.MakeGenericMethod($inType, $outType).Invoke(
# No instance to operate on - the method is static.
$null,
# The arguments for the method, as an array.
# Note the use of the -as operator with the dynamically constructed types.
(
($dt.Rows -as $inArrayType),
({ $args[0].$colName } -as $closedFuncType)
)
)
# Output the result.
$results
Taking a step back:
As shown in the linked answer, PowerShell's member-access enumeration can provide the same functionality, with greatly simplified syntax and without needing to deal with types explicitly:
# Create a sample data table...
[Data.DataTable] $dt = New-Object System.Data.DataTable
[Data.DataColumn] $column = New-Object System.Data.DataColumn "Id", ([int])
$dt.Columns.Add($column)
# ... and add data.
[Data.DataRow]$row = $dt.NewRow()
$row["Id"] = 1
$dt.Rows.Add($row)
$row = $dt.NewRow()
$row["Id"] = 2
$dt.Rows.Add($row)
# Dynamically set the name of the column to use in the projection.
$colName = 'Id'
# Use member-access enumeration to extract the value of the $colName column
# from all rows.
$dt.$colName # Same as: $dt.Rows.$colName
Upvotes: 2
Reputation: 648
You may want to back up and ask yourself why you are trying to use LINQ in PowerShell. A tip is that if it looks like C#, there is likely a better way to do it.
I assume that you are new to PowerShell so I will give you a quick heads up as to why LINQ is actually "easier" in PowerShell (technically it is not LINQ anymore but I think it looks like it is) when combined with the pipeline.
Try Get-Help *-Object
on a PowerShell prompt sometime. Notice the cmdlets that show up? Select-Object
, Where-Object
, Group-Object
, and Sort-Object
do the same things as LINQ and their names match up to what you expect. Plus, there is no strongly-typed requirement strictly speaking.
$data | Where-Object -Property Greeting -Like *howdy* | Select-Object Name,Year,Greeting
Upvotes: 0