Les Reférences Locales

Avec le nouveau framework, il est possible de modifier directement le champ d’une Struct sans passer par une copie de celle-ci.

Exemple

Point est notre Struct composé de 3 champs entiers X,Y, et Z.


struct Point
{
    public int X;
    public int Y;
    public int Z;

    public override string ToString()
    {
         return $"({X}, {Y}, {Z})";
    }
}

Exemple avec un tableau de Points

Dans l’ancienne version du framework, si on voulait modifier un des Points du tableau il fallait :

Soit pointer sur l’élément à modifier, et modifier les champs de cet éléments (en loccurence les champs de la struct Point)


static void TestArrayStruct()
{
    Point[] myArrayOfPoints = new[] { new Point(), new Point(), new Point() };

    myArrayOfPoints[1].X = 1;
    myArrayOfPoints[1].Y = 1;
    myArrayOfPoints[1].Z = 1;

    Console.WriteLine(string.Join(",", myArrayOfPoints));
}

Soit créer un nouvel élement (Point dans notre cas), lui assigner l’élement du tableau à modifier, modifier les champs de ce nouvel élément, copier le nouveau Point à l’élément à modifier…bref c’est assez lourd…

static void TestArrayStructLocalVar()
{
    Point[] myArrayOfPoints = new[] { new Point(), new Point(), new Point() };
    Point p = myArrayOfPoints[1];
    p.X = 1;
    p.Y = 1;
    p.Z = 1;
    myArrayOfPoints[1] = p; //copy p to myArrayOfPoints
    Console.WriteLine(string.Join(",", myArrayOfPoints));
}

Aujourd’hui en applicant le mot clé ref à notre nouveau point ET à l’élément du tableau à modifier, on peut modifier directement le nouveau Point sans faire de recopie.

static void TestRefLocal()
{
    Point[] a = new[] { new Point(), new Point(), new Point() };
    ref Point p = ref a[1]; //enable Struct member to be modified 
                            //without doing any copy
    p.X = 1;
    p.Y = 1;
    p.Z = 1;
    Console.WriteLine(string.Join(",", a));
}

Sortie

(0, 0, 0),(1, 1, 1),(0, 0, 0)

Les Closures C# avant le framework .Net 4.5 Partie3 (Comprendre ce qu’il se passe en coulisse)

Demystification

On sait que de manière générale les types valeurs sont stockées sur la pile, et que les types références sont sur le tas (le pointer sur ces références étant sur la pile)

A la sortie de la fonction ReturnFunc, les types valeurs (dont fait parti localvariable) sont dites ‘unwind’: elles disparaissent de la pile.

A la création de la fonction anonyme (delegate) qui fait une closure sur la variable ‘libre’ localvariable : le compilateur détecte la closure.

Non seulement il stock le code du delegate sur le tas mais il y alloue aussi un slot mémoire contenant localvariable!

D’où le fait que localvariable persiste !


Note

Le garbage collector sait gérer la mémoire allouée sur le tas mais pas sur la pile.

Sur le tas, tant qu’une fonction pointe ou utilise une variable local, celle-ci n’est pas garbage collecté.

Si on tente de créer une Struct, et d’y implémenter une closure sur un champs de cette Struct : le compilateur C# nous interdit cette opération.

En effet, la closure pointerai sur un champs spécifique d’une Struct (Struct étant sur la pile) qui est amené à être unwind…

En revanche, si on créé une class, et qu’on y implémente une closure sur une variable local : le compilateur autorise cette opération car la class est copié sur le tas ainsi que ses variables locales (le garbage collector sait gérer les alloations mémoires).

struct myStruct
{
    public int i;
    public Func<int> fct()
    {
        Func<int> f = () => i;//<-Not authorized !
        return f;
    }
}

Simulons le traitement du compilateur

Tous ce passe comme si une nouvelle class (qu’on appellera ReturnFuncClosure) était créée.

Cette class contenant une variable local localvariable, et une fonction qui prends en paramètre un entier, et retourne un entier.


class ReturnFuncClosure
{
    public int localvariable = 1;

    public int Fct(int var1)
    {
        Console.WriteLine("Value of localvariable: " + localvariable);
        localvariable += 1;
        return var1 * localvariable;
    }
}

On appelle ensuite les membres de cette classe dans notre closure “fait à la main”

private static Func<int, int> ReturnFuncMadeManually()
{
    /*
    Instanciate ReturnFuncClosure. 
    'closure' is on the Heap. 
    A pointer to closure is on the Stack.
    */
    var closure = new ReturnFuncClosure();

    /*
    localvariable belonging to object closure is persisted on the heap
    */
    closure.localvariable = 1;

    /*
    myfunc pointer is on the stack.
    myfunc pointer target method Fct belonging to closure object.
    Hence Fct is persisted on the heap.
    */
    Func<int, int>myfunc = new Func<int, int>(closure.Fct);
    
    return myfunc;
}

Appel

static void Main(string[] args)
{
    var v = ReturnFuncMadeManually();

    Console.WriteLine("First call");
    Console.WriteLine("Result: "+v(2));
    Console.WriteLine("Second call");
    Console.WriteLine("Result: "+v(4));
}

Sortie

First call
Value of localvariable: 1
Result: 4
Second call
Value of localvariable: 2
Result: 12

 

Les Closures C# avant le framework .Net 4.5 Partie2 (Une solution de contournement)

Workarround

Sur le 1er exemple

Au lieu de transmettre la variables ‘libre’ à l’expression lambda (ou fonction anonyme), on créer une copie locale de cette variable libre qu’on transmet au lambda.

De cette manière, la fermeture sera à chaque fois effectuée autour de la variable de copie qui a la valeur actuelle du compteur au moment de l’exécution.

Cette valeur de copie locale persisite, elle ne sera pas modifiée ultérieurement par la boucle.


private static Func<int, int> ReturnFunc()
{
    var localvariable = 1;

    Func<int, int> myfunc = delegate (int var1)
    {
        //copylocalvariable is used inside of the delegate 
        var copylocalvariable = localvariable;
        Console.WriteLine("Value of copylocalvariable: " + copylocalvariable);
        copylocalvariable += 1;
        return var1 * copylocalvariable;
    };

    //When we return the myfunc, copylocalvariable is bound 
    //inside of EACH delegate
    return myfunc;
}

Plus tard, lors des différents appels à ReturnFunc , chacun utilisera sa propre fermeture autour de la copie copylocalvariable et aura donc sa propre valeur attendue.

 Sortie

First call
Value of copylocalvariable: 1
Result: 4
Second call
Value of copylocalvariable: 1
Result: 8

Sur le 2nd exemple

var listAction = new List<Action>();

for (int i=0; i<10; i++)
{
    var j = i;
    listAction.Add(() => Console.WriteLine(j));
}

foreach(Action item in listAction)
{
    //When we return the item, j is bound 
    //inside of EACH delegate composing the loop
    item();
}

Sortie

0
1
2
3
4
5
6
7
8
9

Les Closures C# avant le framework .Net 4.5 Partie1 (Un comportement plutôt curieux)

Définition d’une fermeture (closure en anglais) selon Wikipedia

En informatique, une fermeture est une fonction de première classe avec des variables libres liées dans l’environnement lexical

Une fonction de première classe… ? Variables libres liéés dans l’environnement lexical…??

Une fonction de première classe signifie simplement qu’il s’agit d’une fonction concideré par C# (ou autre langage) en tant que type de donnée de première classe…

En termes plus simples, cela signifie qu’on peu assigner une fonction à une variable.

Variable libre

C’est une variable référencée dans une fonction. En revanche, cette variable n’est pas un paramètre ni une variable locale à la fonction. Elle y est juste référencée et utilisée.

Exemples

Dans ReturnFunc() y est référencée la variable ‘libre’ localvariable.

localvariable n’est pas un paramètre ni une variable locale au delegate(fonction anonyme) qui l’utilise.

Son rôle dans le delegate : s’incrémenter et se multiplier avec une variable locale au delegate.


private static Func<int, int> ReturnFunc()
{
    var localvariable = 1;

    //'Free' localvariable is used inside of the delegate 
    Func<int, int> myfunc = delegate(int var1) 
    {
        Console.WriteLine("Value of localvariable: "+ localvariable);
        localvariable += 1;
        return var1 * localvariable;
    };

    //When we return the myfunc, localvariable is bound inside of the delegate
    return myfunc;
}

Nous faisons 2 appels successifs à ReturnFunc().

La sortie devrait être : 4 [(1+1)*2] puis 8 [(1+1)*4]


static void Main(string[] args)
{
    //First class function assigned to variable v
    var v = ReturnFunc();

    Console.WriteLine("First call");
    Console.WriteLine("Result: "+v(2));

    Console.WriteLine("Second call");
    Console.WriteLine("Result: "+v(4));

    Console.Read();
}

Sortie

First call
Value of localvariable: 1
Result: 4
Second call
Value of localvariable: 2
Result: 12

A chaque appel, on s’aperçoit que localvariable conserve sa valeure précédente avant de s’incrémenter !

Comportement étrange

Les variables locales ne sont-elles pas créées sur la pile? Ne partent-elles pas lorsque nous avons fini d’exécuter la méthode?

En d’autres termes, localvariable devrait être reinitialisée à chaque appel à ReturnFunc(), et donc valoir 1 !

Deduction

La variable libre localvariable vit maintenant avec la méthode renvoyé par ReturnFunc() : localvariable y est liée en dehors de sa portée d’origine.

Voilà ce qu’est une closure ! la variable libre localvariable est liée dans l’environnement lexical!

Un autre exemple

On créer une liste de fonction anonyme (‘Action’)

On utilise une boucle qui contient une variable libre ‘i’.

A chaque tour de boucle, on ajoute à la liste une fonction anonyme qui utilise la variable libre ‘i’

static void Main(string[] args)
{
    var listAction = new List<Action>;
		
    for(int i=0; i<10; i++)
    {
 	//'Free' i, is used inside of the delegate
	listAction.Add(() => Console.WriteLine(i));
    }

    foreach(Action item in listAction)
    {
	//When we return the item, i is bound inside of the delegate
	item();
    }

    Console.Read();
}

Sortie

Si nous n’étions pas avisé au sujet des closures, la sortie attendu serait 0 1 2 3 4 5 6 7 8 9

Mais nous savons que la variable libre ‘i’ est liée dans l’environnement lexical de la fonction anonyme ()=>console.WriteLine(i)

Nous observons le même phénomène.

10
10
10
10
10
10
10
10
10
10

En d’autres termes :

  • Dans la première boucle, la variable libre ‘i’ s’itère de 0 à 9,
  • La variable libre ‘i’ est l’utilisé dans l’expression lambda (la fonction anonyme). Chaque lambda obtient son accès à ‘i’ via la closure.
  • A la fin de cette première boucle, ‘i’ = 10 (10 car on utilise l’opérateur post-incrémentation: i++ : lors de la dernière itération de la boucle, on augmente le compteur à 10 et on vérifie si ‘i'<10 ce qui n’est plus le cas, donc sortie de boulce)
  • Ensuite, les ‘Action’ (ou fonctions anonymes ()=>Console.WriteLine(i)) s’executent : *elles ont toutes une closure sur la même variable libre ‘i’ qui vaut 10.
  • Chaque ‘Action’ (fonction anonymes) affiche donc sa valeur actuelle : 10 sur la console.

*Point important pour comprendre la solution de contournement évoquée en partie 2

Petit apperçu sur les tâches asynchrones avec C# 5.0

 

private static int TestChoice()
{
	Console.WriteLine("Type test 1,2,3 or 4: ");

	int testId = Convert.ToInt32(Console.ReadLine());

	return testId;
}

*********

private static async void Run(int testID)
{
	switch(testID) 
	{
		case 1:
			await RunIt();
			break;
		case 2:
			await RunItFaster();
			break;
		case 3:
			await RunItEvenFasterV1();
			break;
		case 4:
			await RunItEvenFasterV2();
			break;
	}
}

*********

private static async Task<string> RunIt()
{
    var start = DateTime.Now;

    Console.WriteLine("[{0}] START ", start);
    
    var tasktorun = new TasksToRun();

    await tasktorun.MonitorServer(2000);
    await tasktorun.SendMail(2000);
    await tasktorun.DownloadFile(2000);

    var end = DateTime.Now;

    Console.WriteLine("[{0}] END ", end);
    Console.WriteLine("Elapsed time : {0} ", end - start);

    return "Done";
}

*********

private static async Task<string> RunItFaster()
{
	var start = DateTime.Now;

	Console.WriteLine("[{0}] START ", start);

	var tasktorun = new TasksToRun();

	tasktorun.MonitorServer(2000);

	await tasktorun.SendMail(2000);
	await tasktorun.DownloadFile(2000);

	var end = DateTime.Now;

	Console.WriteLine("[{0}] END ", end);
	Console.WriteLine("Elapsed time : {0} ", end - start);

	return "Done";
}

*********

private static async Task<string> RunItEvenFasterV1()
{
	var start = DateTime.Now;

	Console.WriteLine("[{0}] START ", start);

	var tasktorun = new TasksToRun();

	var monitorServer = tasktorun.MonitorServer(2000);
	var emailToSend = tasktorun.SendMail(2000);
	var randomInt =  tasktorun.DownloadFile(2000);

	var all = Task.WhenAll(monitorServer, emailToSend, randomInt);

	await all;

	var end = DateTime.Now;

	Console.WriteLine("[{0}] END ", end);
	Console.WriteLine("Elapsed time : {0} ", end - start);

	return "Done";
}

*********

private static async Task<string> RunItEvenFasterV2()
{
	var start = DateTime.Now;

	Func<int, Task<string>> target = null;

	Console.WriteLine("[{0}] START ", start);

	var tasktorun = new TasksToRun();

	target = tasktorun.MonitorServer;
	target += tasktorun.SendMail;
	target += tasktorun.DownloadFile;

	var all = Task.WhenAll(target(2000));

	await all;

	var end = DateTime.Now;

	Console.WriteLine("[{0}] END ", end);
	Console.WriteLine("Elapsed time : {0} ", end - start);

	return "Done";
}

*********

public class TasksToRun
{
	public Task<string> MonitorServer(int ms)
	{
		Console.WriteLine("Enter MonitorServer()");

		return Task.Delay(ms).ContinueWith(_=>"Leave MonitorServer");
		
	}

	public async Task<string> SendMail(int ms)
	{
		Console.WriteLine("Enter SendMail()");

		return await Task.Delay(ms).ContinueWith(_ => "Leave SendMail");
	}

	public async Task<string> DownloadFile(int ms)
	{
		Console.WriteLine("Enter DownloadFile()");

		return await Task.Delay(ms).ContinueWith(_ => "Leave DownloadFile");
	}
}

La portée des variables et le “Hoisting”

VAR

Les variables typées var :

  1. ont une portée limitée seulement dans le cadre d’une fonction (ce n’est pas le cas dans les blocks if,for,foreach…)
  2. si la fonction a accès au context extérieur via une variable (“closure”), celle ci peut être modifiée
var a = 'World'
var a = function (){
        console.log('Hello')
        a = 'Buddy'
        var b = function (){
               console.log(a)
        }
        return b()
}
a()

Sortie

 
Hello
Buddy

Hoisting

Le Hoisting (ou hissage) élève la déclaration des variables tout en haut de l’execution de notre script.

En effet, Javascript n’est pas executé de la même manière qu’on l’écrit.

Dans l’exemple précédant, si on retourne b() avant de définir celle-ci une erreur apparait à l’execution :

 
var a = 'World' 
var a = function (){ 
        console.log('Hello') 
        a = 'Buddy' 
        return b() 
        var b = function (){ 
                console.log(a) 
        } 
} 
a() 

Sortie

TypeError: b is not a function

En effet Javascript remanie le code en déclarant les variables en haut de la fonction (c’est l’action de Hoisting) :

 
var a 
a = 'World' 
a = function (){ 
    var b //Declare b
    console.log('Hello') 
    a = 'Buddy' 
    return b() //Call b which is undefined at this step -> Error
    var b = function (){ 
            console.log(a) 
    } 
} 

En revanche, si on n’assigne pas nos fonctions à des variables, javascript Hoist (hisse) la définition de la fonction :

 
function a (){ 
    console.log('Hello') 
    var a = 'Buddy' 
    return b() 
    function b (){ 
        console.log(a) 
    } 
} 
a()

devient …

 
function a (){
    var a 
	function b (){ 
        console.log(a) 
    } 
    console.log('Hello') 
    a = 'Buddy' 
    return b()   
} 
a()

Le Destructuring

Le Destructuring ou déballage

En Javascript , le Destructing consiste à

  1. ‘déballer’ ou récupérer les élements d’un tableau ou bien les propriétés d’un objet
  2. Assigner unitairement ces éléments à des variables distincts

Exemple

function DestructureIt(fct){
    //variable a and b are assigned with split result
    [a,b] = fct('a').trim().split('|')
    console.log(a,b)
}

//Call
DestructureIt(a => a)

Sortie

a undefined

Note : quand le séparateur n’existe pas, split() renvoie undefined.

On peut encore utiliser le Destructuring afin d’assigner à des variables, les propriétés d’un objet :

var monObject = {nom:'Jimi', prenom:'Hendrix'}
var {nom,prenom} = monObject
console.log(nom, " ", prenom)
Note: A condition que ces variables portent le même nom que les propriétés qui les assignent.

Sortie

 Jimi Hendrix 

Un cas où le point virgule est important !

Le code Javascript permet une plus grande souplesse syntaxique et plus particulièrement, le developpeur peu s’aquitter de l’usage des fameux point virgule ‘;’ qui terminent chaque instructions.

Dans certains cas, le ‘;’ peu s’avérer obligatoire :


//Ce petit script affiche dans la console si 'n' est pair ou impair 
//et insére cette valeur dans le tableau associé.

let even = [], odd = []

let n = 246

console.log('n is %s', n % 2 === 0 ? 'even' : 'odd')

(n % 2 === 0 ? even : odd).push(n)

Sortie


TypeError: console.log(...) is not a function
at Object.&lt;anonymous&gt; (D:\MSO Consulting\Formations\Javascript\Docs\Samples\Server\server.js:39:1)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)

Bug

Bug à la compilation : console.log(…) is not a function.

La raison

Du fait de l’absent de ‘;’ à la fin de l’instruction console.log(…) Javascript interprète le code précédent comme l’appel d’une fonction imbriquée:

fonction()(param).push(element)

console.log('n is %s', n % 2 === 0 ? 'even' : 'odd')(n % 2 === 0 ? even : odd).push(n)

Dans ce cas précis, il est donc nécessaire d’ajouter un ‘;’ à la fin de l’instruction console.log (…)

Solution

console.log('n is %s', n % 2 === 0 ? 'even' : 'odd');
(n % 2 === 0 ? even : odd).push(n)