333 votes

Passage d'objets par référence ou par valeur en C#

En C#, j'ai toujours pensé que les variables non primitives étaient transmises par référence et les valeurs primitives transmises par valeur.

Ainsi, lorsqu'on passe à une méthode un objet non primitif, tout ce qui est fait à l'objet dans la méthode affecte l'objet passé. (C# 101 trucs)

Cependant, j'ai remarqué que lorsque je passe un objet System.Drawing.Image, cela ne semble pas être le cas ? Si je passe un objet system.drawing.image à une autre méthode, et que je charge une image sur cet objet, puis que je laisse cette méthode sortir de son champ d'application et que je reviens à la méthode appelante, cette image n'est pas chargée sur l'objet original ?

Pourquoi ?

29 votes

Toutes les variables sont transmises par valeur par défaut en C#. Vous passez la valeur de la référence dans le cas des types de référence.

0 votes

0 votes

Comme aucun code n'a été donné, la question n'est pas vraiment claire. Peut-être que le PO voulait dire image.Load(filename) ou peut-être qu'ils voulaient dire image = Image.Load(filename) donde image est le paramètre de la fonction.

680voto

Jon Skeet Points 692016

Objets ne sont pas passés du tout. Par défaut, l'argument est évalué et son valeur est passé, par valeur, comme valeur initiale du paramètre de la méthode que vous appelez. Le point important est que la valeur est une référence pour les types de référence - un moyen d'accéder à un objet (ou null). Les modifications apportées à cet objet seront visibles pour l'appelant. Cependant, si vous changez la valeur du paramètre pour vous référer à un objet différent, vous pourrez pas être visible lorsque vous utilisez la méthode de pass by value, qui est la méthode par défaut de l'option todos types.

Si vous voulez utiliser la méthode pass-by-reference, il faut debe utiliser out o ref si le type de paramètre est un type de valeur ou un type de référence. Dans ce cas, la variable elle-même est effectivement transmise par référence, de sorte que le paramètre utilise le même emplacement de stockage que l'argument - et les modifications du paramètre lui-même sont vues par l'appelant.

Donc :

public void Foo(Image image)
{
    // This change won't be seen by the caller: it's changing the value
    // of the parameter.
    image = Image.FromStream(...);
}

public void Foo(ref Image image)
{
    // This change *will* be seen by the caller: it's changing the value
    // of the parameter, but we're using pass by reference
    image = Image.FromStream(...);
}

public void Foo(Image image)
{
    // This change *will* be seen by the caller: it's changing the data
    // within the object that the parameter value refers to.
    image.RotateFlip(...);
}

J'ai un article qui donne beaucoup plus de détails sur ce sujet. . Fondamentalement, "passer par référence" ne signifie pas ce que vous pensez qu'il signifie.

2 votes

Tu as raison, je n'avais pas vu ça ! Je chargeais image = Image.FromFile(..) et cela remplaçait la variable image et ne changeait pas l'objet ! :) bien sûr.

0 votes

En termes simples, je peux dire que si nous changeons les propriétés ou appelons une fonction de l'objet paramètre, cela sera affecté mais si nous lançons la variable paramètre, elle sera une référence à un nouvel emplacement/objet. paramX.Caption = "asdasdasd" ; //travaillera paramX = nouvel objet() ; //déconnectera l'objet de l'appelant et ce paramX sera une référence à un nouvel emplacement.

1 votes

@Adeem : Pas tout à fait - il n'y a pas d'"objet paramètre", il y a l'objet auquel la valeur du paramètre fait référence. Je pense que vous avez la bonne idée, mais la terminologie est importante :)

114voto

OlegI Points 389

Beaucoup de bonnes réponses ont été ajoutées. Je veux toujours contribuer, peut-être que cela clarifiera un peu plus les choses.

Lorsque vous passez une instance en tant qu'argument à la méthode, celle-ci transmet l'élément copy de l'instance. Maintenant, si l'instance que vous passez est un value type (réside dans le stack ), vous passez le copie de cette valeur, donc si vous la modifiez, cela ne sera pas reflété dans l'appelant. Si l'instance est un type de référence, vous passez l'attribut copie de la référence (qui réside à nouveau dans le stack ) à l'objet. Vous avez donc deux références au même objet. Les deux peuvent modifier l'objet. Mais si dans le corps de la méthode, vous instanciez un nouvel objet, votre copie de la référence ne fera plus référence à l'objet original, elle fera référence au nouvel objet que vous venez de créer. Vous finirez donc par avoir 2 références et 2 objets.

25voto

Vitaliy Points 1286

Un autre exemple de code pour illustrer ceci :

void Main()
{

    int k = 0;
    TestPlain(k);
    Console.WriteLine("TestPlain:" + k);

    TestRef(ref k);
    Console.WriteLine("TestRef:" + k);

    string t = "test";

    TestObjPlain(t);
    Console.WriteLine("TestObjPlain:" +t);

    TestObjRef(ref t);
    Console.WriteLine("TestObjRef:" + t);
}

public static void TestPlain(int i)
{
    i = 5;
}

public static void TestRef(ref int i)
{
    i = 5;
}

public static void TestObjPlain(string s)
{
    s = "TestObjPlain";
}

public static void TestObjRef(ref string s)
{
    s = "TestObjRef";
}

Et le résultat :

TestPlain:0

TestRef:5

TestObjPlain:test

TestObjRef:TestObjRef

4 votes

Donc, fondamentalement, le type de référence doit toujours être passé comme référence si nous voulons voir les changements dans la fonction de l'appelant.

7 votes

Les chaînes de caractères sont des types de référence immuables. Immuable signifie qu'il ne peut pas être modifié après avoir été créé. Toute modification apportée à une chaîne de caractères crée une nouvelle chaîne. C'est pourquoi les chaînes de caractères doivent être passées en tant que 'ref' pour être modifiées par la méthode d'appel. D'autres objets (par exemple, un employé) peuvent être passés sans 'ref' pour récupérer les changements dans la méthode d'appel.

6 votes

@vmg, comme HimalayaGarg, ce n'est pas un très bon exemple. Vous devez inclure un autre exemple de type de référence qui n'est pas immuable.

19voto

Egli Becerra Points 13

Je suppose que c'est plus clair quand tu le fais comme ça. Je vous recommande de télécharger LinqPad pour tester ce genre de choses.

void Main()
{
    var Person = new Person(){FirstName = "Egli", LastName = "Becerra"};

    //Will update egli
    WontUpdate(Person);
    Console.WriteLine("WontUpdate");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateImplicitly(Person);
    Console.WriteLine("UpdateImplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");

    UpdateExplicitly(ref Person);
    Console.WriteLine("UpdateExplicitly");
    Console.WriteLine($"First name: {Person.FirstName}, Last name: {Person.LastName}\n");
}

//Class to test
public class Person{
    public string FirstName {get; set;}
    public string LastName {get; set;}

    public string printName(){
        return $"First name: {FirstName} Last name:{LastName}";
    }
}

public static void WontUpdate(Person p)
{
    //New instance does jack...
    var newP = new Person(){FirstName = p.FirstName, LastName = p.LastName};
    newP.FirstName = "Favio";
    newP.LastName = "Becerra";
}

public static void UpdateImplicitly(Person p)
{
    //Passing by reference implicitly
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

public static void UpdateExplicitly(ref Person p)
{
    //Again passing by reference explicitly (reduntant)
    p.FirstName = "Favio";
    p.LastName = "Becerra";
}

Et cela devrait donner

WontUpdate

Prénom : Egli, Nom de famille : Becerra

UpdateImplicitly

Prénom : Favio, Nom de famille : Becerra

Mise à jourExplicitement

Prénom : Favio, Nom de famille : Becerra

9voto

Haris Hasan Points 17497

Lorsque vous passez le System.Drawing.Image à une méthode, vous passez en fait une copie de la référence à cet objet.

Donc, si dans cette méthode vous chargez une nouvelle image, vous utilisez une référence nouvelle/copiée. Vous ne modifiez pas l'original.

YourMethod(System.Drawing.Image image)
{
    //now this image is a new reference
    //if you load a new image 
    image = new Image()..
    //you are not changing the original reference you are just changing the copy of original reference
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X