Powershell: Improvements for classes

Created on 17 Mar 2018  路  25Comments  路  Source: PowerShell/PowerShell

Original discussion in https://github.com/PowerShell/PowerShell/issues/6015

I have the following few things that keep bugging me when I write classes and would would be great to seem them appear in a next version (or at least a few of them)

  • [ ] Interface creation (Tracking #2223)
  • [ ] Abstract classes
  • [ ] Possibility to Override properties getters and setters (Tracking #2219)
  • [ ] Creation of static and non static nested classes
  • [ ] Implementing comment based help in Classes
  • [ ] Comment based help for Methods
  • [ ] Remove the need to reload classes (Currently, when developing, one needs to kill the console, and reload the class to get the latest version. IT would make sense to have the same functionality as in functions: Just F5, and the old version of classes get overwritten by the new version, and we can use it immediatley).
Issue-Enhancement WG-Language

Most helpful comment

@SamPosh Also, many of the features requested here are common polymorphic class features in many Object Oriented programming languages, not just C#. Classes in PowerShell open up a language-native Object Oriented programming paradigm. Unfortunately, the current implementation of classes in PowerShell offer little benefit over advanced features of PSCustomObjects which has hindered adoption. Addition of features requested in this issue improve Object Oriented paradigm and bring is closer to parity with other Object Oriented languages.

All 25 comments

cc @rjmholt if he wants to take on some of these

@Stephanevg thanks for opening this issue, I updated your description into checkboxes so we can track progress as the different items won't all be addressed at once.

@Stephanevg What do you mean when you say "Creation of static and non static _internal_ classes". (I can think of a couple of interpretations but i'd like to hear what you're thinking.)

Here are some nice to have features

  • The order of classes (with inheritance) should not matter when in a single file
    As a module author, I concat all my classes and functions into the psm1 at publishing time.
  • The ability to create classes in a namespace or auto namespace based on module name.
  • Use of default values or optional parameters in constructors
  • A way to export classes from a module in a discoverable way
  • Add option to mark class as hidden so using module does not expose or import them. (private classes)
  • A Get-Class -Module type of command like Get-Command
  • The ability to create a class based cmdlet in PowerShell

I want to ask for true private classes and members, but I am aware of why that does not work in PowerShell.

@Stephanevg @KevinMarquette it would be great if you can order your lists from highest priority to lowest priority

@BrucePay The main need I have behind this one is to implement the Builder Pattern.

In my module RegardsCitoyensPS, some of the constructors I have build are simply HUGE, implementing the Builder patter would allow me reduce this.

I want to create a static internal class called Builder to whom I would delegate the mechanism of the creation of the Object. In the end, I will simplify the process of creation of an instance of my class for the engineers using my class.

Example

This is my use case:

Current -->

Class Depute{
    [int]$id
    [String]$Nom
    [String]$Prenom
    [Sexe]$Sexe
    [DateTime]$DateNaissance
    [String]$LieuNaissance
    [String]$Groupe
    [String]$NomCirconscription
    [int]$numcirco
    [int]$PlaceHemicylce
    [DateTime]$DebutDeMandat
    [String]$Profession
    [String]$Twitter
    [int]$NombreDeMandats
    [String]$partirattfinancier
    [Mandat[]]$autresmandats
    [String[]]$Collaborateurs
    [String[]]$Emails

}  

    Depute([int]$id,[String]$Nom,[String]$Prenom,[String]$Groupe,[DateTime]$DateNaissance,[String]$LieuNaissance,[Sexe]$Sexe,[string]$nomcirco,[int]$numcirco,[int]$PlaceHemicylce,[DateTime]$DebutDeMandat,[String]$Profession,[string]$Twitter,[int]$NbMandats,[string]$partirattfinancier,[Mandat[]]$autresmandats,[string[]]$Collaborateurs,[string[]]$Emails){
        $this.id = $id
        $this.Nom = $Nom
        $This.Prenom = $Prenom
        $This.Groupe = $Groupe
        $this.DateNaissance = $DateNaissance
        $this.LieuNaissance = $LieuNaissance
        $This.Sexe = $Sexe
        $this.NomCirconscription = $nomcirco
        $this.numcirco = $numcirco
        $this.PlaceHemicylce = $PlaceHemicylce
        $this.Profession = $Profession
        $This.Twitter = $Twitter
        $this.DebutDeMandat = $DebutDeMandat
        $this.NombreDeMandats = $NbMandats
        $this.partirattfinancier = $partirattfinancier
        $this.autresmandats = $autresmandats
        $this.Collaborateurs = $Collaborateurs
        $this.Emails = $Emails

    }
}

Ho I imagined implementing the Builder Pattern to solve this:

Class Depute{
    [int]$id
    [String]$Nom
    [String]$Prenom
    [Sexe]$Sexe
    [DateTime]$DateNaissance
    [String]$LieuNaissance
    [String]$Groupe
    [String]$NomCirconscription
    [int]$numcirco
    [int]$PlaceHemicylce
    [DateTime]$DebutDeMandat
    [String]$Profession
    [String]$Twitter
    [int]$NombreDeMandats
    [String]$partirattfinancier
    [Mandat[]]$autresmandats
    [String[]]$Collaborateurs
    [String[]]$Emails

  Hidden Depute([Builder]$Builder){
    $this.Id = $Builder.Id
    $this.Prenom = $Builder.Prenom
    $this.Nom = $Builder.Nom
     #etc...

  }

  Static Class Builder {
     [int]$id
     [String]$Nom
      [String]$Prenom
      [Sexe]$Sexe
      [DateTime]$DateNaissance
      [String]$LieuNaissance
      [String]$Groupe
      [String]$NomCirconscription
      [int]$numcirco
      [int]$PlaceHemicylce
      [DateTime]$DebutDeMandat
      [String]$Profession
      [String]$Twitter
      [int]$NombreDeMandats
      [String]$partirattfinancier
      [Mandat[]]$autresmandats
      [String[]]$Collaborateurs
      [String[]]$Emails

     [Builder] Id($Id){
      $This.id = $id
      return $this
     }
     [Builder] Nom($Nom){
       $this.Nom = $nom
       return $this
    }
   [Builder] Prenom($Prenom){
    $this.Prenom = $prenom 
    return $this
   }

   [Builder] Depute Groupe($Groupe){
      $This.Groupe = Groupe
       return $this
    }

   #etc... for every property.

   #The Build Method which actually would create the object
   [Depute] Depute Build(){

     Return [Depute]::New($This)

     }

}

Usage

In the end, I hope to reduce the complexity for the consumers of this class, and make the usage of a long constructor more straightforward and easier. The example above would result in something like this:

 [Depute]::New().Builder().Prenom("Emanuel").Nom("Macron").Groupe("LREM").Build()

That is how I imagined it.

Today, I managed to make it work using a static method that would call [Depute]::New() and methods in [Depute] to add the different properties and that return $this.

It works, but I actually liked the idea of having an internal class to attach functionality to it, and build my class through that Static class.

Is that what you had in mind @BrucePay ?

@SteveL-MSFT done. To me, everything that would help us to simply the implementation of Design patterns should be highest prio. (Interfaces and Abstract Classes are the two main ones for me).

Adding help, is really secondary in my opinion, BUT really necessary.

@Stephanevg By internal here you mean a nested or inner class rather than an internal namepsace functionality? (I infer your primary language is French, just want to make sure I have the right idea)

Also, There is another small pain I have with Classes, is how we have to load them.
As a lot of PowerShell users, for functions, I had a folder Private and Public, containing several .ps1 files. Each file, contained a function, which I dot sourced via the .psm1

This way of organizing an item per file is not possible using classes, since a Class must be the very first thing to be loaded. Which means, the very first line of a Script.

In the end, as @KevinMarquette mentionned, Either during Build all the Classes will be merged in one psm1 file, or they are all already in a single file during developement.

What I understood from the powershell team members present at psconf in Germany, is that this behaviour was really difficult to change.

I was hoping that that we could find a solution for this.

Maybe, a new property in a .psd1 file with a property like ClassFolderToLoad = @(.\Classes\Public\) which would automatically load the classes during each module load could be an idea?

But this is for me really a small small point. The main topics for me at the moment are Interface and Abstract classes.

Hi @rjmholt

Oui, My first language is French ;)
and oui, you are correct. I meant nested / inner class, when I referred to Internal class. (I updated the first post mentionning Nested Classes instead of internal)

I updated the order on my list.

If all the mentioned things are added then is that not called C#?

If we add all the mentioned thing then is that not called C#?

We would like all the good class features that C# has, but allow us to do it in PowerShell. This is a valid request because calling out to PowerShell from C# is not near as easy as just writing PowerShell.

@SamPosh Also, many of the features requested here are common polymorphic class features in many Object Oriented programming languages, not just C#. Classes in PowerShell open up a language-native Object Oriented programming paradigm. Unfortunately, the current implementation of classes in PowerShell offer little benefit over advanced features of PSCustomObjects which has hindered adoption. Addition of features requested in this issue improve Object Oriented paradigm and bring is closer to parity with other Object Oriented languages.

Ok. While i understand the reason for good object oriented support, will these features not spoil the beauty and simplicity of powershell ? Sometimes worst is better. Can't we do it in different way and still maintain the simplicity?

below are just example not needed to be this way.

For Encapsulation - Script level scope variable
For polymorphism -
$ModuleObj= (Import-module "ModuleName" -clone)
$ModuleObj2 = (Import-module "ModuleName" -clone)
Help for functions
Nested class or internal class
Keeping .ps1 files and dot source them into module
Export only what is required.
Also all our existing modules can be easily be reused as kind of object oriented without much effort.

This is just a suggested alternative. I love simplicity of powershell ,that's all. I want to see that language be simple always.

I guess @SamPosh question was more of a rethorical question. as @markekraus mentionned, these features are part of the regular Class features (OOP Paradigm), and available in (almost?) all object oriented languages, not just C#. (Look at Javascript, it not called Java although classes are also available in it).

We are not talking about removing features, but extending what is already there. Powershell is already simple to use, and it will still be easy to use. There is a learning curve to real OOP, which can be difficult at the begining, but, that should not be a valid reason *not to improve* the language for the more advanced users.

@SamPosh I too love the simplicity of PowerShell, but, I also love the expressiveness and flexibility of PowerShell. I love that there are many different ways to do the same thing. There are instances where an Object Oriented paradigm make sense and others where it does not. There are times when Functional Programming paradigm makes sense and times where it does not. I think improving the OOP experience in PowerShell wont diminish any FP experience nor vice versa. I think they compliment each other. Being able to create better classes means better objects for functions to consume. Better functions means better use of objects. The fact that PowerShell can do both is a huge selling point as both a shell and scripting language. I just believe the OOP experience needs some tender love and care to make it a more "true" OOP experience and less painful to use. I don't think anyone in this thread is looking to replace FP aspects of the language.

@markekraus So this language will stay beautiful but more powerful. That's comforting... If that is so , if existing modules can be converted as class we can use it like OOP in some place and fp in legacy places. Please made the bridge between fp and OOP more seamless. When helping advanced developers don't forget the advanced scripters who are loyal to this language because of it's simplicity.The toughest thing is to make some thing simple. But powershell engineers did that. In OOP also if they follow simplicity ,it would be great.

@SteveL-MSFT Have you planned an evolution / correction on the type inference ?

@LaurentDardenne is there an open issue for that?

@SteveL-MSFT
I do not think so.
In the original issue the given examples are built around Powershell classes and one cast is enough to solve the call problem:

#With v6.1.0-preview.1

# this works
[System.Linq.Enumerable] :: Sum ($ values, [Func [int, int]] [N] :: Twice)
# but this does not do
[System.Linq.Enumerable] :: Sum ($ values, [N] :: Twice)

I indicated it here because this issue contains requests for evolutions related to Powershell classes.
If necessary I can create a specific evolution request.

I just hope whatever improvements are made that good documentation is done on the advantages to doing a class over PSObject/traditional PowerShell with real-world examples.

Part of why I find Classes useless in PowerShell is because the syntax gets uglier for something that you can do with a simple [PSCustomObject] @{}. I've never seen a good argument to use it outside of "it's what I know".

Hi, I was wondering if any preogress has been done on this one? @SteveL-MSFT It seems like this one is not assigned anymore. It would be great to have these features / fixes implemented for 6.2

@Stephanevg unfortunately at this time, we can't commit to addressing this issue in 6.2 timeframe perhaps 1 or 2 items may make it, but the team is busy with other necessary work right now

Or in the 7.x timeframe.

There should be a way to reference $this class static members in class methods and property default values.

Example of how it works now (at least in Windows PowerShell):

class Test {
    [String] static $Foo = "Value1"
    [String] static $Bar
    static Test() {
        [Test]::Bar = [Test]::Foo + "Value2"
    }
}

There's no $this::Foo thing.

We should be able to use readonly modifier for class properties in some way, to make them readonly. But, we can't use fields in classes... We can use getter and setter approach for this, but there's no way to make setter private/protected. So, basically, there's no way to make member readonly or writeable for internal use. Every PSClass is exposed by default.

There's no support for namespaces in PSClasses. There's no way to even basically _mimic_ namespaces, like [ModuleName.ClassName]. PowerShell automatically assume what there's no way should exist two classes with same name, using scoping of classes instead.

I could mention the Generics, but these ones are extreme for PowerShell for now. So at least PowerShell should support using them from assemblies, allow using generic methods (#5146) or allow generic inheritance (I could think of inheriting from IDictionary<String,Object>).

Was this page helpful?
0 / 5 - 0 ratings