2

I am getting different result while calling 4 methods in different ways:

static void Main(string[] args)
{
   var emp = new Employee { Name = "ABC" };

   ChangeName1(emp);
   Console.WriteLine(emp.Name); //XYZ

   ChangeName2(ref emp);
   Console.WriteLine(emp.Name); //XYZ

   ChangeToNull1(emp);
   Console.WriteLine(emp.Name); // XYZ

   ChangeToNull2(ref emp);
   Console.WriteLine(emp.Name); // Null Reference Exception

   Console.ReadLine();
}

static void ChangeName1(Employee e)
{
   e.Name = "XYZ";
}
static void ChangeName2(ref Employee e)
{
   e.Name = "XYZ";
}
static void ChangeToNull1(Employee e)
{
   e = null;
}
static void ChangeToNull2(ref Employee e)
{
   e = null;
}

As you can see the first 2 methods, I am changing the value of Name property and after returning from the method, the original object property is changed.

But when set the object to null, ChangeToNull1 method doesn't change the original object but ChangeToNull2 method does.

So my questions are:

1. Why C# behaving this way ?

2. Does C# makes a copy of original object while get passed to a method ?

3. If does then how it changes the original object properties like Name, and why not it changes the original object ?

4. Does c# copies original object while passed by ref ?

Bimal Das
  • 1,882
  • 5
  • 28
  • 53
  • 2
    The problem is that you are handing in a Reference to a object in memory via call by value and call by reference. Stuff becomes wierd. Also note: Do not try to learn class behavior with String! It does not behave anything like the normal Reference Types. It is designed to work more like a primitive value type like lint. – Christopher Nov 19 '18 at 06:23
  • 1
    https://stackoverflow.com/questions/30585483/cannot-set-an-object-to-null-inside-a-method maybe this explanation help you. – Darem Nov 19 '18 at 06:27
  • @Christopher Can you explain when `string` behaves differently than other types? I didn't encouter such a situation yet – Chayim Friedman Aug 23 '20 at 19:34

4 Answers4

3

In C#, there are two catgories of objects: Value-Types and Reference-Types.

Value-Types are structs and enums, such as int (System.Int32). These types always copied when they're passed. If you change an int in a method, the variable inside the caller won't change.

You are talking about reference types - classes, arrays and interfaces, basically.

In reference types, such as string (System.String), there are two sections: the object, and the pointer. For example, let's see your Employee. Suppose I declared a variable named e1 of type Employee, and assigned to it's name "abc":

Employee e1 = new Employee { Name = "abc" };

Now, there is an employee object in the memory (the heap, because reference types almost always are allocated in the heap, expect stackalloc), which contains Name="abc". There is also (in the stack) a pointer to this object. Let's suppose we have this memory image:

 0x123 - An employee object          0x45F - the variable `e1` - pointer to the employee
|-------------------------|         |------------------|
|     Name = "abc"        |         |     0x123        |
|-------------------------|         |------------------|

When you pass it without ref, the value of e1 - 0x123 is copied, but the employee isn't copied! So, if you change its name, the original employee will be changed! But if you change the pointer nothing will happen, since the pointer e1 is just copied!

When you pass with ref, the address of the pointer is copied - 0x45F. So, if you change the parameter, it will change e1, since it was not copied, but its address.

Edit:

If I assign a reference type variable to an another variable, for example:

var e1 = new Employee { Name = "abc" };
Employee e2 = employee;

then, e2 is the same as e1 - it'a also a pointer, which points to the same address. If we takes the prevoius memory image, now there is in the address 0x4AC a variable named e2, contains also 0x123, the address of the object. So, if we'll change e2.Name:

e2.Name = "new";

then, e1.Name is now "new", too. Last important fact about reference types, is that comparing (==) reference types (I'm talking when there is not an overloaded operator ==), won't check if they contains the same values, but if they points to the same object. Let's see an example:

var e1 = new Employee { Name = "abc" };
Employee e2 = e1;
var e3 = new Employee { Name = "abc" };
var e4 = new Employee { Name = "123" };

Console.WriteLine(e1 == e4); // false
Console.WriteLine(e1 == e3); // false, since they don't point to the same object, they just contain the same values
Console.WriteLine(e1 == e2); // true, since they point to the same object

Some comments about strings:

  1. Althought the class System.String is a reference type, it overloads operator ==(), so comparing two strings will give a correct result.
  2. The compiler usually optimize strings, so if string s1 = "abc"; and string s2 = "abc";, s1 and s2 can point to the same address (for memory saving). It's possible because strings are immutable - if, for example, you call Replace() on a string, it'll create a new string. So you shouldn't know about this (but it's important if you write unsafe code).
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • can I set object to null ? because the reference if I set to null,it becomes null but it does not change the instance to null. – Bimal Das Nov 19 '18 at 07:05
  • 1
    @BimalDas Yes, when you set a reference to null, you don't delete the object from the memory, but now, there are not more references to it, and so, in the next time the GC will run, it will find that this object has no references, and it will delete it (it's a very complex process, I explained it simply, but until object is destroyed, this are some stations.) – Chayim Friedman Nov 19 '18 at 07:15
2

It behaves that way because C# passes a copy of the value of the pointer to the reference type. That's a bit of a mouthful, so this might be more revealing:

When you write:

var emp = new Employee { Name = "ABC" }; 

You're creating an instance of Employee and storing the pointer to that object in the variable emp. Let's assume the memory location of emp is 0x000001. And the value of that (the location of the object) is 0x0000AA.

When you call:

ChangeName1(emp);

You're passing the value 0x0000AA. Within the method ChangeName1, the value of e is 0x0000AA, but it's location is not 0x000001. It's stored in a different place in memory.

However, when you call:

ChangeName2(ref emp);

You're passing the memory location of emp, which is 0x000001. So within this method, updating e also updates emp.


To address the members - as noted above, you're not copying the object. Both ChangeName1 and ChangeName2 refer to the same object. They both refer to the object stored at 0x0000AA.


For futher reading, see When is a C# value/object copied and when is its reference copied?

user9912639
  • 153
  • 6
1
static void ChangeName1(Employee e)
{
   e.Name = "XYZ";
}

Here, the new pointer "e" points to the Same object "emp" - so changes reflect.

static void ChangeName2(ref Employee e)
{
   e.Name = "XYZ";
}

Here "emp" is passed as reference - just that its named e. (easy to understand this way)

static void ChangeToNull1(Employee e)
{
   e = null;
}

Here, the new pointer "e" points to the Same object "emp" - When you set e= Null. the new pointer is null'd. Not the original object.

static void ChangeToNull2(ref Employee e)
{
   e = null;
}

I guess by now you understood whats going on.

Prateek Shrivastava
  • 1,877
  • 1
  • 10
  • 17
1

What "emp" is, is a reference variable. A variable that points to a instance of a reference type.

The ways reference variables works, are similar to how Pointers work. Naked pointers are a very fundamental tool of programming, but handling them is scarily dangerous. So the .NET team choose not to force you to handle them by default. But as they are so important, a lot of things had to be invented to repalce them (References and Delegates are jsut 2 common ones).

The object in memory and the (number) of references you hold to it are entirely unrealted. An object in memory can have 0, 1 or many references point at it. A object without any references will be collected by the GC, whenever it chooses to run next.

Setting a reference to null does not force a collection, it meerely makes the collection possible. A a high propabiltiy, given more runtime. But never a guarantee.

Christopher
  • 9,634
  • 2
  • 17
  • 31