Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Passing Parameters by Reference to a Method

By default, the common language runtime (CLR) assumes that all method parameters are passed by value. When reference type objects are passed, the reference (or pointer) to the object is passed (by value) to the method. This means that the method can modify the object and the caller will see the change. For value type instances, a copy of the instance is passed to the method. This means that the method gets its own private copy of the value type and the instance in the caller isn’t affected.

       
   
 
 


The CLR allows you to pass parameters by reference instead of by value. In C#, you do this by using the out and ref keywords. Both keywords tell the C# compiler to emit metadata indicating that this designated parameter is passed by reference, and the compiler uses this to generate code to pass the address of the parameter rather than the parameter itself.

From the CLR’s perspective, out and ref are identical—that is, the same IL is produced regard- less of which keyword you use, and the metadata is also identical except for 1 bit, which is used to record whether you specified out or ref when declaring the method. However, the C# compiler treats the two keywords differently, and the difference has to do with which method is responsible for initializing the object being referred to. If a method’s parameter is marked with out, the caller isn’t expected to have initialized the object prior to calling the method. The called method can’t read from the value, and the called method must write to the value before returning. If a method’s parameter

is marked with ref, the caller must initialize the parameter’s value prior to calling the method. The called method can read from the value and/or write to the value.

Reference and value types behave very differently with out and ref. Let’s look at using out and

ref with value types first.

 

public sealed class Program { public static void Main() {

Int32 x; // x is uninitialized.

GetVal(out x); // x doesn’t have to be initialized. Console.WriteLine(x); // Displays "10"

}

 

private static void GetVal(out Int32 v) {

v = 10; // This method must initialize v.

}

}

 

In this code, x is declared in Main’s stack frame. The address of x is then passed to GetVal. Get­ Val’s v is a pointer to the Int32 value in Main’s stack frame. Inside GetVal, the Int32 that v points to is changed to 10. When GetVal returns, Main’s x has a value of 10, and 10 is displayed on the console. Using out with large value types is efficient because it prevents instances of the value type’s fields from being copied when making method calls.

Now let’s look at an example that uses ref instead of out.

 

public sealed class Program { public static void Main() {

Int32 x = 5; // x is initialized. AddVal(ref x); // x must be initialized. Console.WriteLine(x); // Displays "15"

}

 

private static void AddVal(ref Int32 v) {



v += 10; // This method can use the initialized value in v.

}

}


In this code, x is also declared in Main’s stack frame and is initialized to 5. The address of x is then passed to AddVal. AddVal’s v is a pointer to the Int32 value in Main’s stack frame. Inside AddVal, the Int32 that v points to is required to have a value already. So, AddVal can use the initial value in any expression it desires. AddVal can also change the value, and the new value will be “returned” to the caller. In this example, AddVal adds 10 to the initial value. When AddVal returns, Main’s x will contain 15, which is what gets displayed in the console.

To summarize, from an IL or a CLR perspective, out and ref do exactly the same thing: they both cause a pointer to the instance to be passed. The difference is that the compiler helps ensure that your code is correct. The following code that attempts to pass an uninitialized value to a method ex- pecting a ref parameter produces the following message: error CS0165: Use of unassigned local variable 'x'.

 

public sealed class Program { public static void Main() {

Int32 x; // x is not initialized.

 

// The following line fails to compile, producing

// error CS0165: Use of unassigned local variable 'x'. AddVal(ref x);

 

Console.WriteLine(x);

}

 

private static void AddVal(ref Int32 v) {



v += 10; // This method can use the initialized value in v.

}

}

       
   
 
 


 

Using out and ref with value types gives you the same behavior that you already get when pass- ing reference types by value. With value types, out and ref allow a method to manipulate a single value type instance. The caller must allocate the memory for the instance, and the callee manipulates that memory. With reference types, the caller allocates memory for a pointer to a reference object, and the callee manipulates this pointer. Because of this behavior, using out and ref with reference types is useful only when the method is going to “return” a reference to an object that it knows about. The following code demonstrates.

 

using System; using System.IO;

 

public sealed class Program { public static void Main() {

FileStream fs; // fs is uninitialized

 

// Open the first file to be processed. StartProcessingFiles(out fs);

 

// Continue while there are more files to process. for (; fs != null; ContinueProcessingFiles(ref fs)) {

 

// Process a file. fs.Read(...);

}

}

 

private static void StartProcessingFiles(out FileStream fs) {

fs = new FileStream(...); // fs must be initialized in this method

}

 

private static void ContinueProcessingFiles(ref FileStream fs) { fs.Close(); // Close the last file worked on.

 

// Open the next file, or if no more files, "return" null. if (noMoreFilesToProcess) fs = null;

else fs = new FileStream (...);

}

}


As you can see, the big difference with this code is that the methods that have out or ref refer- ence type parameters are constructing an object, and the pointer to the new object is returned to the caller. You’ll also notice that the ContinueProcessingFiles method can manipulate the object being passed into it before returning a new object. This is possible because the parameter is marked with the ref keyword. You can simplify the preceding code a bit, as shown here.

 

using System; using System.IO;

 

public sealed class Program { public static void Main() {

FileStream fs = null; // Initialized to null (required)

 

// Open the first file to be processed. ProcessFiles(ref fs);

 

// Continue while there are more files to process. for (; fs != null; ProcessFiles(ref fs)) {

 

// Process a file. fs.Read(...);

}

}

 

private static void ProcessFiles(ref FileStream fs) {

// Close the previous file if one was open.

if (fs != null) fs.Close(); // Close the last file worked on.

 

// Open the next file, or if no more files, "return" null. if (noMoreFilesToProcess) fs = null;

else fs = new FileStream (...);

}

}

 

Here’s another example that demonstrates how to use the ref keyword to implement a method that swaps two reference types.

 

public static void Swap(ref Object a, ref Object b) { Object t = b;

b = a; a = t;

}

 

To swap references to two String objects, you’d probably think that you could write code like the following.

 

public static void SomeMethod() { String s1 = "Jeffrey";

String s2 = "Richter";

 

Swap(ref s1, ref s2);

Console.WriteLine(s1); // Displays "Richter" Console.WriteLine(s2); // Displays "Jeffrey"

}


However, this code won’t compile. The problem is that variables passed by reference to a method must be of the same type as declared in the method signature. In other words, Swap expects two Object references, not two String references. To swap the two String references, you must do the following.

 

public static void SomeMethod() { String s1 = "Jeffrey";

String s2 = "Richter";

 

// Variables that are passed by reference

// must match what the method expects. Object o1 = s1, o2 = s2;

Swap(ref o1, ref o2);

 

// Now cast the objects back to strings. s1 = (String) o1;

s2 = (String) o2;

 

Console.WriteLine(s1); // Displays "Richter" Console.WriteLine(s2); // Displays "Jeffrey"

}

 

This version of SomeMethod does compile and execute as expected. The reason why the param- eters passed must match the parameters expected by the method is to ensure that type safety is preserved. The following code, which thankfully won’t compile, shows how type safety could be compromised.

 

internal sealed class SomeType { public Int32 m_val;

}

 

public sealed class Program { public static void Main() {

SomeType st;

 

// The following line generates error CS1503: Argument '1':

// cannot convert from 'ref SomeType' to 'ref object'. GetAnObject(out st);

 

Console.WriteLine(st.m_val);

}

 

private static void GetAnObject(out Object o) { o = new String('X', 100);

}

}

 

In this code, Main clearly expects GetAnObject to return a SomeType object. However, because GetAnObject’s signature indicates a reference to an Object, GetAnObject is free to initialize o to an object of any type. In this example, when GetAnObject returned to Main, st would refer to a String, which is clearly not a SomeType object, and the call to Console.WriteLine would certainly fail. Fortunately, the C# compiler won’t compile the preceding code because st is a reference to SomeType, but GetAnObject requires a reference to an Object.


You can use generics to fix these methods so that they work as you’d expect. Here is how to fix the

Swap method shown earlier.

 

public static void Swap<T>(ref T a, ref T b) { T t = b;

b = a; a = t;

}

 

And now, with Swap rewritten as above, the following code (identical to that shown before) will compile and run perfectly.

 

public static void SomeMethod() { String s1 = "Jeffrey";

String s2 = "Richter";

 

Swap(ref s1, ref s2);

Console.WriteLine(s1); // Displays "Richter" Console.WriteLine(s2); // Displays "Jeffrey"

}

 

For some other examples that use generics to solve this problem, see System.Threading’s Interlocked class with its CompareExchange and Exchange methods.

 

 


Date: 2016-03-03; view: 797


<== previous page | next page ==>
Nbsp;   Implicitly Typed Local Variables | Nbsp;   Passing a Variable Number of Arguments to a Method
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.01 sec.)