PowerShell icon indicating copy to clipboard operation
PowerShell copied to clipboard

Improvements for classes

Open Stephanevg opened this issue 7 years ago • 29 comments

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).

Stephanevg avatar Mar 17 '18 17:03 Stephanevg

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.

SteveL-MSFT avatar Mar 19 '18 16:03 SteveL-MSFT

@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.)

BrucePay avatar Mar 19 '18 22:03 BrucePay

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.

KevinMarquette avatar Mar 20 '18 05:03 KevinMarquette

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

SteveL-MSFT avatar Mar 20 '18 18:03 SteveL-MSFT

@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 ?

Stephanevg avatar Mar 20 '18 20:03 Stephanevg

@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 avatar Mar 20 '18 20:03 Stephanevg

@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)

rjmholt avatar Mar 20 '18 20:03 rjmholt

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.

Stephanevg avatar Mar 20 '18 20:03 Stephanevg

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)

Stephanevg avatar Mar 20 '18 20:03 Stephanevg

I updated the order on my list.

KevinMarquette avatar Mar 21 '18 00:03 KevinMarquette

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

SamPosh avatar Mar 21 '18 11:03 SamPosh

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.

KevinMarquette avatar Mar 21 '18 12:03 KevinMarquette

@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.

markekraus avatar Mar 21 '18 12:03 markekraus

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) [$ModuleObj]::FunctionName $ModuleObj2 = (Import-module "ModuleName" -clone) [$ModuleObj2]::FunctionName (If we can do this then below points are automatically covered) 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.

SamPosh avatar Mar 21 '18 12:03 SamPosh

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.

Stephanevg avatar Mar 21 '18 13:03 Stephanevg

@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 avatar Mar 21 '18 13:03 markekraus

@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.

SamPosh avatar Mar 21 '18 13:03 SamPosh

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

LaurentDardenne avatar Mar 28 '18 12:03 LaurentDardenne

@LaurentDardenne is there an open issue for that?

SteveL-MSFT avatar Mar 28 '18 16:03 SteveL-MSFT

@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.

LaurentDardenne avatar Mar 28 '18 20:03 LaurentDardenne

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

dragonwolf83 avatar Mar 28 '18 23:03 dragonwolf83

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 avatar Oct 18 '18 13:10 Stephanevg

@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

SteveL-MSFT avatar Oct 19 '18 06:10 SteveL-MSFT

Or in the 7.x timeframe.

Jaykul avatar Oct 01 '19 19:10 Jaykul

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>).

fixinggunsinair avatar Apr 21 '20 23:04 fixinggunsinair

I think it would be awesome to get something like C# 9 records. I primarily use classes over [PSCustomObject] just to get reasonable ToString() and Equals(). This requires bunch of boilerplate, some of it I can abstract away with a helper returning a tuple of all properties to forward Equals() and GetHashCode().

dkaszews avatar Jul 18 '23 20:07 dkaszews

Is there any progress, or any other issue tracking the last check box (Remove the need to reload classes)?

Shayan-To avatar Jan 15 '24 09:01 Shayan-To

I believe we should close this issue.

This issue has not had any activity in 6 months, if there is no further activity in 7 days, the issue will be closed automatically.

Activity in this case refers only to comments on the issue. If the issue is closed and you are the author, you can re-open the issue using the button below. Please add more information to be considered during retriage. If you are not the author but the issue is impacting you after it has been closed, please submit a new issue with updated details and a link to this issue and the original.

I don't believe we should close this issue. It was mentioned a few times that this would receive some more love in the next Coming versions, but It looks like investments have been made in other areas that had a higher priority.

Is there some buffer for this one now @SteveL-MSFT ? It has been open for a while, and it would be great to have at least some of the points crossed out one day.

Stephanevg avatar Jan 15 '24 10:01 Stephanevg