Question Re:[Fonction]Création de délégué, générique ou pas

Plus d'informations
il y a 15 ans 2 mois #3588 par Laurent Dardenne
Le problème cité à la fin du précédent post est est du, d'après mes constatations, au système de type de PowerShell ctp2. En V1 je n'ai pas trouvé de manière de faire.
Avec la ctp3, qui supporte désormais les génériques, c'est déjà mieux mais ce n'est pas encore ça puisque la classe utilisée doit se trouver dans le GAC .

Test avec l'assembly, contenant la classe utilisée, n'étant pas référencé dans le GAC :
[code:1]
PS >[System.Reflection.Assembly]::LoadFile((Join-Path $PWd \"MyStruct.dll\"«»))

GAC Version Location
---

False v2.0.50727 G:\PS\Delegate\MyStruct.dll

PS >[System.Type] $m=\"System.Comparison`[MyStruct.Personne]\"
Cannot convert the \"System.Comparison[MyStruct.Personne]\" value of type \"System.String\" to type \"System.Type\".
At line:1 char:17
+ [System.Type] $m <<<< =\"System.Comparison`[MyStruct.Personne]\"
+ CategoryInfo : MetadataError: (:«») [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException

PS >[System.Type] $m=\"System.Collections.ObjectModel.Collection`[MyStruct.Personne]\"
Cannot convert the \"System.Collections.ObjectModel.Collection[MyStruct.Personne]\" value of type \"System.String\" to type
\"System.Type\".
At line:1 char:17
+ [System.Type] $m <<<< =\"System.Collections.ObjectModel.Collection`[MyStruct.Personne]\"
+ CategoryInfo : MetadataError: (:«») [], ArgumentTransformationMetadataException
+ FullyQualifiedErrorId : RuntimeException
[/code:1]
Test avec l'assembly, le fichier dll, référencé dans le GAC :
[code:1]
PS > [System.Reflection.Assembly]::LoadWithPartialName(\"MyStruct\"«»)

GAC Version Location
---

True v2.0.50727 C:\WINDOWS\assembly\GAC_MSIL\MyStruct\2.0.0.0__33d7722ee7a50030\MyStruct.dll


PS > [System.Type] $m=\"System.Comparison`[MyStruct.Personne]\"
PS > [System.Type] $m=\"System.Collections.ObjectModel.Collection`[MyStruct.Personne]\"
#Aucune erreur
[/code:1]
C'est légérement ennuyant puisque, à ce jour, les types créés avec le cmdlet Add-Type, disponible avec la V2, ne pourront être utilisés avec des classes génériques :S

Pour en revenir au tri de structure personnalisée, on doit modifier légérement la méthode Get-DelegateComparison :
[code:1]
Function Get-DelegateComparison([ScriptBlock] $SBCompare,[String] $Type)
... # contenu identique
Add-Member -Name Execute `
-MemberType ScriptMethod `
-value {
#Execute($Tableau-Struct,\"NomDeClé\"«»)
$Tab=$Args[0]
$SortProperty=$Args[1]
if (($Tab[0]|Get-Member -MemberType *Property -Name $SortProperty) -eq $null)
{ Throw (\"La propriété `\"{0}`\" n'existe pas\" -f $SortProperty) }
$this.GenericMethod.Invoke($null,@($Tab,$this.Delegate))
} -Passthru
}
[/code:1]
Ensuite dans la structure C#
[code:1]
#Espace de nom : MyStruct
namespace MyStruct
{
#classe Personne de type struct
public struct Personne
{
#Propriétés
public String Nom;
public Int32 Age;

#Constructeur de la classe Personne
public Personne(String aNom, Int32 aAge)
{
#this référence l'instance courante en cours de création
this.Nom = aNom;
this.Age = aAge;
}

}
}
[/code:1]
On peuple un nouveau tableau typé à partir du tableau de hashtable précédemment créé :
[code:1]
[MyStruct.Personne[]] $Tp=@()
foreach ($Item in $T)
{
$P=new-object MyStruct.Personne
$P.Nom=$Item.Nom
$P.Age=$Item.Age #conversion de $null en Zéro
$Tp+=$P
}
[/code:1]
On utilise le même code de trie des éléments d'un tableau(celui utilisé avec un type HashTable), seul le nom du type de la classe manipulée dans le délégué change :
[code:1]
$SortComparison=Get-DelegateComparison $SBCompareHT \"MyStruct.Personne\"
Write-host \"Origine\"
$Tp
$SortComparison.Execute($Tp,\"Nom\"«»)
Write-host \"Trié par Nom\"
$Tp
$SortComparison.Execute($Tp,\"Age\"«»)
Write-host \"Trié par Age\"
$Tp
Write-host \"Trie avec une propriété inconnue\"
$SortComparison.Execute($Tp,\"Inconnue\"«»)
$Tp
[/code:1]
La création de délégué, sauf erreur de ma part du peu que j'ai vue de la ctp3,n'est pas encore pris en charge avec la ctp3
[code:1]
$MatchEvaluator= new-object System.Text.RegularExpressions.MatchEvaluator {
# Return a replacement for the matching string...
\"<$($args[0].ToString().ToLower())>\"

# and count the number of replacements...
$global:«»PatternCount++
}
#
# New-Object : Cannot find an overload for \"MatchEvaluator\" and the argument count: \"1\".
[/code:1]
Avec la fonction New-Delegate :
[code:1]
$MatchEvaluator= New-Delegate System.Text.RegularExpressions.MatchEvaluator {
# Return a replacement for the matching string...
\"<$($args[0].ToString().ToLower())>\"

# and count the number of replacements...
$global:«»PatternCount++
}
$MatchEvaluator
# Method Target
#

# System.String Handler(System.Collections.ArrayList, Syst... {69466907-543e-4e13-ab31-910ea0996fdf, ...
[/code:1]
Comme le disait Arnaud ce type de code est destiné à des développeurs mais pour moi PowerShell incite les développeurs à adopter une vision d'administrateur et ceux-ci à adopter une perception des choses le plus proche possible d'un développeur.
Si ce n'est certes pas le même métier, les deux manipulent les mêmes objets et c'est en cela que PowerShell est un super outil car il facilite la rencontre de ces 2 métiers.
Sous réserve que les deux le veuillent :whistle:<br><br>Message édité par: Laurent Dardenne, à: 30/12/08 15:29

Tutoriels PowerShell

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

Plus d'informations
il y a 13 ans 11 mois #6509 par Laurent Dardenne
Avec PowerShell version 2, la gestion des délégués est simplifiée, plus besoin de la méthode New-Delegate :
[code:1]
[Int32[]]$T=1..10
[Int32[]]$T2=1..11
#crée le délégué générique System.Predicate&lt;T&gt;, avec T égale à [System.Int32]
$delegate = [System.Predicate[System.Int32]] { param($item); $item -le 10}
[System.Reflection.MethodInfo] $Methode = [System.Array].GetMethod(\&quot;TrueForAll\&quot;«»)
#Crée la méthode avec le type du tableau manipulé, à savoir [System.Int32]
$MethodeGenerique = $Methode.MakeGenericMethod([System.Int32])
#Appel la méthode via Invoke, nativement PowerShell V1 gére mal les génériques
$MethodeGenerique.Invoke($null,@($T,$delegate))
$MethodeGenerique.Invoke($null,@($T2,$delegate))
[/code:1]
Adaptation d'un exemple du blog de MS-PowerShell :
[code:1]
$PatternCount=0
#crée un délégué du type précisé
$delegate= [System.Text.RegularExpressions.MatchEvaluator] {
# Return a replacement for the matching string...
# Renvoi le paramètre en minuscule &lt; paramètre &gt;
\&quot;&lt;$($args[0].ToString().ToLower())&gt;\&quot;
# and count the number of replacements...
$global:«»PatternCount++
}
# on recherche toutes les occurences d'une lettre en majuscule
$re = [regex] \&quot;[A-Z]\&quot;
# now transform some text...

\&quot;Ceci est du TEXTE\&quot; |
ForEach-object { $re.Replace($_, $delegate) }
# And display the number of replacements that were done...
\&quot;`nNumber of replacements: $PatternCount\&quot;
#renvoi
#
#&lt;c&gt;eci est du &lt;t&gt;&lt;e&gt;&lt;x&gt;&lt;t&gt;&lt;e&gt;
#Number of replacements: 6
[/code:1]

[edit]
On peut aussi ne pas préciser le type du délégué :
[code:1] #crée un délégué, ensuite PowerShell ce débrouille avec
$delegate={
# Return a replacement for the matching string...
...
}[/code:1]

On peut également combiner des délégués
[code:1]$PatternCount=0
#crée un délégué du type précisé
$delegateCount= [System.Text.RegularExpressions.MatchEvaluator] {
Write-Debug \&quot;Call Delegate `$delegateCount\&quot;
$global:«»PatternCount++
}
$delegateToLower= [System.Text.RegularExpressions.MatchEvaluator] {
Write-Debug \&quot;Call Delegate `$delegateToLower\&quot;
# Return a replacement for the matching string...
# Renvoi le paramètre en minuscule &lt; paramètre &gt;
\&quot;&lt;$($args[0].ToString().ToLower())&gt;\&quot;
# and count the number of replacements...
}

$Delegues=[System.Delegate]::Combine($delegateCount,$delegateToLower)
# on recherche toutes les occurences d'une lettre en majuscule
$re = [regex] \&quot;[A-Z]\&quot;
# now transform some text...

\&quot;Ceci est du TEXTE\&quot; |
ForEach-object { $re.Replace($_, $Delegues) }
# And display the number of replacements that were done...
\&quot;`nNumber of replacements: $PatternCount\&quot;
#renvoi
#
#&lt;c&gt;eci est du &lt;t&gt;&lt;e&gt;&lt;x&gt;&lt;t&gt;&lt;e&gt;
#Number of replacements: 6[/code:1]<br><br>Message édité par: Laurent Dardenne, à: 20/07/10 21:25

Tutoriels PowerShell

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

Plus d'informations
il y a 8 ans 7 mois - il y a 3 ans 9 mois #20703 par Laurent Dardenne
La version 4 et 5 facilite l'appel des méthodes génériques :
[System.Array]::TrueForAll
# OverloadDefinitions
# -------------------
# static bool TrueForAll[T](T[] array, System.Predicate[T] match)


[Int[]]$T=1..10
#le tableau peut ne pas être typé, 
#mais le contenu correspondre au type attendu, ici [int] 
$T2=1..11

#Doit être typé, sion une exception est levée
[System.Predicate[System.Int32]]$sb={param($item); $item -le 10}

[System.Array]::TrueForAll($T,$sb)
#True
[System.Array]::TrueForAll($T2,$sb)
#false

Tutoriels PowerShell
Dernière édition: il y a 3 ans 9 mois par Laurent Dardenne. Raison: Balise code

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

Plus d'informations
il y a 7 ans 1 mois - il y a 3 ans 9 mois #23099 par Laurent Dardenne
Allez, on remet une pièce pour un tour de manège :-)
Source
Class MyClass {
      
    [int] Add47([int] $value) {    
      return 47 + $value
    }
            
    [int] DoStuff([System.Collections.Generic.IEnumerable[int]] $collection) {
        # ugly
        $func = $this.GetType().GetMethod(\&quot;Add47\&quot;«»).CreateDelegate([Func[int,int]], $this)
        return [Linq.Enumerable]::«»Sum($collection, $func)
    }
}

#Il est nécessaire de typer la collection
[int[]]$t=@(1,2,3)

$o=[myclass]::new()
$o.Add47(10)
#57

$o.DoStuff($t)
#147 -&gt;48+49+50

Tutoriels PowerShell
Dernière édition: il y a 3 ans 9 mois par Laurent Dardenne. Raison: Balise code

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

Plus d'informations
il y a 5 ans 11 mois - il y a 3 ans 9 mois #25262 par Laurent Dardenne
La première preview de la version 6.1 intègre une évolution de conversion d'un PSMethod vers un délégué.
Les méthodes des objets dotNet, sauf les membres personnalisés (ETS), sont vue par Powershell comme des PSMethod.
Un délégué est un 'pointeur' typé vers une méthode, il est donc possible désormais d'utiliser un PSMethod en tant que délégué.

Avec la v5.1 l'exemple suivant :
class M {
    static [int] DoubleStrLen([string] $value) { return 2 * $value.Length }
    
    static [long] AggregateString([string[]] $values, [func[string, int]] $selector) {
        [long] $res = 0
        foreach($s in $values){
            $res += $selector.Invoke($s)
        }
        return $res
    }        
}

[M]::AggregateString((gci).Name, [M]::DoubleStrLen)
#1532
provoque une exception de conversion
Cannot convert argument \&quot;selector\&quot;, with value: \&quot;static int DoubleStrLen(string value)\&quot;, for \&quot;AggregateString\&quot; to 
type \&quot;System.Func`2[System.String,System.Int32]\&quot;: 
   \&quot;Cannot convert the \&quot;static int DoubleStrLen(string value)\&quot; value of type
   \&quot;System.Management.Automation.PSMethod\&quot; to type \&quot;System.Func`2[System.String,System.Int32]\&quot;.\&quot;
Le cast \&quot;[func[string, int]] $selector \&quot; référence le type Func&lt;T,TResult&gt; qui est un délégué.
Il encapsule une méthode qui a un seul paramètre, de type T, et qui retourne une valeur du type spécifié par le paramètre TResult.

Pour simplifier, le cast suivant n'est pas possible avec la v5.1 :
[func[string, int]] $selector=[M]::DoubleStrLen
L'appel du délégué se fait via sa méthode Invoke() :
$selector.Invoke
# 
# OverloadDefinitions
# -------------------
int Invoke(string arg)
On retrouve la signature de [M]::DoubleStrLen, on passe une chaîne et on récupère un résultat de type Int.
Un exemple d'appel :
$s='123'
$selector.Invoke($s)
#6
Ceci fonctionne également avec une méthode d'instance :
class M {
    [int] DoubleStrLen([string] $value) { return 2 * $value.Length }
    
    [long] AggregateString([string[]] $values, [func[string, int]] $selector) {
        [long] $res = 0
        foreach($s in $values){
            $res += $selector.Invoke($s)
        }
        return $res
    }        
}

$Object=[M]::new()
$Object.AggregateString((gci).Name, $Object.DoubleStrLen) 
Puisqu'un délégué est similaire à un pointeur de fonction, une adresse, on peut utiliser n'importe quelle méthode à partir du moment où la signature est identique :
class A {
     [int] DoubleStrLen([string] $value) { return 2 * $value.Length }
}

$Object2=[A]::new()
$Object.AggregateString((gci).Name, $Object2.DoubleStrLen) 
Un délégué, à la différence d'un pointeur de fonction, peut appeler plusieurs méthodes(multicast).

On paramètre des traitements dans une seule variable, le code d'appel ne change pas, seule le contenu de la variable change.
Ainsi on peut ultérieurement ajouter des traitements sans modifier le code appelant.
class Calculatrice {
    hidden [int] $Total=0
    [void] Plus([int] $value) {  $this.Total += $value; Write-Warning \&quot;Plus: $($this.Total)\&quot;}
    [void] Moins([int] $value) {$this.Total -= $value; Write-Warning \&quot;Moins : $($this.Total)\&quot;}
    [Void] Multiplie([int] $value) { $this.Total *= $value;Write-Warning \&quot;Multiplie: $($this.Total)\&quot;}
    [int] Calculer([int]$Number, [Action[int]] $Delegate) {
            $Delegate.Invoke($Number)
            return $this.Total
          } 
}
$O=[Calculatrice]::new()
#Action : méthode ayant un seul paramètre et ne retournant aucune valeur.
[Action[int]] $M1=$O.Plus 
[Action[int]] $M2=$O.Moins
$Delegates=[System.Delegate]::Combine($M1,$M2)
$O.Calculer(10,$Delegates)

[Action[int]] $M1=$O.Plus 
[Action[int]] $M2=$O.Multiplie
[Action[int]] $M3=$O.Moins
$Delegates=[System.Delegate]::Combine($M1,$M2,$M3)
$O.Calculer(10,$Delegates)
On peut également utiliser des scriptblocks au lieu de méthode d'objet sous réserve de les caster :
$delegates=[System.Delegate]::Combine($M1,$M2,[Action[int]]{param([int]$i) $this.Total /= $value; Write-Warning \&quot;/: $($this.Total)\&quot;})
Enfin il est possible de supprimer une entrée d'un délégué multicast:
$New=[System.Delegate]::Remove($Delegates,$M2)
$O.Calculer(10,$New)

Dans le cas d'un multicast où chaque délégué renvoi une valeur, on doit l'appeler de cette manière :
foreach ( $Delegate in $Delegates.GetInvocationList())
{
    $ret = $Delegate.Invoke($S)
    \&quot;ret :$ret\&quot;
}
Sinon seul le résultat du dernier délégué appelé est retourné.


Note :
Avec la version 6.0.2, le type du membre 'DoubleStrLen' est PSMethod:
[M]::DoubleStrLen.GetType().ToString()
#System.Management.Automation.PSMethod
Avec la preview le type du membre 'DoubleStrLen' est PSMethod<T>, un générique donc :
[M]::DoubleStrLen.gettype().ToString()
#System.Management.Automation.PSMethod`1[System.Management.Automation.MethodGroup`1[System.Func`2[System.String,System.Int32]]]
On retrouve dans le nom du type le type du délégué :
[func[string, int]] $selector=[M]::«»DoubleStrLen
$selector.gettype().ToString()
#System.Func`2[System.String,System.Int32]
Ce délégué est un des délégués génériques prédéfinis dans le framework dotNet.
Je suppose que le code recherche et mémorise le type du délégué identique à la signature de la méthode [M]::DoubleStrLen.
Ce qui permet en lisant le type d'un PSMethod de déterminer s'il existe une conversion de PSMethod vers un délégué.
Message édité par: Laurent Dardenne, à: 5/04/18 11:28

Tutoriels PowerShell
Dernière édition: il y a 3 ans 9 mois par Laurent Dardenne. Raison: Balise code

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

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