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

Plus d'informations
il y a 15 ans 11 mois #3540 par Laurent Dardenne
Voici un script très utile, trouvé sur un site japonais, qui offre de nouvelles possibilités sous PowerShell, à savoir l'usage de délégué , en gros des pointeurs de méthodes.
Bruce Payette avait déjà proposé un script sur le sujet mais aux possibilités limités, la gestion des génériques notamment. Celui-ci ne me semble pas poser de problèmes particuliers.

Dans les exemples on peut utiliser le multi-threading et les fonctions paramètrées (foncteurs) de dotNET.
Le code de la function :
[code:1]
Function New-Delegate(
[Type]$DelegateType,
[ScriptBlock]$Script,
[Management.Automation.Runspaces.Runspace]$Runspace = [Management.Automation.Runspaces.Runspace]::«»DefaultRunspace
)
{
#Obtient, après sa création à la volée, un délégué .NET générique ou pas
#
#from flamework.net/archives/3
# $delegate = New-Delegate Threading.ThreadStart {Start-Sleep 3;
# [Media.SystemSounds]::Beep.Play()}
# $thread = New-Object Threading.Thread ($delegate)
# $thread.Start()

# $list = New-Object Collections.Generic.List``1[System.Int32]
# $list.AddRange([int[]](1..3))
# $list
# $delegate = New-Delegate Comparison``1[System.Int32] {$args[1] - $args[0]}
# $list.Sort($delegate)
# $list

if ($args -eq '-?' -or $DelegateType -eq $null -or $Script -eq $null) {
$myCommand = [IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand)
Write-Host \" Explanation: Delegate is drawn up from script block.\"
Write-Host (\"Use: {0} [-DelegateType] Type [-Script] ScriptBlock [-Runspace] Runspace\" -f $myCommand)
Write-Host (\"Example of use: {0} Threading.ThreadStart {{Start-Sleep 3;[Media.SystemSounds]::Beep.Play()}}\" -f $myCommand)
exit 1
}
$targetType = [Collections.ArrayList]
$invokeMethod = $DelegateType.GetMethod('Invoke')
$returnType = $invokeMethod.ReturnType
if ($returnType -eq [void]) {$returnType = $null}
[Type[]]$parameterList = @($invokeMethod.GetParameters() | foreach {$_.ParameterType})
$method = New-Object Reflection.Emit.DynamicMethod('Handler', $returnType, (@($targetType) + $parameterList), $targetType)
$ilGenerator = $method.GetILGenerator()
$Emit = {
process {
if ($_ -eq $null) {
} elseif (@($_).Length -eq 1) {
$ilGenerator.Emit([System.Reflection.Emit.OpCodes]::$_)
} else {
if ($_[0] -eq 'MarkLabel') {
$ilGenerator.MarkLabel($_[1])
} else {
$ilGenerator.Emit([System.Reflection.Emit.OpCodes]::«»($_[0]), $_[1])
}
}
}
}
[void]$ilGenerator.DeclareLocal([Object[]])
(
'Ldarg_0',
'Ldc_I4_0',
('Callvirt', $targetType.GetMethod('get_Item', ([int]))),
('Call', [Management.Automation.Runspaces.Runspace].GetMethod('set_DefaultRunspace', [Management.Automation.Runspaces.Runspace])),
('Ldc_I4', $parameterList.Length),
('Newarr', [Object]),
'Stloc_0'
) | &$Emit
for ($i = 0; $i -lt $parameterList.Length; $i++)
{
(
'Ldloc_0',
('Ldc_I4', $i),
('Ldarg', ($i + 1))
) | &$Emit
if ($parameterList[$i].IsValueType) {
(('Box', $parameterList[$i]), $null) | &$Emit
}
('Stelem_Ref', $null) | &$Emit
}
(
'Ldarg_0',
'Ldc_I4_1',
('Callvirt', $targetType.GetMethod('get_Item', ([int]))),
'Ldloc_0',
('Callvirt', ([ScriptBlock].GetMethod('InvokeReturnAsIs', [object[]])))
) | &$Emit
if ($returnType -eq $null) {
'Pop' | &$Emit
} else {
(
('Castclass', [Management.Automation.PSObject]),
('Callvirt', [Management.Automation.PSObject].GetMethod('get_BaseObject', [Type]::EmptyTypes))
) | &$Emit
if ($returnType.IsValueType) {
(('Unbox_Any', $returnType), $null) | &$Emit
}
}
('Ret', $null) | &$Emit
if ($Runspace -eq $null) {
$Runspace = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$Runspace.Open()
}
$target = New-Object $targetType
$target.AddRange(($Runspace, $Script))
$method.CreateDelegate($DelegateType, $target)
}
[/code:1]
Retrouver les délégués des assemblies chargés dans PowerShell :
[code:1]
#recherche les délégués déclarés dans les assemblies chargés
$Delegates=[AppDomain]::Currentdomain.GetAssemblies()|% {$_.GetExportedTypes()|? {$_.IsPublic -and $_.BaseType -match \"Delegate\"} |% {$_} }
#mémorise le path de l'assembly
$Delegates|Add-member -name \"Location\" -membertype Scriptproperty -value { [System.IO.Path]::GetFilenameWithoutExtension( $this.Assembly.Location)}
#Regroupement par Assembly
$Delegates|ft @{Label=\"Signature\";Expression={$_.GetMethod(\"Invoke\"«»)|% {\"{0} {1}\" -F $_.ReturnType,($_.Declaringtype-replace \"(.*)\.(\w+)$\", '$2')} } } -GroupBy Location
#Signature
$Delegates|% {$_.GetMethod(\"Invoke\"«»)|% {\"{0} {1}\" -F $_.ReturnType,$_.Declaringtype} }
[/code:1]
Un autre exemple, la méthode Array.TrueForAll peut être codé sous PowerShel de cette manière :
[code:1]
$R=1..10
$R|Where {$All++;$_ -le 10 }|% -begin {$All,$Count=0,0} -process {[void]$count++} –end {$Count -eq $All}
#True

$R=1..11
$R|Where {$All++;$_ -le 10 }|% -begin {$All,$Count=0,0} -process {[void]$count++} –end {$Count -eq $All}
#False
[/code:1]
On peut noter que le cmdlet Where-Object a le même comportement que le délégué Predicate...
Maintenant le même traitement via un délégué :
[code:1]
#public static bool TrueForAll<T>( T[] array, Predicate<T> match)
[Int32[]]$T=1..10
[Int32[]]$T2=1..11
#Crée un délégué générique
$deleg = New-Delegate System.Predicate``1[System.Int32] {$Args[0] -le 10}
#Obtient la signature de la méthode [System.Array]::TrueForAll utilisant un délégué générique
[System.Reflection.MethodInfo] $Methode = [System.Array].GetMethod(\"TrueForAll\"«»)
#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,$Deleg))
#True
[/code:1]
Une fois la méthode et le délégué construit on peut l'appeler en modifiant uniquement la variable contenant le tableau concerné
[code:1]
$MethodeGenerique.Invoke($null,@($T2,$Deleg))
#False
[/code:1]
C'est un peu corsé comme code mais vous retrouverez sous PowerShell V2 cette notion avec les événements qui seront d'un accés plus facile je vous rassure.
La prochaine étape étant de combiner plusieurs délégués ;-)<br><br>Message édité par: Laurent Dardenne, à: 30/12/08 11:33

Tutoriels PowerShell

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

Plus d'informations
il y a 15 ans 11 mois #3549 par Laurent Dardenne
Un exemple pour créer et combiner des délégués personnels, il faut avoir quelques notions du langage C# :
[code:1]
#Création de délégués
$code = @'
using System;
using System.Runtime.InteropServices;

namespace PSDynamic
{
//Déclare des délégués, semblables à des signature de méthodes
public delegate int Add(int x, int y);
public delegate void Affiche(string s);
public delegate string Concatene(string s1, string s2);


}
'@
#http://blogs.msdn.com/powershell/archive/2006/04/25/583236.aspx
compile-CSharp $code
$Delegate = New-Delegate PSDynamic.Add {$args[0] + $args[1]}
#Exécute le code du délégué
$Result=$Delegate.Invoke(4,5)
$Result

$DlgtWrite = New-Delegate PSDynamic.Affiche {Write-Host $args[0]}
$DlgtWrite.Invoke(\&quot;Test d'affichage\&quot;«»)

$DlgtConcat= New-Delegate PSDynamic.Concatene {$args[0]+$args[1]}
Write-host ($DlgtConcat.Invoke(\&quot;Addition de \&quot;, \&quot;chaîne de caractères\&quot;«»))

[System.Delegate]|gm -static
$DlgtWrite2 = New-Delegate PSDynamic.Affiche {Write-Host $args[0] -Fore Green}
#Un délégué peut contenir plusieurs méthodes
$Delegues=[System.Delegate]::Combine($DlgtWrite,$DlgtWrite2)
#ou
#$DlgtWrite=[System.Delegate]::Combine($DlgtWrite,$DlgtWrite2)
#Exécute séquentiellment toutes méthodes du délégués
$Delegues.Invoke(\&quot;Test d'affichage\&quot;«»)

$DlgtModif = New-Delegate PSDynamic.Affiche { $args[0] -replace \&quot;Test\&quot;,\&quot;Couleur\&quot;}
$DlgtWrite3 = New-Delegate PSDynamic.Affiche {Write-Host $args[0] -Fore Yellow}

$Delegues=[System.Delegate]::Combine($Delegues,$DlgtModif)
$Delegues=[System.Delegate]::Combine($Delegues,$DlgtWrite3)
$Delegues.Invoke(\&quot;Test d'affichage\&quot;«»)
#Le résultat du délégué $DlgtModif n'est pas pris en compte
#puisque sa signature C# indique qu'il n'y a pas de valeur de retour

$Delegues=$Null
$Delegate = $Null
$DlgtWrite=$Null
$DlgtConcat=$Null
$DlgtWrite2 =$Null
$DlgtModif = $Null
$DlgtWrite3 = $Null

#Modification de la signature de la méthode Affiche
#On crée un nouvel assembly
$code = @'
using System;
using System.Runtime.InteropServices;

namespace PSDynamic2
{
public delegate string Affiche(string s);
}
'@
compile-CSharp $code

#Avec la nouvelle signature le délégué doit renvoyer une valeur
$DlgtWrite = New-Delegate PSDynamic2.Affiche {Write-Host $args[0];$args[0]}
$DlgtWrite.Invoke(\&quot;Test d'affichage\&quot;«»)

$DlgtWrite2 = New-Delegate PSDynamic2.Affiche {Write-Host $args[0] -Fore Green;$args[0]}

$Delegues=[System.Delegate]::Combine($DlgtWrite,$DlgtWrite2)
#ou
#$DlgtWrite=[System.Delegate]::Combine($DlgtWrite,$DlgtWrite2)
$Delegues.Invoke(\&quot;Test d'affichage\&quot;«»)

$DlgtModif = New-Delegate PSDynamic2.Affiche { $args[0] -replace \&quot;Test\&quot;,\&quot;Couleur\&quot;}
$DlgtWrite3 = New-Delegate PSDynamic2.Affiche {Write-Host $args[0] -Fore Yellow;$args[0]}
$DlgtModif2 = New-Delegate PSDynamic2.Affiche { $args[0] -replace \&quot;Couleur\&quot;,\&quot;Delegué\&quot;}

$Delegues=[System.Delegate]::Combine($Delegues,$DlgtModif)
$Delegues=[System.Delegate]::Combine($Delegues,$DlgtWrite3)
$Delegues=[System.Delegate]::Combine($Delegues,$DlgtModif2)
$Delegues=[System.Delegate]::Combine($Delegues,$DlgtWrite)
$Delegues.Invoke(\&quot;Test d'affichage\&quot;«»)

#Affiche la liste des délégués combiné dans la variable $Delegues
$Delegues.GetInvocationList()
#Tiens compte du résultat renvoyer par chaque délégué
$Delegues.GetInvocationList()|% -begin {$Res=\&quot;Test d'affichage\&quot;} -process{$Res=$_.Invoke($Res)}
$Res

$Delegues.GetInvocationList()|fl
$Delegues.GetInvocationList().Count
#Supprime un délégué et renvoi le résulat dans un second délégué
$D2=[System.Delegate]::Remove($Delegues,$DlgtWrite3)
$D2.GetInvocationList()|fl
$D2.GetInvocationList().Count
$Delegues.GetInvocationList().Count
[/code:1]

Tutoriels PowerShell

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

Plus d'informations
il y a 15 ans 11 mois #3558 par Laurent Dardenne
Les délégués peuvent nous être utile pour trier une structure de données \&quot;personnalisée\&quot;.
Par exemple trier un tableau de Hashtable sur un nom de clé, dans le code suivant on trie sur le nom.
[code:1]
function GetMethodEx([Type]$Type,[String]$Signature)
#Tip : Josh Einstein
{ $Type.GetMethods('Public, Static') | ?{ $_.ToString() -eq $Signature } }

[System.Collections.HashTable[]] $T=@(
@{Nom=\&quot;Pierre\&quot;;Age=40},
@{Nom=\&quot;Paul\&quot;;Age=50},
@{Nom=\&quot;Jacque\&quot;;Age=30},
@{Nom=\&quot;Pail\&quot;;Age=20},
@{Nom=\&quot;Pierrot\&quot;;Age=5},
@{Nom=\&quot;Alex\&quot;;Age=78}
)

$SBCompareHT=
{ #Implémente le délégué : System.Comparison&lt;T&gt;( T x, T y)
Write-Debug \&quot;Call Comparison\&quot;
$x=$Args[0].Nom #Le contenu du champ Nom peut être égale à $null, compareTo gére ce cas
Write-Debug $($X)
$y=$Args[1].Nom
Write-Debug $($Y)
if ($x -eq $null)
{
if ($y -eq $null)
{
#If x is null and y is null, they're equal.
Write-debug \&quot;Comparison return 0\&quot;
return 0
}
else
{
#If x is null and y is not null, y is greater.
Write-debug \&quot;Comparison return -1\&quot;
return -1
}
}
else
{
#If x is not null...
if ($y -eq $null)
{
#...and y is null, x is greater.
Write-debug \&quot;Comparison return 1\&quot;
return 1
}
else
{
Write-debug \&quot;Call CompareTo\&quot;
$x.CompareTo($y)
}
}
}

#New-Delegate from flamework.net/archives/3
$deleg = New-Delegate System.Comparison``1[System.Collections.Hashtable] $SBCompareHT
[System.Reflection.MethodInfo] $Methode = GetMethodEx Array 'void sort[t](t[], system.comparison`1[t])'
$MethodeGenerique = $Methode.MakeGenericMethod([System.Collections.Hashtable])
$T
$MethodeGenerique.Invoke($null,@($T,$Deleg))
$T
[/code:1]
Si la valeur d'un champ est à $null cette situation est gérée.

A noter que l'appel suivant ne fonctionne pas sur une méthode générique :
[code:1]
[System.Reflection.MethodInfo] $Methode = [System.Array].GetMethod(\&quot;Sort\&quot;,@([System.Array],[System.Comparison``1]) )
[/code:1]
C'est pour cette raison qu'on utilise la méthode GetMethodEx qui recherche une signature dans la liste des méthodes d'un type.
Attention si on modifie le scriptblock on doit recréer le délégué le reste du code restera valide.

Prochaine étape : paramètrer le champ à interrroger lors de l'appel à CompareTo ;-)

Tutoriels PowerShell

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

Plus d'informations
il y a 15 ans 11 mois #3559 par Arnaud Petitjean
Salut Laurent,

Whahh faut s'accrocher pour suivre quand on n'est pas développeur :lol:. Pas mal en tout cas mais je t'avoue que pour l'instant je n'en ai pas eu besoin... Faut tout de même que je t'avoue que je n'avais encore jamais entendu parlé de délégués, si ce n'est aux dernières élections américaines :lol:

Utile en tout cas pour ceux qui ne peuvent attendre la sortie définitive de PowerShell v2.

Merci,

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 15 ans 11 mois #3560 par Laurent Dardenne
Bonjour Arnaud,
Arnaud écrit:

Whahh faut s'accrocher pour suivre quand on n'est pas développeur :lol:.

Je confirme, surtout pour la fonction new-delegate qui est assez complexe. En même temps si on encapsule ces traitements on masque leurs complexité, c'est ce que fait un cmdlet :)
Arnaud écrit:

je t'avoue que pour l'instant je n'en ai pas eu besoin...

Je n'ai pas donné d'exemples permettant de bien appréhender ce qu'ils peuvent apporter, c'est une autre maniére de coder.
Par exemple pour le tri cela permet de s'affranchir de l'usage du C# comme ici .
Arnaud écrit:

Utile en tout cas pour ceux qui ne peuvent attendre la sortie définitive de PowerShell v2.

Je dois installer la ctp3 mais je ne pense pas que la V2 permettra la création simplifié de délégué, j'espére me tromper.
Pour le moment le seul qu'elle gére est le gestionnaire d'événements :
[code:1]
public delegate void EventHandler (
Object sender,
EventArgs
)
[/code:1]
Avec la combinaison de délégués, on pourra donc exécuter plusieurs traitement lors de la réception d'un événement.
voir aussi cet exemple .
Mais c'est un autre le sujet, dans un prochain tutoriel peut être...

Tutoriels PowerShell

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

Plus d'informations
il y a 15 ans 11 mois #3580 par Laurent Dardenne
Laurent Dardenne écrit:

Prochaine étape : paramètrer le champ à interrroger lors de l'appel à CompareTo

Voici le code :
[code:1]
function GetMethodEx([Type]$Type,[String]$Signature)
#Tip : Josh Einstein
{ $Type.GetMethods('Public, Static') | ?{ $_.ToString() -eq $Signature } }

[System.Collections.HashTable[]] $T=@(
#Suppose la construction des hashtables identiques
@{Nom=\&quot;Pierre\&quot;;Age=40},
@{Nom=\&quot;Paul\&quot;;Age=50},
@{Nom=\&quot;Jacque\&quot;;Age=30},
#@{Nom=\&quot;Pail\&quot;;Age=\&quot;Ok\&quot;},
@{Nom=\&quot;Pail\&quot;;Age=15}
@{Nom=\&quot;Pierrot\&quot;;Age=$null},
@{Nom=$Null;Age=78})

$SBCompareHT=
{ #Implémente le délégué : System.Comparison&lt;T&gt;( T x, T y)
Write-Debug \&quot;Comparaison du type des valeurs sur le champ : $SortProperty \&quot;
$x=$Args[0].$SortProperty #Le contenu du champ peut être égale à $null, compareTo gére ce cas
$y=$Args[1].$SortProperty
Write-Debug $(\&quot;x= {0}\&quot; -F $X)
Write-Debug $(\&quot;y= {0}\&quot; -F $y)

if ($x -eq $null)
{
if ($y -eq $null)
{
#If x is null and y is null, they're equal.
Write-debug \&quot;Comparaison return 0\&quot;
return 0
}
else
{
#If x is null and y is not null, y is greater.
Write-debug \&quot;Comparaison return -1\&quot;
return -1
}
}
else
{
#If x is not null...
if ($y -eq $null)
{
#...and y is null, x is greater.
Write-debug \&quot;Comparaison return 1\&quot;
return 1
}
else
{
# ...and y is not null, compare
$xType=$x.GetType()
if ($xType -ne $y.GetType())
{ Write-debug $xType.FullName
Write-debug $y.GetType().FullName
$Msg=\&quot;Les types de données à comparer ne sont pas identiques.\&quot;
Write-debug $Msg
#La méthode appelante renvoi cette exception dans le champ InnerException
# Elle n'est pas affichée dans le host mais bien dans celle de l'appel du délégué
Throw $Msg }

Write-debug \&quot;Test de l'implémentation de l'interface System.IComparable\&quot;
$IsXComparable=$x -is [System.IComparable]
if (!$IsXComparable)
{
Write-debug \&quot;Erreur\&quot;
Throw \&quot;Le type $($xType.FullName) n'implémente pas l'interface System.IComparable.\&quot;
}
Write-debug \&quot;CompareTo\&quot;
$res=$x.CompareTo($y)
Write-debug $res
return $res
}
}
}

Function Get-DelegateComparison([ScriptBlock] $SBCompare,[String] $Type)
{ #On doit recréer le délégué si on modifie le scriptblock
#New-Delegate from flamework.net/archives/3
$Delegate=New-Delegate System.Comparison``1[$Type] $SBCompare
[System.Reflection.MethodInfo] $Methode = GetMethodEx Array 'void sort[t](t[], system.comparison`1[t])'
$MethodeGenerique = $Methode.MakeGenericMethod([Type] $Type)

#Crée un objet encapsulant la gestion du délégué
#Et l'envoi dans le pipe
new-object System.Management.Automation.PsObject |`
Add-Member -Name Delegate `
-MemberType NoteProperty `
-value $Delegate -Passthru|`
Add-Member -Name GenericMethod `
-MemberType NoteProperty `
-value $MethodeGenerique -Passthru|`
Add-Member -Name Execute `
-MemberType ScriptMethod `
-value {
#Execute($Tableau-Hashtable,\&quot;NomDeClé\&quot;«»)
$Tab=$Args[0]
$SortProperty=$Args[1]
if (($Tab[0].Keys|Where {$_ -eq $SortProperty}) -eq $null)
{ Throw (\&quot;La propriété `\&quot;{0}`\&quot; n'existe pas\&quot; -f $SortProperty) }
$this.GenericMethod.Invoke($null,@($Tab,$this.Delegate))
} -Passthru
}


$SortComparison=Get-DelegateComparison $SBCompareHT \&quot;System.Collections.Hashtable\&quot;
Write-host \&quot;Origine\&quot;
$T
$SortComparison.Execute($T,\&quot;Nom\&quot;«»)
Write-host \&quot;Trié par Nom\&quot;
$T
$SortComparison.Execute($T,\&quot;Age\&quot;«»)
Write-host \&quot;Trié par Age\&quot;
$T
Write-host \&quot;Trie avec une propriété inconnue\&quot;
$SortComparison.Execute($T,\&quot;Inconnue\&quot;«»)
#http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx
Rver
[/code:1]
Il reste un soucis, l'usage avec une classe externe ne fonctionne pas :
[code:1]
#Ok
[System.Type] $m=\&quot;System.Comparison``1[System.Int32]\&quot;
[System.Reflection.Assembly]::LoadFile((Join-Path $PWd \&quot;MyStruct.dll\&quot;«»))
#NOk
[System.Type] $m=\&quot;System.Comparison``1[MyStruct.Personne]\&quot;
[/code:1]
Ce qui limite un peu l'usage :dry:

Tutoriels PowerShell

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

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