mardi 20 mars 2012

Retourner plusieurs valeurs depuis une fonction - retourner un @HashTable

Introduction
Ce n'est pas la première fois que j'essaye de retourner un HashTable ou un Array dans une fonction.
Et à chaque fois, je n'arrive pas à retrouver le contenu et les méthodes de l'objet.

Je dois avouer être resté circonspect face à ce problème en me demandant pourquoi PowerShell modifiait mon typage au point de ne pas pouvoir récupérer un HashTable via une fonction.

La réponse réside dans le fait qu'une fonction sait retourner plusieurs résultats!
Et que ces résultats peuvent être compilés dans un Array!

Retourner plusieurs résultats dans une fonction
Voici un petit bout de code issu du blog de Matrin Zugec.
La fonction retourne simplement plusieurs valeurs.
Function Get-MultiValue () {
  [hashtable]$Return = @{}
  $Return.Success = $True
  $Return.PercentComplete = 100
  $Return.ReturnTime = $(Get-Date)
  $Return.Username = "Martin Zugec"
  Return $Return
}

L'appel et la réutilisation des valeurs se font comme suit:
$Var = Get-MultiValue
If ($Var.Success) {
  $Var.UserName
  $Var.ReturnTime
}

Super comme truc!

Retourner plusieurs résultats, un comportement par défaut des fonctions
C'est en parcourant ce code et en essayant de le comprendre (en relation avec cet article de Stack-Overflow) que je comprends que le comportement par défaut d'une fonction est de retourner plusieurs valeurs.
Ainsi donc, lorsque qu'une commande affiche quelque-chose à l'écran (par exemple Write-Output) ou que la fonction évalue un objet (par son appel direct, ce qui provoque généralement son affichage), ces derniers éléments sont considérés comme des résultats de la fonction. Ces résultats accompagnerons aussi la valeur retournée avec la commande return.

Ca a l'air compliqué comme ça mais si vous rencontrez des problèmes, pensez simplement à tester la longueur de la variable et a éventuellement itérer le table qu'elle pourrait contenir contient.

En exemple pratique, voici la fonction loadShrWsArticles qui charge le contenu d'un fichier de donnée dans un HashTable.
Cette fonction est censée retourner le HashTable. La HashTable stocke une liste d'objets créés à l'aide de la  fonction CreateShrWsArticleDic (cette dernière crée des dictionnaire d'objet).

L'intérêt réside surtout dans l'utilisation de la fonction (décrite plus bas)

function CreateShrWsArticleDic{
 return @{ "artCode" = ""; "artEan" = ""; "artLabel" = ""; "CBStatus"="" } 
    # Source = File WSys/OutData/WSys-Articles-shrink.dat 
    #    26070-10      -> artCode
    #    8712734085995 -> artEan
    #    4 POLES MALES -> artLabel
    #    0             -> CB Status code
    #    OK            -> CB Status (as text)
    
}

# ------------------------------
#   Load the WSys-Articles-shrink.dat
# ------------------------------
# Returns a dictionnary of ShrWsArticles objects
#
function loadShrWsArticles {
    $sFilename = "C:\Dev\GGTools\WSys\OutData\WSys-Articles-shrink.dat"
    write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status "Openning file" -percentcomplete 0
    Write-Output $("Loading "+$sfilename)
    $hashArticles = @{} # HashTable to store the objects
    $f = Get-Content -Encoding String $sFilename
    # $f[0] -> SourceFile : xxx
    # $f[1] -> CreationDate : yyyyMMdd hh-mm-ss
    $iMax = $f.Length
    $iPos = 2
    while( $iPos -lt $iMax ) {
        # Write Status
     if ( ($iPos % 25) -eq 0 ) {
            write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status ("Progression :"+$iPos+"/"+$iMax ) -percentcomplete $([Math]::Round($iPos/$iMax*100))
     }        
     $a = CreateShrWsArticleDic
     $a.artCode = $f[$iPos]
     $a.artEan = $f[$iPos+1] 
     $a.artLabel = $f[$iPos+2]
        $a.CBStatus = $f[$iPos+3]
        # $a.CBStatusText = $f[$iPos+4]
        
     # Next record 
     $iPos = $iPos + 5
     
     # Add to Hash Table (used simplified key)
        [string]$key = $a.artCode 
        $key = $key.Replace( " ", "" ).Replace( ",", "." ).ToUpper() 
        
     if( $hashArticles.ContainsKey( $key ) ){
      Write-Host "  loadShrWsArticles - hashArticles " $a.artCode " already registered"      
     }
     else {
      $hashArticles.Add( $key, $a )
     }
    }
    write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status "Loaded!" -completed
    Write-Output $("   " +$hashArticles.Count +" items loaded")
    
    return $hashArticles
}

En faisant l'appel
$test = loadShrWsArticles
Je m'attendais à pouvoir retrouver un des éléments de mon dictionnaire.
La ligne $test.Item("B1061403-50").artCode devait logiquement fournir un résultat.
Et pourtant, je reçois obstinément l'erreur:
L'appel de la méthode a échoué parce que [System.Object[]] ne contient pas de méthode nommée « Item ».
Au niveau de ligne : 1 Caractère : 11
+ $test.Item <<<< ("B1061403-50").artCode
    + CategoryInfo          : InvalidOperation: (Item:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

Les choses deviennent claires lorsque l'on comprends que la fonction a retourné un tableau!
Mon HashTable est un des éléments de ce tableau.
> $test.Length
retourne 3, il y a 3 éléments dans mon résultat (en vérifiant chacun d'eux, je retrouve mon HashTable)

> $test[0]
Loading C:\Dev\GGTools\WSys\OutData\WSys-Articles-shrink.dat

> $test[1]
   1916 items loaded

> $test[2]
 Name                           Value                                                                                                                                                                                   
----                           -----                                                                                                                                                                                   
BF632/10A-100                  {artCode, artLabel, artEan, CBStatus}                                                                                                                                                   
MWP6-10                        {artCode, artLabel, artEan, CBStatus}                                                                                                                                                   
1000AROUGE                     {artCode, artLabel, artEan, CBStatus}   
...
...


J'ai retrouvé mon HashTable à la deuxième position du tableau!
Notez que les instructions Write-Output exécutés dans la fonction  font également partie du résultat de la fonction.Je vais enfin pouvoir retrouver mes objets :-) YES!!!

> $monHash = $test[2]
> $monHash.Item("B1061403-50").artCode

 B1061403-50

Cette fois-ci, plus d'erreur :-)

Aucun commentaire: