Reputation: 21418
I'm trying to invoke the List[T](IEnumerable)
directly adding an item to the initial List
like so, where T
is a PowerShell class I've written (the below example uses the class name Thing
:
$someObject = Get-Thing # returns a single object
$list = [List[Thing]]::new(@( $someObject ))
However, this yields an error suggesting it can't find the overload for this constructor:
Cannot find an overload for "List`1" and the argument count: "1".
Setting List[T]
to the Object
class works, however:
$someObject = Get-Thing
$list = [List[Object]]::new(@( $someObject ))
While this works, I'm unsure why I'm unable to use my PowerShell class as the type. My understanding is that only context-bound types and (by default) nested types are unable to be used with generics, but the following shows that my class is not a ContextBoundObject
:
class Thing {
$Name
Thing($name) {
$this.Name = $name
}
}
$thing = [Thing]::new('Bender')
$thing -is [System.ContextBoundObject] # ==> False
I'm not certain if a PowerShell class would be a nested type of some sort, and about_Classes does not mention nested types.
Upvotes: 1
Views: 1432
Reputation: 437688
To complement Mathias R. Jessen's helpful answer, which explains the problem well and offers an effective solution:
PowerShell's casts are not only syntactically more convenient than constructor calls, but also more flexible when it comes to on-demand type conversions.
Indeed, using a cast instead of calling a constructor, via the static ::new()
method, does work:
using namespace System.Collections.Generic
class Thing { [string] $Name; Thing([string] $name) { $this.Name = $name } }
# Both of the following work:
# Single [Thing] instance.
$list = [List[Thing]] [Thing]::new('one')
# Multiple [Thing] instances, as an array, via the grouping operator, (...)
# @(...), the array subexpression operator, works too, but is unnecessary.
$list = [List[Thing]] ([Thing]::new('one'), [Thing]::new('two'))
Unfortunately, as of this writing the rules aren't documented, but a comment in the source-code provides a high-level overview, as does the (pretty low-level) ETS type converters documentation, which can be summarized as follows, in descending order of precedence:
First, engine-internal, fixed conversion rules may be applied (see source-code link above).
A notable internal rule concerns to-string conversions: while any .NET type supports it by an explicit call to its .ToString()
method (inherited from the root of the object hierarchy, System.Object
), PowerShell applies custom rules:
If a type has a culture-sensitive .ToString(<IFormatProvider>)
overload, PowerShell passes the invariant culture deliberately, to achieve a culture-invariant representation, whereas a direct .ToString()
call would yield a culture-sensitive representation - see this answer for details; e.g., in a culture where ,
is the decimal mark, [string] 1.2
returns '1.2'
(period), whereas (1.2).ToString()
returns '1,2'
(comma).
Collections, including arrays, are stringified by concatenating their (stringified) elements with a space as the separator (by default, can be overridden with preference variable $OFS
); e.g., [string] (1, 2)
returns 1 2
, whereas (1, 2).ToString()
returns merely System.Object[]
.
Also, PowerShell converts freely:
.
as the decimal mark when converting from a string).[int[]] 42
creates a single-element array, and [System.Collections.Generic.List[int]] 42
creates a single-element list; small caveat: there are bugs with certain list types - see this answer).Next, TypeConverter
or (PSTypeConverter
) classes that implement custom conversions for specific types are considered.
If the input type is a string ([string]
), a static ::Parse()
method is considered, if present: first, one with a culture-sensitive signature, ::Parse(<string>, <IFormatProvider>)
, in which case the invariant culture is passed, and, otherwise one with signature ::Parse(<string>)
.
Next, a single-argument constructor is considered, if the input type matches the argument's type or is convertible to it.
If an implicit or explicit conversion operator exists for conversion between the input and the target type.
Finally, if the input object implements the System.IConvertible
interface and the target type is a supported-by-the-implementation primitive .NET type except [IntPtr]
and [UIntPtr]
or one of the following types: [datetime]
, [DBNull]
, [decimal]
.
Upvotes: 3
Reputation: 174485
I'm unsure why I'm unable to use my PowerShell class as the type
The array subexpression operator @()
returns its results as [object[]]
- a type which satisfies the argument type [IEnumerable[object]]
- which is why it always works when you use [object]
as the type parameter for the receiving collection type.
So, what to do about that?
If the array consists only of [Thing]
's, you can explicitly cast to a more specific collection type that implements [IEnumerable[Thing]]
:
$list = [List[Thing]]::new([Thing[]]@( $someObject ))
Upvotes: 3