Class instance event field always null - powershell

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

Related

how to add objects in arraylist in powershell class

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")
}
}

Testing singleton initialization and throwing appropriate exceptions

I am continuing to try to grok a singleton implementation with PowerShell classes, and having issues now with handling exceptions.
My test singleton needs to be initialized the first time with a list of files to process, and all following references should have no arguments, since I never want to reprocess files, just reference what was processed initially. To that end I have this...
class TestClass {
# Properties
[collections.arrayList]$Files = [collections.arrayList]::New()
static [TestClass] $instance = $null
# Constructor
TestClass([string[]]$filePaths){
foreach ($path in $filePaths) {
$this.Files.Add("$($path): Processed")
}
}
[void] ListFiles() {
foreach ($processedFile in $this.Files) {
Write-Host "$processedFile!"
}
}
# Singleton Methods
static [TestClass] GetInstance() {
if ([TestClass]::Instance -ne $null) {
$caller = (Get-PSCallStack)[-1]
throw "Px Tools Exception: Singleton must be initialized ($caller)"
}
return [TestClass]::Instance
}
static [TestClass] GetInstance([string[]]$filePaths) {
if ([TestClass]::Instance -eq $null) {
[TestClass]::Instance = [TestClass]::New($filePaths)
} else {
$caller = (Get-PSCallStack)[-1]
throw "Px Tools Exception: Singleton cannot be reinitialized ($caller)"
}
return [TestClass]::Instance
}
}
A properly done first initialization works, i.e.
$firstInstance = [TestClass]::GetInstance($unprocessedFiles)
$firstInstance.ListFiles()
will initialize the singleton and list the files. So the conditional if ([TestClass]::Instance -eq $null) works.
Also, if I try to reinitialize like this
$firstInstance = [TestClass]::GetInstance($unprocessedFiles)
$secondInstance = [TestClass]::GetInstance($unprocessedFiles)
I get the appropriate
Exception: Singleton cannot be reinitialized
However, if I make the first reference without the files to process, like this...
$firstInstance = [TestClass]::GetInstance()
I get no error. So, why does if ([TestClass]::Instance -eq $null) seem to work correctly but if ([TestClass]::Instance -ne $null) does not? And, is there a better way to address this? I thought perhaps I could test the $Files property, either for $null or a Count of 0, but I can't reference a non static property from a static method, so barring the kludgy approach of a static $Initialized property, what is the solution?
EDIT: Duh. Hopefully this helps someone else. Don't test for the value, test for existence, like this.
if (-not [TestClass]::Instance) {

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"}

A property referencing another property in a PowerShell Class

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.