1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace ParameterPassing
7: {
8: enum CoverType
9: {
10: Hardback,
11: Softback
12: }
13:
14: enum ClassType
15: {
16: Spy,
17: Drama,
18: Romance,
19: Historical
20: }
21:
22: class Book
23: {
24: public string Author { get; set; }
25: public string Title { get; set; }
26: public int NumberPages { get; set; }
27: public CoverType Cover { get; set; }
28: public ClassType Class { get; set; }
29:
30: //...
31:
32: }
33: }
There are two types: reference and value types.
Value types are simple types (boolean, integer, byte, float, long, decimal, char, etc.), struct, and enum. A value type holds its value in its variable and is stored on the stack memory.
So consider the following code. What would the value of copyNum be? How about num?
1: int num = 10;
2: int copyNum = num;
3: num++
1.1.2 Reference Types:
Reference types are known as objects. They include: class, arrays, interface, delegate, built-in reference types (e.g. object, string, and dynamic), and nullable types. Unlike a value type which holds its data in the variable, a reference type holds the heap's memory location of its corresponding object. So when you see referenced to or address of, it means the memory location in the heap.
Did I lose you? If so, let's step back one step. Recall that the variable holding the heap's memory location (or address or reference) is stored in the stack memory and that the actual object is stored in the heap memory. The variable simply holds the memory location and no data.
An example:
Book myBook = new Book();
Here's what happens with the above line of code: First the compiler creates the variable myBook on the stack. Then the new keyword creates the object on the heap, which is where the data (its properties and methods) for object will reside. The heap's memory location (address) is then stored in the variable myBook.
Let's now move forward a couple of steps. What happens if we copy a reference type to another without using the new keyword?
Book myBook = new Book();
Book copyBook = myBook;
There are four types of parameters: value, reference, output, and parameter arrays, all of which can be used with both value and reference types. I'm only going to cover the first two.
This is the default of how parameters are passed. For example, in the following method, myBook.NumberPages is passed into the method by value because no other parameter keyword is specified (i.e. ref, out, or params).
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace ParameterPassing
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: Run();
13: }
14: public static void Run()
15: {
16: Book myBook = new Book();
17: myBook.NumberPages = 250;
18: Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
19:
20: Change(myBook.NumberPages);
21:
22: Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
23: Console.ReadLine();
24: }
25:
26: public static void Change(int numPages)
27: {
28: numPages = 100;
29: Console.WriteLine("numPages: {0}", numPages);
30: }
31: }
32: }
numPages: 100
myBook.NumberPages: 250
What if we want to be able to change the original variable with our passed parameter? For example, using the code above, we want Change() to update originalNumPages. To do this, place the ref keyword before originalNumPages and the method's parameter, as shown:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace ParameterPassing
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: Run();
13: }
14: public static void Run()
15: {
16: Book myBook = new Book();
17: myBook.NumberPages = 250;
18: //Can't ref a property; therefore, we'll use an int
19: int originalNumPages = myBook.NumberPages;
20: Console.WriteLine("originalNumPages: {0}", originalNumPages);
21:
22: Change(ref originalNumPages);
23:
24: Console.WriteLine("originalNumPages: {0}", originalNumPages);
25: Console.ReadLine();
26: }
27:
28: public static void Change(ref int numPages)
29: {
30: numPages = 100;
31: Console.WriteLine("numPages: {0}", numPages);
32: }
33: }
34: }
numPages: 100
originalNumPages: 100
The result would be that both numPages and originalNumPages are both changed to a value of 100.
-------------------------------------------------------------------------------------------------
Here is where you might get confused, as this is where I had my misunderstanding (which I explain in the conclusion below). Remember, reference type variables hold the heap's memory location (or address) to the actual object (Book being my object). We covered that above. When we pass a reference type by value to a method, we are passing a memory location to a newly created parameter, which is created on the stack. Did I lose you? Here's an example:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5:
6: namespace ParameterPassing
7: {
8: class Program
9: {
10: static void Main(string[] args)
11: {
12: Run();
13: }
14: public static void Run()
15: {
16: Book myBook = new Book();
17: myBook.NumberPages = 250;
18: Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
19:
20: Change(myBook);
21:
22: Console.WriteLine("myBook.NumberPages: {0}", myBook.NumberPages);
23: Console.ReadLine();
24: }
25:
26: public static void Change(Book bookObj)
27: {
28: bookObj.NumberPages = 100;
29: Console.WriteLine("bookObj.NumberPages: {0}", bookObj.NumberPages);
30: }
31: }
32: }
When myBook is passed to the parameter bookObj, a new block of memory is created on the stack for bookObj and the value in myBook's variable is passed (copied) to bookObj parameter. Remember that value in myBook's variable is the object's heap memory location. (I need to stress that only the object's heap memory location is passed and not the object, as the object lives on the heap.) Now both myBook and bookObj have the same value (reference to the Book object's memory location).
bookObj.NumberPages: 100
myBook.NumberPages: 100
Do you understand why both bookObj.NumberPages and myBook.NumberPages are equal to 100? Because they are both addressed to (referenced to) the same object.
2.3.1 Proof - Examine Memory Addresses:
Since I'm talking in terms of memory locations, I thought it would help to grab and read each out on the Console. In order to do that though, we need to modify and add some code to our example. Please note, that the code and concepts here are above and beyond the beginner level. But I still wanted to provide them to assist you in your learning process.
C# is a managed software and the garbage collector moves around memory. Therefore we need to pin down what the variable we want in order to evaluate it. We also have to make our Book class blittable (for more information on what that is, go to MSDN). We also modify our Program class to pin, get the handle, and then get the address of the pinned object.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Runtime.InteropServices; //Added
6:
7: namespace ParameterPassing
8: {
9: #region "enums"
10: //enum CoverType
11: //{
12: // Hardback,
13: // Softback
14: //}
15:
16: //enum ClassType
17: //{
18: // Spy,
19: // Drama,
20: // Romance,
21: // Historical
22: //}
23: #endregion
24:
25: //Our class is now blittable
26: [StructLayout(LayoutKind.Sequential)]
27: class Book
28: {
29: //public string Author { get; set; }
30: //public string Title { get; set; }
31: public int NumberPages { get; set; }
32: //public CoverType Cover { get; set; }
33: //public ClassType Class { get; set; }
34:
35: //...
36:
37: }
38: }
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Runtime.InteropServices; //Added
6:
7: namespace ParameterPassing
8: {
9: class Program
10: {
11: static void Main(string[] args)
12: {
13: Run();
14: }
15: public static void Run()
16: {
17: Book myBook = new Book();
18: /* In order to get the memory address of myBook,
19: * we have to "pin it" first.
20: */
21: GCHandle myHandle = GCHandle.Alloc(myBook,
22: GCHandleType.Pinned);
23: IntPtr myBookAddress = myHandle.AddrOfPinnedObject();
24: myHandle.Free();
25:
26: Console.WriteLine("myBook address: {0}", myBookAddress);
27:
28: Change(myBook);
29:
30: //Repeat code to see if address changed
31: myHandle = GCHandle.Alloc(myBook,
32: GCHandleType.Pinned);
33: myBookAddress = myHandle.AddrOfPinnedObject();
34: myHandle.Free();
35:
36: Console.WriteLine("myBook address: {0}", myBookAddress);
37: Console.ReadLine();
38: }
39:
40: public static void Change(Book bookObj)
41: {
42: GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
43: GCHandleType.Pinned);
44: IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
45: bookObjHandle.Free();
46:
47: Console.WriteLine("bookObj address: {0}", bookObjAddress);
48: }
49: }
50: }
The Console output is:
myBook address: 11762472
bookObj address: 11762472
myBook address: 11762472
Notice that the memory addresses do not change.
2.3.2 Proof - Now What Happens If I Change bookObj to Another Book Object?
What happens if bookObj is instantiated to another Book object?
1: public static void Change(Book bookObj)
2: {
3: GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
4: GCHandleType.Pinned);
5: IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
6: bookObjHandle.Free();
7:
8: Console.WriteLine("bookObj address: {0}", bookObjAddress);
9:
10: //What happens if I create another Book object?
11: bookObj = new Book();
12:
13: bookObjHandle = GCHandle.Alloc(bookObj,
14: GCHandleType.Pinned);
15: bookObjAddress = bookObjHandle.AddrOfPinnedObject();
16: bookObjHandle.Free();
17:
18: Console.WriteLine("bookObj address: {0}", bookObjAddress);
19:
20: }
The Console output is:
myBook address: 11762472
bookObj address: 11762472
bookObj address: 11765252
myBook address: 11762472
Interesting bookObj is now referenced to a different memory location (i.e. a different object on the heap), while myBook retains its original memory location. Therefore, we have two Book objects on the heap now and both are referenced.
2.4 Passing Reference Parameters for Reference Types:
Looking at the last proof in 2.3.2, what if we want to change the object that both variables are referenced to? We do that by passing the parameter by reference and using the ref keyword.
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.Runtime.InteropServices; //Added
6:
7: namespace ParameterPassing
8: {
9: class Program
10: {
11: static void Main(string[] args)
12: {
13: Run();
14: }
15: public static void Run()
16: {
17: Book myBook = new Book();
18: /* In order to get the memory address of myBook,
19: * we have to "pin it" first.
20: */
21: GCHandle myHandle = GCHandle.Alloc(myBook,
22: GCHandleType.Pinned);
23: IntPtr myBookAddress = myHandle.AddrOfPinnedObject();
24: myHandle.Free();
25:
26: Console.WriteLine("myBook address: {0}", myBookAddress);
27:
28: Change(ref myBook);
29:
30: //Repeat code to see if address changed
31: myHandle = GCHandle.Alloc(myBook,
32: GCHandleType.Pinned);
33: myBookAddress = myHandle.AddrOfPinnedObject();
34: myHandle.Free();
35:
36: Console.WriteLine("myBook address: {0}", myBookAddress);
37: Console.ReadLine();
38: }
39:
40: public static void Change(ref Book bookObj)
41: {
42: GCHandle bookObjHandle = GCHandle.Alloc(bookObj,
43: GCHandleType.Pinned);
44: IntPtr bookObjAddress = bookObjHandle.AddrOfPinnedObject();
45: bookObjHandle.Free();
46:
47: Console.WriteLine("bookObj address: {0}", bookObjAddress);
48:
49: //What happens if I create another Book object?
50: bookObj = new Book();
51:
52: bookObjHandle = GCHandle.Alloc(bookObj,
53: GCHandleType.Pinned);
54: bookObjAddress = bookObjHandle.AddrOfPinnedObject();
55: bookObjHandle.Free();
56:
57: Console.WriteLine("bookObj address: {0}", bookObjAddress);
58:
59: }
60: }
61: }
The Console reads out:
myBook address: 11762472
bookObj address: 11762472
bookObj address: 11765252
myBook address: 11765252
So what happened here?
http://rapidapplicationdevelopment.blogspot.com/2007/01/parameter-passing-in-c.html
No comments:
Post a Comment