Question [Astuce]Créer un membre synthétique d'accès privé

Plus d'informations
il y a 17 ans 6 mois #2754 par Laurent Dardenne
Voici une méthode pour créer un membre synthétique en ReadOnly.
La création de membres synthétiques ne permet pas de créer des champs privés accessibles uniquement par le code de l'instance.

Il y a bien le membre ScriptProperty qui permet de ne pas déclarer de setter (accesseur en écriture) mais dans ce cas on ne peut déclarer qu'une constante.
[code:1]
$Objet= new-object System.Management.Automation.PsObject
$Objet | add-member ScriptProperty Comment -value {\"Commentaire en readOnly\"}
$Objet.Comment=\"Nouveau contenu\"
[/code:1]
Il reste possible de redéclarer l'objet avec un nouveau contenu mais dans ce cas on perd toutes les valeurs des possibles autres membres.
Premier problème, la portée des variables :
[code:1]
Function Test
{ #Crée un objet possédant des membres synthétique en lecture uniquement
param([String] $Comment)

$Objet= new-object System.Management.Automation.PsObject
$Objet | add-member ScriptProperty Comment -value {$Comment}
Write-host (\"*** Debug interne : {0}\" -F $Objet.Comment)
$Objet
}
$Comment=$null
#$Comment=\"Variable globale\"

$O=Test \"Paramètre\"
$O
[/code:1]
Ici le membre Comment reste vide car la variable $Comment, c'est à dire le paramètre, n'est plus accessible.
Si on supprime le commentaire de la seconde déclaration on à droit à un petit effet de bord.

Le paramètre value de Add-member étant un scriptbloc on peut lui préciser une constante et utiliser le pipe :
[code:1]
$O| add-member ScriptProperty Min -value {10}
# Peut être :
# $O| add-member ScriptProperty FirstFile -value {Dir|Select -First 1}
$O
$O.Min=11
[/code:1]
Pour obtenir ce comportement au sein d'une fonction on peut construire le code de la déclaration du membre synthétique.
On déclare une chaîne paramètrée , i.e. utilisée avec l'instruction de formatage -F, qui contient la définition du membre synthétique :
[code:1]
$O= new-object System.Management.Automation.PsObject
$RazMember=\"`$O| add-member -Force ScriptProperty Min -value {0}\"
[/code:1]
Ensuite on formate la chaîne avec le contenu à insérer en tant que script bloc :
[code:1]
$MaValeur=257
$S=$RazMember -F \"{[int]$MaValeur}\"
[/code:1]
Et enfin on exécute le code contenu dans notre chaine formatée
[code:1]
Invoke-Expression $S
$O
$O.Min=-1
[/code:1]
Une fonction d'exemple :
[code:1]
Function Create-SyntheticMemberRO
{ #Crée un objet possédant des membres synthétique en lecture uniquement
param([String] $Comment,
[int] $MaxValue,
[switch] $Cycle)


#Création de l'objet
$Objet= new-object System.Management.Automation.PsObject

# Ajout des propriétés synthétiques en R/O, le code est créé dynamiquement
$MakeReadOnlyMember=@\"
`$Objet | add-member ScriptProperty Comment -value {\"$Comment\"} -Passthru|`
add-member ScriptProperty MaxValue -value {[int]$MaxValue} -Passthru|`
add-member ScriptProperty Cycle -value {`$$Cycle}
\"@
Invoke-Expression $MakeReadOnlyMember

$Objet| add-member ScriptMethod Inc{
$NewValue=$this.MaxValue
$NewValue++
#Déclaration paramétrée pour la redéfinition du membre CurrVal
#Le paramètre -Force annule et remplace la définition du membre spécifié
$RazMember=\"`$this | add-member -Force ScriptProperty MaxValue -value {0}\"

#On construit (par formatage) la définition du membre CurrVal
#puis on reconstruit le membre.
Invoke-Expression ($RazMember -F \"{[int]$NewValue}\"«»)
}
#Spécifie l'affichage des propriétés par défaut. On évite ainsi l'usage d'un fichier de type .ps1xml
#From poshoholic.com/2008/07/05/essential-powe...-for-custom-objects/
$DefaultProperties =@(
'MaxValue',
'Cycle',
'Comment'
)
$DefaultPropertySet=New System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$DefaultProperties)
$PSStandardMembers=[System.Management.Automation.PSMemberInfo[]]@($DefaultPropertySet)
$Objet|Add-Member MemberSet PSStandardMembers $PSStandardMembers

return $Objet
}

$Obj=Create-SyntheticMemberRO \"Membre en lecture seule\" 25 -Cycle
$obj
$obj.MaxValue
#Modification impossible
$obj.MaxValue=10
$obj.Inc()
$obj
[/code:1]
Ainsi on peut donc, certes au détriment des performances, créer des membres synthétiques en lecture seule.
Pas mal les langages dynamiques, n'est-il pas ?
;-)<br><br>Message édité par: Laurent Dardenne, à: 20/12/08 18:14

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 17 ans 6 mois #2755 par Arnaud Petitjean
Salut Laurent !

Un poil complexe mais très intéressant. Ca me fait penser qu'il faudrait qu'à l'occasion je fasse un tuto (plus basique) sur la création d'objets tel que tu peux le faire dans cet exemple.

@++

Arnaud

MVP PowerShell et créateur de ce magnifique forum :-)
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 17 ans 6 mois #2758 par Laurent Dardenne
Arnaud écrit:

Un poil complexe mais très intéressant.

Je dirais d''un usage avancé car on une fois que l'on a compris le principe, il n'y a rien de complexe.
Les langage dynamiques comportent une approche permettant du codage à la volée, une fois que l'on compris cela PS devient vraiment puissant.

Arnaud écrit:

Ca me fait penser qu'il faudrait qu'à l'occasion je fasse un tuto (plus basique) sur la création d'objets tel que tu peux le faire dans cet exemple.

Je viens d'en terminer un sur le sujet (+- 20 pages).
Ainsi qu'un exemple complet mais là d'un niveau avancé, je le poste prochainement.
Quel aspect de ce sujet aurais- tu aimer développer ?

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 17 ans 6 mois #2759 par Arnaud Petitjean
Salut !

Je dirais d'un usage avancé...

Oui c'est ce que je voulais dire.

Quel aspect de ce sujet aurais-tu aimer développer ?

Je pensais à quelque chose d'assez simple pour commencer, pour montrer quelle est la technique pour se créer des objets personnalisés. Je n'ai pas d'exemple en tête particulier. Mais l'idée serait d'expliquer dans quel cas choisir un type (ScriptProperty, NoteProperty) plutôt qu'un autre.

Je viens d'en terminer un sur le sujet (+- 20 pages). Ainsi qu'un exemple complet mais là d'un niveau avancé, je le poste prochainement.

Whaou ! Super, j'ai hate de le lire ! :woohoo:

Arnaud

MVP PowerShell et créateur de ce magnifique forum :-)
Auteur de 6 livres PowerShell aux éditions ENI
Fondateur de la société Start-Scripting
Besoin d'une formation PowerShell ?

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 17 ans 6 mois #2761 par Laurent Dardenne
Arnaud écrit:

Je pensais à quelque chose d'assez simple pour commencer, pour montrer quelle est la technique pour se créer des objets personnalisés.

Après réflexion je ne sais pas si ce sujet peut être simple pour un débutant. L'usage d'objets personnalisés n'est pas la première chose que l'on fait sous PS.
La technique est relativement simple à appréhender, ensuite c'est plus de la conception et de l'étude des possibilités technique de PS.
Arnaud écrit:

Mais l'idée serait d'expliquer dans quel cas choisir un type (ScriptProperty, NoteProperty) plutôt qu'un autre.

Je me suis limité à décrire leurs fonctions ensuite je ne vois pas trop quoi ajouter sans tomber dans des explications des mécanismes de la POO...
La recherche d'exemples et leurs études est bien plus utile, il me semble.
Connaissant la POO depuis pas mal de temps ce qui m'a posé pb au début était de comprendre leurs rôles et la syntaxe de leurs déclarations. Puis de découvir leurs limites car PS n'est pas un langage objet comme certains le disent mais basé objet comme VB6, c'est à dire qu'on ne peut pas créer de classe. Des objets personnalisés oui mais pas de nouvelle classe, héritée ou non.

Pour résoudre ce pb de membre privé j'aurais trés bien pu utiliser des variables contraintes, voir mon tuto sur le sujet, associée à des membres de type NoteProperty mais le \&quot;défaut\&quot; de cette solution est d'utiliser du code C# compilé et une duplication de données. Dans ce cas autant créer une classe .NET. Si j'ai le temps je mettrais en ligne cette solution.
Arnaud écrit:

j'ai hate de le lire !

Patiente encore qq jours :)
En attendant je poste le script Create-Sequence.<br><br>Message édité par: Laurent Dardenne, à: 3/09/08 17:16

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Plus d'informations
il y a 16 ans 1 mois #6052 par Laurent Dardenne
Une autre solution.

L'approche classique avec un membre ScriptProperty est de le coupler avec un membre de type NoteProperty.

Le pb est que l'objet est en accès public et n'est donc pas protégé d'une modification intempestive.

Puisqu'on ne dispose pas de la possibilité d'ajouter des membres additionnels privés, on peut modifier la valeur du \&quot;Getter\&quot; qui est un scriptblock renvoyant une valeur.

Dans l'exemple du premier post cette modification est effectué via un autre membre, avec l'approche suivante ce n'est plus nécessaire.

Celle-ci fonctionne avec des scalaires uniquement (string, entier, date,etc) puisque ceux-ci peuvent être représentés à l'aide d'une chaîne de caractères.

Le principe est de modifier le getter, l'accesseur renvoyant la valeur de la propriété, dans le bloc du setter, l'accesseur modifiant la valeur de la propriété.

Ceci est possible car PS \&quot;extrait\&quot; le code de la définition de l'objet puis l'exécute, ce code exécuté est donc indépendant de sa déclaration, c'est un des principes du dynamisme.

Première solution :
[code:1]
#On modifie la valeur du getter,
#tout en gardant le code du setter,
#puis on reconstruit dynamiquement ce membre.
$O=New-Object PSObject|
Add-Member ScriptProperty PropertyName -value {\&quot;CreationTime\&quot;} -SecondValue {
$Setter=($this.PsObject.Members|Where {$_.Name -eq \&quot;PropertyName\&quot;}).SetterScript.ToString()
Invoke-Expression(
((\&quot;`$this | add-member -Force ScriptProperty PropertyName -value {0} \&quot;-F \&quot;{`\&quot;$($Args[0])`\&quot;}\&quot;«»)+
\&quot;-SecondValue {$Setter}\&quot;
)
)
} -Passthru
$o.PropertyName
$o.PropertyName=\&quot;LastWriteTime\&quot;
$o.PropertyName
[/code:1]
Après l'exécution du code d'affectation, le getter ressemble à ceci :
[code:1]
-value {\&quot;LastWriteTime\&quot;} -SecondValue { #le code reste identique
[/code:1]
On ne peut pas modifier directement les accesseurs, car ils sont en lecture seule, on doit recréer le membre.

Seconde solution, utilisant les membres de la classe dotnet sous-jacente de l'objet $O:
[code:1]
$O=New-Object PSObject|
Add-Member ScriptProperty PropertyName -value {\&quot;CreationTime\&quot;} -SecondValue {
#On modifie la valeur du getter
#(\&quot;CreationTime\&quot; est sa valeur d'origine).
$Getter=$ExecutionContext.InvokeCommand.NewScriptBlock(\&quot;`\&quot;$($Args[0])`\&quot;\&quot;«»)
#On réutilise le code du setter
#(ici on est certains de ne récupérer qu'un seul élément).
$Setter=@($this.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»))[0]
#On supprime la propriété actuelle.
[void]$this.PsObject.Properties.Remove(\&quot;PropertyName\&quot;«»)
#Puis on la reconstruit dynamiquement.
$ANewMySelf=New-Object System.Management.Automation.PSScriptProperty(\&quot;PropertyName\&quot;,`
$Getter,$Setter.SetterScript)
[void]$this.PsObject.Properties.Add($ANewMySelf)
} -Passthru

$o.PropertyName
$O.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»)
Write-Warning \&quot;Modification\&quot;
$o.PropertyName=\&quot;LastWriteTime\&quot;
$O.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»)
$o.PropertyName
[/code:1]
Ensuite on peut envisager un objet verrouillant une de ses propriétés selon une condition particulière.

Rayon bricolage :
[code:1]
$O=New-Object PSObject|
Add-Member ScriptProperty PropertyName -value {\&quot;CreationTime\&quot;} -SecondValue {
if ($Args[0] -ne \&quot;@*@Pattern@-@ReadOnly\&quot;«»)
{
#On modifie la valeur du getter
#(\&quot;CreationTime\&quot; est sa valeur d'origine).
$Getter=$ExecutionContext.InvokeCommand.NewScriptBlock(\&quot;`\&quot;$($Args[0])`\&quot;\&quot;«»)
#On réutilise le code du setter
#(ici on est certains de ne récupérer qu'un seul élément).
$Setter=@($this.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»))[0]
#On supprime le membre actuel.
[void]$this.PsObject.Properties.Remove(\&quot;PropertyName\&quot;«»)
#Puis on le reconstruit dynamiquement.
$ANewMySelf=New-Object System.Management.Automation.PSScriptProperty(\&quot;PropertyName\&quot;,$Getter,$Setter.SetterScript)
[void]$this.PsObject.Properties.Add($ANewMySelf)
}
else
{
$Getter=@($this.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»))[0]
$Setter=$ExecutionContext.InvokeCommand.NewScriptBlock('Throw \&quot;PropertyName est en lecture seule.\&quot;')
[void]$this.PsObject.Properties.Remove(\&quot;PropertyName\&quot;«»)
$ANewMySelf=New-Object System.Management.Automation.PSScriptProperty(\&quot;PropertyName\&quot;,`
$Getter.GetterScript,$Setter)
[void]$this.PsObject.Properties.Add($ANewMySelf)
}
} -Passthru

$o.PropertyName=\&quot;LastWriteTime\&quot;
$O.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»)
$o.PropertyName
#Bascule la propriété en ReadOnly
$o.PropertyName=\&quot;@*@Pattern@-@ReadOnly\&quot;
$O.PsObject.Properties.Match(\&quot;PropertyName\&quot;«»)
$o.PropertyName
$o.PropertyName=\&quot;Erreur\&quot;
[/code:1]
Ce dernier cas est, comme le saint-émilion, à consommer avec modération ;-)<br><br>Message édité par: Laurent Dardenne, à: 29/01/10 15:39

Tutoriels PowerShell

Connexion ou Créer un compte pour participer à la conversation.

Temps de génération de la page : 0.070 secondes
Propulsé par Kunena