En C#, une variable peut soit contenir une valeur, soit une référence vers un objet. Il est très important de bien comprendre la différence entre les 2.

Utilisation

Au niveau de l’utilisation standard, ces deux types sont strictement identiques. Dans les 2 cas, on peut faire

maVariable = maValeur;
Console.WriteLine(maVariable.ToString());

Explication plus précise

Une variable par valeur contient directement un objet. Au contraire, une variable par référence contient une référence vers cet objet. Pour ceux qui connaissent des langages bas niveau, ça correspond aux pointeurs (à la différence près qu’il n’est pas possible de faire d’opération sur les références comme sur les pointeurs, et que la syntaxe de l’utilisation est identique à celle des variables par valeur).

Les paramètres des méthodes

Lorsqu’on insère un objet en tant que paramètre d’une méthode, on en fait une copie. Ce qui veut donc dire que si l’on modifie une variable dans une méthode, on modifie la copie, et non l’original.
Cependant, si, au lieu de faire une copie de l’objet, on fait une copie de la référence, c’est l’objet original que l’on modifie dans une méthode.

Reconnaître l’un de l’autre

On accède toujours à une classe par référence, et à une structure par valeur.
Structures souvent utilisées : int, short, long, double, decimal, float, char, bool, byte, DateTime, Color…
Classes souvent utilisées : string, Form, List, SqlConnection…
Une autre petite différence, une classe peut avoir la valeur null, contrairement à une structure

La mémoire

La gestion mémoire est totalement différente entre les 2.
Dès qu’une structure est déclarée, sa mémoire est allouée. Et la mémoire est vidée à la fin du bloc dans lequel la variable a été déclarée.

public void maMethode1()
{
	int a; // La mémoire est automatiquement allouée
}

public void maMethode2()
{ // Bloc 1
	int a;

	{ // Bloc 2
		int b;
	} // Fermeture du bloc 2
	// la variable b n'existe plus puisqu'elle a été déclarée dans le bloc 2, et que celui-ci est fermé.
} // Fermeture du bloc 1

Concernant les classes, la mémoire n’est allouée que lors de l’utilisation de l’instruction new QuelqueChose() (sauf pour les string). Celle-ci est libérée automatiquement par le GarbageCollector lorsque le programme ne possède plus aucune référence à cet objet, c’est à dire, quand vous n’avez gardé aucun moyen d’y accéder.

public void maMethode()
{
	maClasse a; // mémoire non allouée
	a = new maClasse(); // allocation de la mémoire
	a = null; // on perd la référence à l'objet créé ci-dessus.
	// Il peut être supprimé à tout moment.

	new maClasse2(); // On crée un objet, mais on ne garde aucune référence à celui-ci.
	// Il peut donc être supprimé à tout moment par le GarbageCollector
}

Le mot clé « ref »

Si l’on souhaite modifier une structure dans une autre méthode, il existe le mot-clé ref. Il suffit d’insérer ce mot-clé avant le type de l’argument dans le prototype de la méthode et de l’indiquer aussi avant la variable dans l’appel à la méthode :

public void maMethode1(ref int maVariable)
{
	maVariable = 10;
}

public void maMehtode2()
{
	int maVariable = 50;
	maMethode1(ref maVariable);
}

Résumé

struct maStructure
{
	int valeur;
}

class maClasse
{
	int valeur;
}

class Program
{
	public static void main()
	{
		maStructure s1; // Mémoire allouée
		maStructure s2; // Mémoire allouée
		maClasse c1; // Mémoire non allouée
		maClasse c2; // Mémoire non allouée

		c1 = new maClasse(); // Mémoire allouée

		s1.valeur = 10;
		s2.valeur = 10;

		c1.valeur = 20;
		// c2.valeur = 20; va générer une exception puisque c2 ne référence rien

		c2 = new maClasse();
		c2.valeur = 20;

		if(s1 == s2)
			Console.WriteLine("s1 et s2 ont la même valeur");
		else
			Console.WriteLine("s1 et s2 ont des valeurs différentes");
		// Affichera s1 et s2 ont la même valeur

		if(c1 == c2)
			Console.WriteLine("c1 et c2 référencent le même objet");
		else
			Console.WriteLine("c1 et c2 référencent des objets différents");
		// Affichera c1 et c2 référencent des objets différents

		s2 = s1; // On copie la valeur de s1 dans s2
		c2 = c1; // On copie la référence de c1 dans c2
		// On n'a pas gardé de référence vers l'objet anciennement référencé par c2
		// Il peut donc être supprimé à tout moment
		if(c1 == c2)
			Console.WriteLine("c1 et c2 référencent le même objet");
		else
			Console.WriteLine("c1 et c2 référencent des objets différents");
		// Affichera c1 et c2 référencent le même objet

		s1.valeur = 1;
		s2.valeur = 2;
		c1.valeur = 3;
		c2.valeur = 4; // Ceci modifie aussi c1, puisque c'est une référence vers le même objet

		Console.WriteLine(s1.valeur); // 1
		Console.WriteLine(s2.valeur); // 2
		Console.WriteLine(c1.valeur); // 4
		Console.WriteLine(c2.valeur); // 4
	}
}