C# Language
Type de valeur vs type de référence
Recherche…
Syntaxe
- Passage par référence: public void Double (ref int numberToDouble) {}
Remarques
introduction
Types de valeur
Les types de valeur sont les plus simples des deux. Les types de valeur sont souvent utilisés pour représenter les données elles-mêmes. Un entier, un booléen ou un point dans un espace 3D sont tous des exemples de bons types de valeur.
Les types de valeur (structs) sont déclarés à l'aide du mot-clé struct. Voir la section syntaxe pour un exemple de comment déclarer une nouvelle structure.
De manière générale, nous avons 2 mots-clés utilisés pour déclarer les types de valeur:
- Structs
- Énumérations
Types de référence
Les types de référence sont légèrement plus complexes. Les types de référence sont des objets traditionnels au sens de la programmation orientée objet. Ainsi, ils prennent en charge l'héritage (et les avantages de celui-ci) et prennent également en charge les finaliseurs.
En C #, nous avons généralement ces types de référence:
- Des classes
- Les délégués
- Interfaces
Les nouveaux types de référence (classes) sont déclarés à l'aide du mot-clé class. Pour un exemple, voir la section sur la syntaxe pour savoir comment déclarer un nouveau type de référence.
Différences majeures
Les principales différences entre les types de référence et les types de valeur peuvent être vues ci-dessous.
Des types de valeur existent sur la pile, des types de référence existent sur le tas
C'est la différence souvent mentionnée entre les deux, mais en réalité, lorsque vous utilisez un type de valeur en C #, tel qu'un int, le programme utilisera cette variable pour faire directement référence à cette valeur. Si vous dites int mine = 0, alors la variable mine fait directement référence à 0, ce qui est efficace. Cependant, les types de référence contiennent en réalité (comme leur nom l'indique) une référence à l'objet sous-jacent, ce qui s'apparente aux pointeurs dans d'autres langages tels que C ++.
Vous pourriez ne pas remarquer les effets de ceci immédiatement, mais les effets sont là, sont puissants et subtils. Voir l'exemple sur la modification des types de référence ailleurs pour un exemple.
Cette différence est la principale raison des différences suivantes et mérite d’être connue.
Les types de valeur ne changent pas lorsque vous les modifiez dans une méthode, les types de référence le font
Lorsqu'un type de valeur est transmis dans une méthode en tant que paramètre, si la méthode modifie la valeur de quelque manière que ce soit, la valeur n'est pas modifiée. En revanche, le passage d'un type de référence à cette même méthode et sa modification modifieront l'objet sous-jacent. d'autres objets utilisant ce même objet auront l'objet nouvellement modifié plutôt que leur valeur d'origine.
Voir l'exemple des types de valeur vs types de référence dans les méthodes pour plus d'informations.
Et si je veux les changer?Transmettez-les simplement dans votre méthode en utilisant le mot-clé "ref" et vous transmettez ensuite cet objet par référence. Ce qui signifie que c'est le même objet en mémoire. Les modifications que vous apporterez seront donc respectées. Voir l'exemple sur le passage par référence pour un exemple.
Les types de valeur ne peuvent pas être null, les types de référence peuvent
Tout comme il est dit, vous pouvez attribuer la valeur NULL à un type de référence, ce qui signifie que la variable que vous avez affectée ne peut pas être associée à un objet réel. Dans le cas des types de valeur, cela n'est toutefois pas possible. Vous pouvez, cependant, utiliser Nullable, pour que votre type de valeur soit nullable, si cela est une exigence, mais si c'est quelque chose que vous envisagez, pensez fermement si une classe peut ne pas être la meilleure approche ici, si c'est la vôtre type.
Changer les valeurs ailleurs
public static void Main(string[] args)
{
var studentList = new List<Student>();
studentList.Add(new Student("Scott", "Nuke"));
studentList.Add(new Student("Vincent", "King"));
studentList.Add(new Student("Craig", "Bertt"));
// make a separate list to print out later
var printingList = studentList; // this is a new list object, but holding the same student objects inside it
// oops, we've noticed typos in the names, so we fix those
studentList[0].LastName = "Duke";
studentList[1].LastName = "Kong";
studentList[2].LastName = "Brett";
// okay, we now print the list
PrintPrintingList(printingList);
}
private static void PrintPrintingList(List<Student> students)
{
foreach (Student student in students)
{
Console.WriteLine(string.Format("{0} {1}", student.FirstName, student.LastName));
}
}
Vous remarquerez que même si la liste printingList a été créée avant les corrections apportées aux noms d'élèves après les fautes de frappe, la méthode PrintPrintingList affiche toujours les noms corrigés:
Scott Duke
Vincent Kong
Craig Brett
En effet, les deux listes contiennent une liste de références aux mêmes étudiants. SO modifier l'objet objet sous-jacent propage aux utilisations par l'une ou l'autre liste.
Voici à quoi ressemblerait la classe d'élèves.
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Student(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
En passant par référence
Si vous souhaitez que les types de valeurs et les types de référence dans les méthodes fonctionnent correctement, utilisez le mot-clé ref dans la signature de votre méthode pour le paramètre que vous souhaitez transmettre par référence, ainsi que lorsque vous appelez la méthode.
public static void Main(string[] args)
{
...
DoubleNumber(ref number); // calling code
Console.WriteLine(number); // outputs 8
...
}
public void DoubleNumber(ref int number)
{
number += number;
}
Faire ces modifications rendrait le numéro mis à jour comme prévu, ce qui signifie que la sortie de la console pour le numéro serait 8.
Passage par référence en utilisant le mot-clé ref.
De la documentation :
En C #, les arguments peuvent être transmis aux paramètres par valeur ou par référence. Le passage par référence permet aux membres de la fonction, aux méthodes, aux propriétés, aux indexeurs, aux opérateurs et aux constructeurs de modifier la valeur des paramètres et de conserver cette modification dans l'environnement appelant. Pour transmettre un paramètre par référence, utilisez le mot-clé
ref
ouout
.
La différence entre ref
et out
est que out
signifie que le paramètre passé doit être assigné avant que la fonction ends.in change les paramètres passés avec ref
peuvent être modifiés ou laissés inchangés.
using System;
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a = 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a = 6;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
a = 7;
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Sortie :
Inside Main - Before Callee: a = 20
Inside Callee a : 5
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 6
Inside Main - After CalleeRef: a = 6
Inside Main - Before CalleeOut: a = 6
Inside CalleeOut a : 7
Inside Main - After CalleeOut: a = 7
Affectation
var a = new List<int>();
var b = a;
a.Add(5);
Console.WriteLine(a.Count); // prints 1
Console.WriteLine(b.Count); // prints 1 as well
L'affectation à une variable d'une List<int>
ne crée pas une copie de la List<int>
. Au lieu de cela, il copie la référence à la List<int>
. Nous appelons les types qui se comportent de cette façon les types de référence .
Différence avec les paramètres de méthode réf et out
Il existe deux manières possibles de passer un type de valeur par référence: ref
et out
. La différence est que, en le passant avec ref
la valeur doit être initialisée mais pas lors du passage avec out
. L'utilisation de out
garantit que la variable a une valeur après l'appel de la méthode:
public void ByRef(ref int value)
{
Console.WriteLine(nameof(ByRef) + value);
value += 4;
Console.WriteLine(nameof(ByRef) + value);
}
public void ByOut(out int value)
{
value += 4 // CS0269: Use of unassigned out parameter `value'
Console.WriteLine(nameof(ByOut) + value); // CS0269: Use of unassigned out parameter `value'
value = 4;
Console.WriteLine(nameof(ByOut) + value);
}
public void TestOut()
{
int outValue1;
ByOut(out outValue1); // prints 4
int outValue2 = 10; // does not make any sense for out
ByOut(out outValue2); // prints 4
}
public void TestRef()
{
int refValue1;
ByRef(ref refValue1); // S0165 Use of unassigned local variable 'refValue'
int refValue2 = 0;
ByRef(ref refValue2); // prints 0 and 4
int refValue3 = 10;
ByRef(ref refValue3); // prints 10 and 14
}
Le hic est que l'aide out
le paramètre must
être initialisé avant de quitter la méthode, donc la méthode suivante est possible ref
mais pas out
:
public void EmtyRef(bool condition, ref int value)
{
if (condition)
{
value += 10;
}
}
public void EmtyOut(bool condition, out int value)
{
if (condition)
{
value = 10;
}
} //CS0177: The out parameter 'value' must be assigned before control leaves the current method
En effet, si la condition
ne tient pas, la value
ne sera pas attribuée.
paramètres de réf vs out
Code
class Program
{
static void Main(string[] args)
{
int a = 20;
Console.WriteLine("Inside Main - Before Callee: a = {0}", a);
Callee(a);
Console.WriteLine("Inside Main - After Callee: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeRef: a = {0}", a);
CalleeRef(ref a);
Console.WriteLine("Inside Main - After CalleeRef: a = {0}", a);
Console.WriteLine();
Console.WriteLine("Inside Main - Before CalleeOut: a = {0}", a);
CalleeOut(out a);
Console.WriteLine("Inside Main - After CalleeOut: a = {0}", a);
Console.ReadLine();
}
static void Callee(int a)
{
a += 5;
Console.WriteLine("Inside Callee a : {0}", a);
}
static void CalleeRef(ref int a)
{
a += 10;
Console.WriteLine("Inside CalleeRef a : {0}", a);
}
static void CalleeOut(out int a)
{
// can't use a+=15 since for this method 'a' is not intialized only declared in the method declaration
a = 25; //has to be initialized
Console.WriteLine("Inside CalleeOut a : {0}", a);
}
}
Sortie
Inside Main - Before Callee: a = 20
Inside Callee a : 25
Inside Main - After Callee: a = 20
Inside Main - Before CalleeRef: a = 20
Inside CalleeRef a : 30
Inside Main - After CalleeRef: a = 30
Inside Main - Before CalleeOut: a = 30
Inside CalleeOut a : 25
Inside Main - After CalleeOut: a = 25