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"}
Related
i want to add items to arraylist object inside the class. how to do it. below is powershell code. it doesnt allow. how to achive this with
1. array declared at global level
2. array declared at class level
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$logArrayGlobal.Add("sada")
$this.logArrayClass
}
}
PowerShell will allow you to write to non-local variables from within a class method body if you explicitly specify the variables scope:
$logArrayGlobal = [System.Collections.ArrayList]::new()
class LogManager {
$logArrayClass = [System.Collections.ArrayList]::new()
LogManager()
{
$global:logArrayGlobal.Add("sada")
}
}
i just study a powershell. and i have a little problem. for example :
class a
{
[eventhandler]$event;
a()
{
$this.event+=[b]::method($this,[c]::new(3)) #inside constuctor i add static method as a handler
}
}
class b
{
static method([object]$s,[eventargs]$ea)
{
# do smthng;
}
}
class c:System.EventArgs
{
$x;
c($i)
{
$this.x = $i
}
}
$aInstance = [a]::new();
$aInstance.event -eq $null # TRUE
Whatever i do, my instance event always NULL. What i do wrong?
so sad. but in ps v.5.1 events works with some errors, i mean self declarated events; Everything ok in ps v7 core
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
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.
Is it possible to have a property in a class reference another? For example, something like this. I've tried a few ways and I'm now not sure if I can do this...:
class TestClass {
[string]
$SQLInstanceName
[string]
$Server = "$($env:COMPUTERNAME)\$SQLInstanceName"
[string]myResult()
{
return $this.Server
}
}
....Thanks
Yes. Here it is implemented in your class definition:
class TestClass {
[string]
$SQLInstanceName
hidden $_Server = $($this | Add-Member ScriptProperty 'Server' `
{
# get
"$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
}
)
[string]myResult()
{
return $this.Server
}
}
To see this working, new up an instance and assign a value to SQLInstanceName.
$c = [TestClass]::new()
$c.SQLInstanceName = 'MyDB'
Then invoking
$c.Server
$c.myResult()
results in
ComputerName\MyDB
ComputerName\MyDB
You should be using $this if you want to refer to a non-static property/method in the same object just like you have done with your myResult() method. Also your current sample has no default value or constructor so the SQLInstanceName is blank so just adding $this, without setting the variable, might give you misleading results. The following example might be something to consider but it is flawed.
class TestClass {
[string]$SQLInstanceName = "Test"
[string]$Server = "$($env:COMPUTERNAME)\$($this.SQLInstanceName)"
[string]myResult()
{
return $this.Server
}
}
$tcobject = New-Object TestClass
$tcobject.myResult()
However this does not work if you change the SQLInstanceName property since you are just setting default values. Classes in v5 don't really have get and set truly implemented in the same way as a .Net class so you would have to roll your own solution for that as discussed in this answer but also on this blog about v5 classes in general.
So a simple solution would work like this to get what I think you want.
class TestClass {
[string]
$SQLInstanceName = "Test"
[string]
$Server = $env:COMPUTERNAME
[string]myResult()
{
return "$($this.Server)\$($this.SQLInstanceName)"
}
}
$tcobject = New-Object TestClass
$tcobject.SQLInstanceName = "server\prod"
$tcobject.myResult()
This would be a design choice but I would not be trying to dynamically change one property based on the value of another in this case. Since you are using a value of them combined a simple method could work.