Passer des objets par référence ou valeur en C #

234

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

Ainsi, lors du passage à une méthode d'un objet non primitif, tout ce qui est fait à l'objet dans la méthode affectera l'objet transmis. (Trucs C # 101)

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

Pourquoi est-ce?

Michael
la source
20
Toutes les variables sont passées 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.
Andrew Barber le

Réponses:

503

Les objets ne sont pas du tout passés. Par défaut, l'argument est évalué et sa valeur est transmise, par valeur, comme valeur initiale du paramètre de la méthode que vous appelez. Maintenant, 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 par l'appelant. Cependant, la modification de la valeur du paramètre pour faire référence à un autre objet ne sera pas visible lorsque vous utilisez la valeur de passage, qui est la valeur par défaut pour tous. types.

Si vous souhaitez utiliser le passage par référence, vous devez utiliser outouref , que le type de paramètre soit 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 apportées au paramètre lui-même sont vues par l'appelant.

Alors:

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 va dans beaucoup plus de détails . Fondamentalement, «passer par référence» ne signifie pas ce que vous pensez que cela signifie.

Jon Skeet
la source
2
Tu as raison, je n'ai pas vu ça! J'ai chargé image = Image.FromFile (..) et cela remplaçait l'image variable et ne changeait pas l'objet! :) bien sûr.
Michael
1
@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 :)
Jon Skeet
2
Si nous supprimons des mots clés refet outde c #, est-il correct de dire que c # transmet les paramètres de la même manière que Java, c'est-à-dire toujours par valeur Y a-t-il une différence avec Java?
haut débit du
1
@broadband: Oui, le mode de passage par défaut est par valeur. Bien sûr, C # a des pointeurs et des types de valeur personnalisés, ce qui rend tout cela un peu plus compliqué qu'en Java.
Jon Skeet
3
@Vippy: Non, pas du tout. C'est une copie de la référence . Je vous suggère de lire l'article lié.
Jon Skeet
18

Un autre exemple de code pour illustrer cela:

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 la sortie:

TestPlain: 0

TestRef: 5

TestObjPlain: test

TestObjRef: TestObjRef

vmg
la source
2
Donc, fondamentalement, le type de référence doit encore être PASSÉ comme référence si nous voulons voir les changements dans la fonction Caller.
Incassable le
1
Les chaînes sont des types de référence immuables. Immuable signifie qu'il ne peut pas être modifié après sa création. Chaque modification d'une chaîne créera une nouvelle chaîne. C'est pourquoi les chaînes devaient être passées en tant que «ref» pour obtenir un changement dans la méthode d'appel. D'autres objets (par exemple employé) peuvent être passés sans «ref» pour récupérer les changements dans la méthode d'appel.
Himalaya Garg
1
@vmg, selon 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.
Daniel
11

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

Lorsque vous passez une instance en tant qu'argument à la méthode, elle transmet la copyde 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, elle ne sera pas reflétée dans l'appelant. Si l'instance est un type de référence, vous transmettez la copie de la référence (réside à nouveau dans le stack) à l'objet. Vous avez donc obtenu 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 d'origine, elle se référera au nouvel objet que vous venez de créer. Vous finirez donc par avoir 2 références et 2 objets.

OlegI
la source
Ce devrait être la réponse choisie!
JAN
Je suis complètement d'accord! :)
JOSEFtw
8

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

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 produire

WontUpdate

Prénom: Egli, Nom: Becerra

Mettre à jour implicitement

Prénom: Favio, Nom: Becerra

Mise à jourExplicitement

Prénom: Favio, Nom: Becerra

Egli Becerra
la source
et qu'en est-il du vide statique public WhatAbout (Person p) {p = new Person () {FirstName = "First", LastName = "Last"}; }. :)
Marin Popov
4

Lorsque vous passez l' System.Drawing.Imageobjet type à une méthode, vous passez en fait une copie de référence à cet objet.

Donc, si à l'intérieur de cette méthode, vous chargez une nouvelle image, vous chargez en utilisant 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
}
Haris Hasan
la source
-1

Dans Pass By Reference Vous ajoutez seulement "ref" dans les paramètres de la fonction et encore une chose que vous devriez déclarer la fonction "statique" car le principal est statique (# public void main(String[] args))!

namespace preparation
{
  public  class Program
    {
      public static void swap(ref int lhs,ref int rhs)
      {
          int temp = lhs;
          lhs = rhs;
          rhs = temp;
      }
          static void Main(string[] args)
        {
            int a = 10;
            int b = 80;

  Console.WriteLine("a is before sort " + a);
            Console.WriteLine("b is before sort " + b);
            swap(ref a, ref b);
            Console.WriteLine("");
            Console.WriteLine("a is after sort " + a);
            Console.WriteLine("b is after sort " + b);  
        }
    }
}
user5593590
la source