Powershell subclass constructor - Cannot find an overload for "new" - powershell

Powershell: 5.1.1904.1
OS: Windows 10 Pro x64, version 2004
I'm probably missing something incredibly obvious here, but...
I have a base class and a subclass.
class Vehicle {
[string]$Name
Vehicle($Name) {
$this.Name = $Name
}
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
}
}
$testVar = [Car]::new("Ford Fiesta", 4)
When calling the subclass constructor I'm getting the message
Cannot find an overload for "new" and the argument count: "0".
When I remove the class inheritance I can call the constructor. Powershell seems to find it.
When I remove the inheritance and get info on the constructor
[Car]::new
I see
OverloadDefinitions
-------------------
Car new(System.Object Name, System.Object NumWheels)
When inheritance is specified (Car : Vehicle) I still get
OverloadDefinitions
-------------------
Car new(System.Object Name, System.Object NumWheels)
Totally unclear why this it seems to see the constructor, but I'm unable to call it when I inherit.
I know I have to be doing something wrong here. :P
Thank you in advance

To complement Doug Maurer's helpful answer:
The ultimate cause of the problem is that your Vehicle class has no default - always argument-less - constructor, which, as Doug points out, is the result of the class having at least one explicitly defined constructor (Vehicle($Name) { ... }). Once you have at least one explicitly defined constructor, you'll have to define an argument-less one explicitly (Vehicle() { ... }) - if needed.
The immediate cause is that the constructor in your derived class, Car, implicitly tries to call an argument-less constructor on the base class, Vehicle, because you haven't specified an explicit base-class constructor call via base(...) as part of the derived-class constructor.
Therefore, the right solution for you is to call the base-class constructor explicitly, namely the one with the $Name parameter:
# Note the call to the base-class constructor via `base`
Car($Name, $NumWheels) : base($Name) {
$this.NumWheels = $NumWheels
}
This obviates the need to add an argument-less constructor to the base class just for the sake of inheritance.
See also: about_Classes.

The error "no overload with argument count 0" is because there is no default constructor. When you create your own constructor the default constructor is no longer provided for you. The other issue is you are not fulfilling the contract by assigning the name.
Either create a default constructor for [Vehicle]
class Vehicle {
[string]$Name
Vehicle(){}
Vehicle($Name) {
$this.Name = $Name
}
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
$this.Name = $Name
}
}
[Car]::new("Ford Fiesta",4)
NumWheels Name
--------- ----
4 Ford Fiesta
or just remove the custom constructor from it
class Vehicle {
[string]$Name
}
class Car : Vehicle {
[int]$NumWheels
Car($Name, $NumWheels) {
$this.NumWheels = $NumWheels
$this.Name = $Name
}
}
[Car]::new("Ford Fiesta",4)
NumWheels Name
--------- ----
4 Ford Fiesta

Related

PowerShell classes with single-argument constructors do not validate data type

I'm designing a module and using classes to type-validate my parameters. I noticed that, when attempting to type-validate input parameters, a class with a single-argument constructor appears to act as a type accelerator instead of validating data type.
Example:
Class stack {
$a
$b
stack($inp) {
$this.a = $inp
$this.b = 'anything'
}
}
function foo {
Param(
[stack]$bar
)
$bar
}
PS>foo -bar 'hello'
a b
- -
hello anything
$bar has been type accelerated into an instantiation of stack.
Compare this to the same class with a constructor that takes 2 arguments:
Class stack {
$a
$b
stack($inp,$inp2) {
$this.a = $inp
$this.b = 'anything'
}
}
function foo {
Param(
[stack]$bar
)
$bar
}
PS>foo -bar 'hello'
foo : Cannot process argument transformation on parameter 'bar'. Cannot convert the "hello" value of type "System.String" to type "stack".
Now the class type is correctly validating the input parameter.
I first saw this in PS5.1 on Windows 10, but I just tried it on my private laptop with pwsh 7.2.1 and seems to be the same.
Is there a workaround to this behavior? Is it a bug?
Edit: Well, after further testing, I realized this also happens if I supply 2 input parameters for the constructor with 2 arguments, e.g., foo -bar 'hello' 'world'. So I guess it's probably intended, and I'm doing something wrong. Can I use classes to validate my data types for input parameters? How?
What you're seeing is unrelated to type accelerators, which are simply short alias names for .NET type names; e.g., [regex] is short for [System.Text.RegularExpressions.Regex].
Instead, you're seeing PowerShell's flexible automatic type conversions, which include translating casts (e.g. [stack] ...) and type constraints (ditto, in the context of an assignment or inside a param(...) block) into constructor calls or ::Parse() calls, as explained in this answer.
Therefore, given that your [stack] class has a (non-type-constrained) single-argument constructor, something like [stack] 'hello' is automatically translated into [stack]::new('hello'), i.e. a constructor call - and that is also what happens when you pass argument 'hello' to a parameter whose type is [stack].
I suggest not fighting these automatic conversions, as they are usually helpful.
In the rare event that you do need to ensure that the type of the argument passed is exactly of the type specified in the parameter declaration (or of a derived type), you can use the following technique (using type [datetime] as an example, whose full .NET type name is System.DateTime):
function Foo {
param(
# Ensure that whatever argument is passed is already of type [datetime]
[PSTypeName('System.DateTime')]
$Bar
)
"[$Bar]"
}
Kudos to you for discovering the [PSTypeName()] attribute for this use case.
Without the [PSTypeName(...)] attribute, a call such as Foo 1/1/1970 would work, because the string '1/1/1970' is automatically converted to [datetime] by PowerShell.
With the [PSTypeName(...)] attribute, only an actual [datetime] argument is accepted (or, for types that can be sub-classed, an instance of a type derived from the specified type).
Important: Specify the target type's full .NET type name (e.g. 'System.DateTime' rather than just 'datetime') to target it unambiguously.
However, for PowerShell custom classes, their name is the full name (they are not inside a namespace), so in the case of your [stack] class, the attribute would be [PSTypeName('stack')]
Any type name is accepted, even if it doesn't refer to an existing .NET type or custom class, and any such non-existent type would require an argument to use a matching virtual ETS (PowerShell's Extended Type System) type name. In fact, supporting such virtual type names is the primary purpose of this attribute.[1] E.g., if [PSTypeName('Bar')] were used, you could pass a custom object with an ETS type name of Bar as follows:
[pscustomobject] #{ PSTypeName = 'Bar'; Baz = 'quux' }
[1] To quote from the linked docs (emphasis added): "This attribute is used to restrict the type name of the parameter, when the type goes beyond the .NET type system."
If you really want to validate the passed type you need to actually validate, not just cast the input as a specific type.
function foo {
Param(
[ValidateScript({$_ -is [stack]})]
$bar
)
$bar
}
Doing this will not try to cast the input as a specific type, and will fail if the input type is wrong.
Your 'foo' function requires a [stack] argument, it doesn't create one.
So your call should be as follow:
foo -bar ([stack]::new('fun:foo','hello'))
I don't know exactly how you will use it but if the goal is to validate arguments, I would suggest to specify the types everywhere... here is a small example but it can be improved, just an example:
Class stack {
[string]$a
[string]$b
stack([string]$inp,[string]$inp2) {
$this.a = $inp
$this.b = $inp2
}
}
function foo {
Param([stack]$bar)
$bar
}
function foo2 {
Param([array]$bar)
[stack]::new($bar[0],$bar[1])
}
foo -bar ([stack]::new('fun:foo','hello'))
foo2 -bar 'fun:foo2','hello'
foo2 -bar #('fun:foo2','hello2')
Aha, I thought I had seen something to this effect somewhere. I luckily managed to get it working by applying the description from an article on using the PSTypeName in PSCustomObjects for type-validation to classes. It turns out that classes also work with the same syntax.
In summary, it seems one has to type [PSTypeName('stack')] to use class types to validate data types.
Class stack {
$a
$b
stack($inp) {
$this.a = $inp
$this.b = 'anything'
}
}
function foo {
Param(
[PSTypeName('stack')]$bar
)
$bar
}
PS>foo -bar 'hello'
foo : Cannot bind argument to parameter 'bar', because PSTypeNames of the argument do not match the PSTypeName required by the parameter: stack.
PS>$test = [stack]::new('Overflow')
PS>foo -bar $test
a b
- -
Overflow anything

Powershell: Inherited classes calling Parent's empty constructors, even when passed objects

In powershell 5 I'm running into a strange inheritance problem with classes.
I want to enforce that we are passed an object during setup like [class]::new($mailbox_object), and I was intending to do this by causing [class]::new() to throw an error if it's associated object isn't assigned (say by a child constructor).
But powershell is calling empty parent constructors BEFORE calling the child constructor that was passed the object and I can't figure out if this is a bug or expected, and more importantly how to enforce that we have to be given an object at creation time
Design pattern speak: I'm trying to implement what I call a Unified Interface pattern, which is a Facade pattern to simplify/unify interactions with similar but differently typed objects, where actions for those objects are selected using a Strategy pattern and the the strategy is chosen automatically by the Facade when created (currently by trying to use an invisible Factory hidden within the Facade)
IRL Example: trying to create a unified interface for Exchange Mailbox/Group objects, and implement a MemberOf function (to return which groups it's a member of). But Mailboxes and Groups use different commands (despite matching functionality) AND 365 and On Premises versions also use different commands (get-unifiedgroup instead of get-distributiongroup) so I'm trying to hide that complexity behind a unified Facade for clarity and usability
I'm open to changing my approach, particularly if there's a better way to do this. Just keep in mind there will be at minimum the following types of disparate objects each of which will need their own implementation of .MemberOf(): Interface_Mailbox_365, Interface_Mailbox_OnPremises, Interface_Group_365, Interface_Group_OnPremises, and I may implement Offline and Generic versions eventually.
MRE below, lines with > are the output. Since I've narrowed it to the an issue with the Interface creation, I've not included the Facade or Factory, but I can add them if they end up being needed.
class Interface_MailObject
{
$MailObject = "Interface_MailObject class - initial"
Interface_MailObject(){write-warning "Interface_MailObject::new() MailObject: {$($this.MailObject)}"}
static [Interface_MailObject] Build($object)
{
if
($object -eq "Mailbox Standin")
{return [Interface_Mailbox_365]::new($object)}
else
{throw("we don't reach here")}
}
}
Class Interface_Mailbox : Interface_MailObject
{
$MailObject = "Interface_Mailbox class - initial"
Interface_Mailbox () {write-warning "Interface_Mailbox::new() MailObject: {$($this.MailObject)}"}
Interface_Mailbox ($MailObject) {$this.MailObject = "Interface_Mailbox class - {$($MailObject)}"}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
$MailObject = "Interface_Mailbox_365 class - initial"
Interface_Mailbox_365 () {write-warning "Interface_Mailbox_365::new() MailObject: {$($this.MailObject)}"}
Interface_Mailbox_365 ($MailObject) {$this.MailObject = "Interface_Mailbox_365 class - {$($MailObject)}"}
[object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}
[Interface_MailObject]::new("Mailbox Standin")|tee -va a
> WARNING: Interface_MailObject::new() MailObject: {Interface_Mailbox_365 class - initial}
> WARNING: Interface_Mailbox::new() MailObject: {Interface_Mailbox_365 class - initial}
>
> MailObject
> ----------
> Interface_Mailbox_365 class - {Mailbox Standin}
Notice that even though we called [Interface_Mailbox_365]::new("Mailbox Standin") powershell executed the grandparent's empty constructor, then the parent's empty constructor, before running the one we called.
If they executed in the other order, it would be fine. If they called parent constructors that match the same parameter qty and type, that would also be fine
But it's doing neither, and I don't know how to resolve it without using some weird acrobatics with a Singleton factory which seems like an excessive amount of micromanagement for something that should be a common need (requiring an input parameter during initialization) so I'm guessing I'm overlooking something
TL:DR
Use child(object):base(object){} to declare the constructor instead of child(object){}
Thanks to #Mathias R. Jessen for helping me work it out.
At first I thought I had to decouple the Facade/Factory from the Template, rather than being able to have them be the same class. Unfortunately this would mean I'm calling [MailObject_Interface]::Build($object) but not returning a [MailObject_Interface] type.
After doing some research I realized what Mathias was saying is a child constructor child(object){} is inferred to mean child(object):base(){} and you can override this by explicitly stating child(object):base(object){}
Paring that with an additional piece to verify the parent isn't called directly I was able to achieve success
Class MailObject_Interface
{
[string] $MailObject
MailObject_Interface ()
{throw("You must call ::Build(`$object), because we return specialized types based on the mail object")}
MailObject_Interface ($object) {[MailObject_Interface]::new()} # this triggers the error above
MailObject_Interface ($object, $codephrase)
{
Write-Warning "calling MailObject_Interface::New($($object), $($codephrase)) {$($this.MailObject)}"
# the Codephrase ensures
# either we're being called from one of our children,
# or whomever calls us is aware of our internal workings and is taking responsibility for making sure we're handled correctly
if
($codephrase -eq "Shazam!")
{$this.MailObject = $object}
else
{[MailObject_Interface]::new()} # this triggers the error above
}
# We run through ::Build instead of ::New because we want to return a child typed object rather than ourselves
static [MailObject_Interface] Build($object)
{
if
($object -eq "Mailbox Standin")
{return [Interface_Mailbox_365]::new($object)}
else
{throw("we don't reach here")}
}
}
Class Interface_MailObject_Template : MailObject_Interface
{
Interface_MailObject_Template ($object) : base ($object, "Shazam!") {Write-Warning "calling Interface_MailObject_Template::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw(".MemberOf will be type+context specific")}
}
Class Interface_Mailbox : Interface_MailObject_Template
{
Interface_Mailbox ($object) : base ($object) {Write-Warning "calling Interface_Mailbox::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw("Mailbox.MemberOf will be context specific")}
}
Class Interface_Mailbox_365 : Interface_Mailbox
{
Interface_Mailbox_365 ($object) : base ($object) {Write-Warning "calling Interface_Mailbox_365::New($($object)) {$($this.MailObject)}"}
[object[]] MemberOf(){throw("Interface_Mailbox_365.MemberOf TBD")}
}
#\/ Rough Tests \/#
# should succeed
function Test_Correct()
{
Try
{
[MailObject_Interface]$a = [MailObject_Interface]::Build("Mailbox Standin")
return "Succeded ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_New_WithObject_MissingCodephrase()
{
Try
{
$a = [MailObject_Interface]::New("Mailbox Standin")
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_EmptyBuild()
{
Try
{
$a = [MailObject_Interface]::Build()
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
# should fail
function Test_EmptyNew()
{
Try
{
$a = [MailObject_Interface]::New()
return "Succeded: ($a)"
}
Catch
{return "Failed"}
}
"$(Test_Correct):`tTest_Correct (should have succeeded)"
"$(Test_New_WithObject_MissingCodephrase):`tTest_New_WithObject_MissingCodephrase (should have failed)"
"$(Test_EmptyBuild):`tTest_EmptyBuild (should have failed)"
"$(Test_EmptyNew):`tTest_EmptyNew (should have failed)"
And here are the test results
> WARNING: calling MailObject_Interface::New(Mailbox Standin, Shazam!) {}
> WARNING: calling Interface_MailObject_Template::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox::New(Mailbox Standin) {Mailbox Standin}
> WARNING: calling Interface_Mailbox_365::New(Mailbox Standin) {Mailbox Standin}
> Succeded (Interface_Mailbox_365): Test_Correct (should have succeeded)
> Failed: Test_New_WithObject_MissingCodephrase (should have failed)
> Failed: Test_EmptyBuild (should have failed)
> Failed: Test_EmptyNew (should have failed)
Also sorry about the unusual format in the Testing functions. I forgot to convert the bracket indenting to the normal standard, I use a nonstandard approach that I find more functional because it frames control logic with whitespace making it more naturally follow the perimeter walk your eyes do when skimming

How to use delegates inside a static PowerShell class?

I want to use a func delegate in my static PowerShell 5.0 class:
I had issues to find a way to assign other static class methods for my delegate.
This code is working but not very convinient.
Is there a better way to use a delegate here ?
And I need to instanciate my static! class, only to get the type.
I tried the outcommented line, how you would do it with .NET types, but it's not working for my own class.
How can I get the type of my static class here more elegant ?
And, BTW, GetMethod() did not accecpt the BindingFlags parameter, why ?
class Demo
{
hidden static [object] Method_1([string] $myString)
{
Write-Host "Method_1: $myString"
return "something"
}
hidden static [object] Method_2([string] $myString)
{
Write-Host "Method_2: $myString"
return $null
}
hidden static [object] TheWrapper([string]$wrappedMethod, [string] $parameter)
{
# do a lot of other stuff here...
#return [System.Type]::GetType("Demo").GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
return [Demo]::new().GetType().GetMethod($wrappedMethod).CreateDelegate([Func``2[string, object]]).Invoke($parameter)
}
static DoWork()
{
Write-Host ([Demo]::TheWrapper('Method_1', 'MyMessage'))
[Demo]::TheWrapper('Method_2', 'c:\my_file.txt')
}
}
[Demo]::DoWork()
You don't need to create an instance of [demo] since [demo] is the actual type of the class. Also, you can write the delegate type more simply as [Func[string,object]]. This simplifies the body of TheWrapper method to
return [Demo].GetMethod($wrappedMethod).CreateDelegate([Func[string, object]]).Invoke($parameter)
but a much simpler way to do this in PowerShell is to get the method by passing its name to the '.' operator then invoking the result:
return [demo]::$wrappedMethod.Invoke($parameter)
In PowerShell, the right-hand side of the '.' operator doesn't need to be a constant. You can use an expression that results in the name of the method (or property) to retrieve.

Constructor chaining in PowerShell - call other constructors in the same class

I was doing some testing and stumbled upon the following:
You can overload methods in PoShv5 as you wish. If you call the method without parameters, it can internally call the method with parameters, to keep your code non-redundant. I expected this to be also true for constructors.
In this example, the last constructor is working as expected. The other constructors only return objects without set values.
Class car {
[string]$make
[string]$model
[int]$Speed
[int]$Year
speedUp (){
$this.speedUp(5)
}
speedUp ([int]$velocity){
$this.speed += $velocity
}
# Constructor
car () {
[car]::new('mall', $Null, $null)
}
car ([string]$make, [string]$model) {
[car]::new($make, $model, 2017)
}
car ([string]$make, [string]$model, [int]$Year) {
$this.make = $make
$this.model = $model
$this.Year = $year
}
}
[car]::new() # returns "empty" car
[car]::new('Make', 'Nice model') # returns also an "empty" one
[car]::new( 'make', 'nice model', 2017) # returns a "filled" instance
Is there a way to fix this? Did I miss something?
To complement Mathias R. Jessen's helpful answer:
The recommended approach is to use hidden helper methods to compensate for the lack of constructor chaining:
Class car {
[string]$Make
[string]$Model
[int]$Year
speedUp (){
$this.speedUp(5)
}
speedUp ([int]$velocity){
$this.speed += $velocity
}
# Hidden, chained helper methods that the constructors must call.
hidden Init([string]$make) { $this.Init($make, $null) }
hidden Init([string]$make, [string]$model) { $this.Init($make, $model, 2017) }
hidden Init([string]$make, [string]$model, [int] $year) {
$this.make = $make
$this.model = $model
$this.Year = $year
}
# Constructors
car () {
$this.Init('Generic')
}
car ([string]$make) {
$this.Init($make)
}
car ([string]$make, [string]$model) {
$this.Init($make, $model)
}
car ([string]$make, [string]$model, [int]$year) {
$this.Init($make, $model, $year)
}
}
[car]::new() # use defaults for all fields
[car]::new('Fiat') # use defaults for model and year
[car]::new( 'Nissan', 'Altima', 2015) # specify values for all fields
This yields:
Make Model Year
---- ----- ----
Generic 2017
Fiat 2017
Nissan Altima 2015
Note:
The hidden keyword is more of a convention that PowerShell itself observes (such as omitting such members when outputting); members tagged this way are technically still accessible, however.
While you can't call a constructor of the same class directly, it is possible to do so with a base-class constructor, using C#-like syntax.
TL;DR: No!
What you're looking for (overloaded constructors calling each other in succession) is also colloquially known as constructor chaining, and looks roughly like this in C#:
class Car
{
string Make;
string Model;
int Year;
Car() : this("mall", null)
{
}
Car(string make, string model) : this(make, model, 2017)
{
}
Car(string make, string model, int Year)
{
this.Make = make;
this.Model = model;
this.Year = year;
}
}
Unfortunately, PowerShell doesn't seem to have any syntax for this - you can't do:
Car() : $this("Porsche") {}
Car([string]$Make) {}
without having the parser throw up at you for missing the body definition of your constructor, and I don't expect to see it anytime soon - the PowerShell team has expressed an explicit desire not to become the maintainers of a new watered down C# - which I can perfectly well understand :-)
You'll just have to re-implement the member assignments in each constructor definition.

Set a property for a PowerShell class on Instantiation

Is it possible to have the value of a property of a PowerShell class defined on instantiation without using a constructor?
Let's say there's a cmdlet that will return Jon Snow's current status (alive or dead). I want that cmdlet to assign that status to a property in my class.
I can do this using a constructor, but I'd like this to happen regardless of which constructor is used, or even indeed if one is used at all.
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
#Constructor 1
JonSnow()
{
$this.Knowledge = "Nothing"
$this.Status = Get-JonsCurrentStatus
}
#Constructor 2
JonSnow([int]$Season)
{
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
$this.Status = Get-JonsCurrentStatus #I don't want to have to put this in every constructor
}
}
}
$js = [JonSnow]::new()
$js
Unfortunately, you cannot call other constructors in the same class with : this() (though you can call a base class constructor with : base())[1]
Your best bet is a workaround with a (hidden) helper method:
function Get-JonsCurrentStatus {
return "Alive"
}
Class JonSnow {
[string]
$Knowledge
[string]
$Status
# Hidden method that each constructor must call
# for initialization.
hidden Init() {
$this.Status = Get-JonsCurrentStatus
}
#Constructor 1
JonSnow()
{
# Call shared initialization method.
$this.Init()
$this.Knowledge = "Nothing"
}
#Constructor 2
JonSnow([int]$Season)
{
# Call shared initialization method.
$this.Init()
if ($Season -ge 6)
{
$this.Knowledge = "Still nothing"
}
}
}
$js = [JonSnow]::new()
$js
[1] The reason for this by-design limitation, as provided by a member of the PowerShell team is:
We did not add : this() syntax because there is a reasonable alternative that is also somewhat more intuitive syntax wise
The linked comment then recommends the approach used in this answer.
You can initialise class properties on instantiation this way:
$jon = new-object JonSnow -Property #{"Status" = Get-JonsCurrentStatus; "Knowledge" = "Nothing"}