web-dev-qa-db-fra.com

Les structures sont-elles «pass-by-value»?

J'ai récemment essayé de créer une propriété pour un Vector2 champ, juste pour réaliser que cela ne fonctionne pas comme prévu.

public Vector2 Position { get; set; }

cela m'empêche de changer les valeurs de ses membres (X & Y)

En recherchant des informations à ce sujet, j'ai lu que la création d'une propriété à un Vector2 struct renvoie uniquement une copie de l'objet d'origine et non une référence.

En tant que développeur Java, cela m'embrouille.

Quand les objets en C # sont-ils passés par valeur et quand sont-ils passés par référence?
Tous les objets struct sont-ils passés par valeur?

34
Acidic

Il est important de réaliser que tout en C # est passé par valeur , sauf si vous spécifiez ref ou out dans la signature.

Ce qui différencie les types de valeur (et donc structs) des types de référence, c'est qu'un type de valeur est accessible directement, tandis qu'un type de référence est accessible via sa référence. Si vous passez un type de référence dans une méthode, sa référence, pas la valeur elle-même, est passée par valeur.

Pour illustrer cela, imaginez que nous avons une classePointClass et une structPointStruct, définies de façon analogue (en omettant les détails non pertinents):

struct PointStruct { public int x, y; }

class PointClass { public int x, y; }

Et nous avons une méthode SomeMethod qui prend ces deux types par valeur:

static void ExampleMethod(PointClass apc, PointStruct aps) { … }

Si nous créons maintenant deux objets et appelons la méthode:

var pc = new PointClass(1, 1);
var ps = new PointStruct(1, 1);

ExampleMethod(pc, ps);

… Nous pouvons visualiser cela avec le schéma suivant:

diagram

Puisque pc est une référence, elle ne contient pas la valeur en elle-même; il fait plutôt référence à une valeur (sans nom) ailleurs dans la mémoire. Ceci est visualisé par la bordure en pointillés et la flèche.

Mais: pour pc et ps, la variable réelle est copiée lors de l'appel de la méthode.

Que se passe-t-il si ExampleMethod réattribue les variables d'argument en interne? Allons vérifier:

static void ExampleMethod(PointClass apc, PointStruct aps); {
    apc = new PointClass(2, 2);
    aps = new PointStruct(2, 2);
}

Sortie de pc et ps après avoir appelé la méthode:

pc: {x: 1, y: 1}
ps: {x: 1, y: 1}

ExampleMethod a modifié une copie des valeurs et les valeurs d'origine ne sont pas affectées.

C'est, fondamentalement, ce que signifie "passer par la valeur".

Il existe toujours une différence entre les types de référence et de valeur, et cela entre en jeu lorsque vous modifiez membres de la valeur, pas la variable elle-même. C'est la partie qui déclenche les gens lorsqu'ils sont confrontés au fait que les types de référence sont passés par valeur. Considérons un ExampleMethod différent.

static void ExampleMethod(PointClass apc, PointStruct aps) {
    apc.x = 2;
    aps.x = 2;
}

Nous observons maintenant le résultat suivant après avoir appelé la méthode:

pc: {x: 2, y: 1}
ps: {x: 1, y: 1}

→ L'objet de référence a été modifié, contrairement à l'objet de valeur. Le diagramme ci-dessus montre pourquoi: pour l'objet de référence, même si pc a été copié, la valeur réelle que la référence pc et apc reste identique, et nous pouvons la modifier via apc. Quant à ps, nous avons copié la valeur réelle elle-même dans aps; ExampleMethod ne peut pas toucher la valeur d'origine.

75
Konrad Rudolph

Un struct est un type de valeur, il est donc toujours transmis en tant que valeur.

Une valeur peut être soit un type de référence (objet), soit un type de valeur (struct). Ce qui se passe est toujours une valeur; pour un type de référence, vous lui transmettez la valeur de la référence, pour un type de valeur, vous passez la valeur elle-même.

Le terme par référence est utilisé lorsque vous utilisez les mots clés ref ou out pour passer un paramètre. Ensuite, vous transmettez une référence à la variable qui contient la valeur au lieu de transmettre la valeur. Normalement, un paramètre est toujours transmis par valeur.

22
Guffa

Avant-propos: C # tout en étant géré a toujours les idiomes de mémoire de base créés par C. La mémoire peut être raisonnablement considérée comme un tableau géant où l'index dans le tableau est étiqueté "adresse de mémoire". Un pointeur est un index numérique de ce tableau alias une mémoire adresse. Les valeurs de ce tableau peuvent être des données ou un pointeur vers une autre adresse mémoire. Un const pointer est une valeur stockée dans ce tableau à un indice qui ne peut pas changer. Une adresse mémoire existe intrinsèquement et ne peut jamais changer, cependant la valeur qui se trouve à cette adresse peut toujours changer si elle est pas const.

Passer par classe

Un type de classe/référence (qui inclut une chaîne) est transmis par une référence de pointeur const. La mutation affectera toutes les utilisations de cette instance. Vous ne pouvez pas modifier l'adresse de l'objet. Si vous tentez de modifier l'adresse avec une affectation ou new, vous créerez en fait une variable locale qui partage le même nom que le paramètre dans la portée actuelle.

Passer par copie

Les primitives/ValueTypes/structs (les chaînes ne sont pas non plus même si elles se présentent comme des faux) sont complètement copiées lorsqu'elles sont renvoyées d'une méthode, d'une propriété ou reçues en tant que paramètre. La mutation d'une structure ne sera jamais partagée. Si une structure contient un membre de classe, ce qui est copié est la référence du pointeur. Cet objet membre serait modifiable.

Les primitives ne sont jamais mutables. Vous ne pouvez pas muter de 1 à 2, vous pouvez muter l'adresse mémoire qui se réfère actuellement à 1 à l'adresse mémoire de 2 dans la portée actuelle.

Passer par vrai référence

Nécessite l'utilisation de mots clés out ou ref.

ref vous permettra de modifier le pointeur d'un objet new ou d'affecter un objet existant. ref vous permettra également de passer une primitive/ValueType/struct par son pointeur mémoire pour éviter de copier l'objet. Cela vous permettrait également de remplacer le pointeur par une primitive différente si vous lui affectiez.

out est sémantiquement identique à ref avec une différence mineure. ref les paramètres doivent être initialisés où out les paramètres peuvent être non initialisés car ils doivent être initialisés dans la méthode qui accepte le paramètre. Ceci est généralement montré dans les méthodes TryParse et élimine le besoin d'avoir int x = 0; int.TryParse("5", out x) lorsque la valeur initiale de x ne servirait à rien.

5
Chris Marisic

Les types de données .NET sont divisés en types valeur et référence. Les types de valeurs incluent int, byte et structs. Les types de référence incluent string et les classes.

les structures sont appropriées au lieu de classes lorsqu'elles contiennent juste un ou deux types de valeurs (bien que même là, vous pouvez avoir des effets secondaires involontaires).

Ainsi, les structures sont en effet transmises par valeur et ce que vous voyez est attendu.

5
Jonathan Wood

Juste pour illustrer les différents effets du passage de struct vs classe à travers des méthodes:

(note: testé dans LINQPad 4 )

Exemple

/// via http://stackoverflow.com/questions/9251608/are-structs-pass-by-value
void Main() {

    // just confirming with delegates
    Action<StructTransport> delegateTryUpdateValueType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    Action<ClassTransport> delegateTryUpdateRefType = (t) => {
        t.i += 10;
        t.s += ", appended delegate";
    };

    // initial state
    var structObject = new StructTransport { i = 1, s = "one" };
    var classObject = new ClassTransport { i = 2, s = "two" };

    structObject.Dump("Value Type - initial");
    classObject.Dump("Reference Type - initial");

    // make some changes!
    delegateTryUpdateValueType(structObject);
    delegateTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after delegate");
    classObject.Dump("Reference Type - after delegate");

    methodTryUpdateValueType(structObject);
    methodTryUpdateRefType(classObject);

    structObject.Dump("Value Type - after method");
    classObject.Dump("Reference Type - after method");

    methodTryUpdateValueTypePassByRef(ref structObject);
    methodTryUpdateRefTypePassByRef(ref classObject);

    structObject.Dump("Value Type - after method passed-by-ref");
    classObject.Dump("Reference Type - after method passed-by-ref");
}

// the constructs
public struct StructTransport {
    public int i { get; set; }
    public string s { get; set; }
}
public class ClassTransport {
    public int i { get; set; }
    public string s { get; set; }
}

// the methods
public void methodTryUpdateValueType(StructTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateRefType(ClassTransport t) {
    t.i += 100;
    t.s += ", appended method";
}

public void methodTryUpdateValueTypePassByRef(ref StructTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

public void methodTryUpdateRefTypePassByRef(ref ClassTransport t) {
    t.i += 1000;
    t.s += ", appended method by ref";
}

Résultats

(depuis le vidage LINQPad)

Value Type - initial 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - initial 
ClassTransport 
UserQuery+ClassTransport 
i 2 
s two 

//------------------------

Value Type - after delegate 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after delegate 
ClassTransport 
UserQuery+ClassTransport 
i 12 
s two, appended delegate 

//------------------------

Value Type - after method 
StructTransport 
UserQuery+StructTransport 
i 1 
s one 


Reference Type - after method 
ClassTransport 
UserQuery+ClassTransport 
i 112 
s two, appended delegate, appended method 

//------------------------

Value Type - after method passed-by-ref 
StructTransport 
UserQuery+StructTransport 
i 1001 
s one, appended method by ref 


Reference Type - after method passed-by-ref 
ClassTransport 
UserQuery+ClassTransport 
i 1112 
s two, appended delegate, appended method, appended method by ref 
3
drzaus

Le problème est que le getter retourne une copie de Vector2. Si vous changez les coordonnées comme ceci

obj.Position.X = x;
obj.Position.Y = y;

Vous ne modifiez que les coordonnées de cette copie éphémère.

Faites cela à la place

obj.Position = new Vector2(x, y);

Cela n'a rien à voir avec la valeur ou la référence. Value2 est un type de valeur et get renvoie cette valeur. Si le vecteur avait un type de référence (classe), get retournerait cette référence. return renvoie les valeurs par valeur. Si nous avons un type de référence, ces références sont les valeurs et sont renvoyées.

2

Les objets sont passés par référence et les structures par valeur. Mais notez que vous avez les modificateurs "out" et "ref" sur les arguments.

Vous pouvez donc passer une structure par référence comme ceci:

public void fooBar( ref Vector2 position )
{
}
1
Dan Byström

Oui, les structures héritent de ValueType et sont transmises par valeur. Cela est également vrai pour les types primitifs - int, double, bool, etc. (mais pas string). Les chaînes, les tableaux et toutes les classes sont des types de référence et sont transmis par référence.

Si vous voulez passer une structure par ref, en utilisant le mot clé ref:

public void MyMethod (ref Vector2 position)

qui passera la struct by-ref, et vous permettra de modifier ses membres.

1
Avner Shahar-Kashtan

Chaque emplacement de stockage d'un type de structure contient tous les champs, privés et publics, de cette structure. Passer un paramètre d'un type de structure implique d'allouer de l'espace pour cette structure sur la pile et de copier tous les champs de la structure vers la pile.

En ce qui concerne l'utilisation des structures stockées dans une collection, l'utilisation de structures mutables avec les collections existantes nécessite généralement de lire une structure sur une copie locale, de muter cette copie et de la stocker. En supposant que MyList est un List<Point>, et on veut ajouter une variable locale z à MyList[3].X:

 Point temp = MaListe [3]; 
 Temp.X + = Z; 
 MaListe [3] = temp; 

Ceci est légèrement ennuyeux, mais est souvent plus propre, plus sûr et plus efficace que l'utilisation de structures immuables, bien plus efficace que les objets de classe immuables et bien plus sûr que l'utilisation d'objets de classe mutables. J'aimerais vraiment voir le support du compilateur pour une meilleure façon pour les collections d'exposer des éléments de type valeur. Il existe des moyens d'écrire du code efficace pour gérer une telle exposition avec une bonne sémantique (par exemple, un objet de collection peut réagir lorsque les éléments sont mis à jour, sans exiger que ces éléments aient un lien vers la collection) mais le code se lit horriblement. L'ajout de la prise en charge du compilateur d'une manière conceptuellement similaire aux fermetures permettrait également à un code efficace d'être lisible.

Notez que contrairement à ce que certains prétendent, une structure est fondamentalement différent à partir d'un objet de type classe, mais pour chaque type de structure, il existe un type correspondant, parfois appelé "structure en boîte", qui dérive de Object (voir la spécification CLI (Common Language Infrastructure) , sections 8.2.4 et 8.9.7). Bien que le compilateur convertisse implicitement n'importe quelle structure en son type encadré correspondant lorsque cela est nécessaire pour le passer au code qui attend une référence à un objet de type classe, permettra aux références aux structures encadrées d'avoir leur contenu copié en structures réelles, et autorisera parfois code pour travailler directement avec des structures en boîte, les structures en boîte se comportent comme des objets de classe, car c'est ce qu'elles sont.

0
supercat