Friday, May 11, 2007

Pop quiz on ref and out parameters in C#

If you line up 100 C# developers, I would be willing to bet that the number of developers that could explain the difference between out and ref parameter keywords could be counted on one hand.  When I first started .NET development coming over from pointer-centric languages such as C and C++, I used the ref keyword with reckless abandon.  I started in VB.NET, which only exacerbated the problem with its ByVal and ByRef keywords.  While working on a defect today, I spotted an interesting use of the ref keyword that took me back to my nascent days as a .NET developer:

SqlCommand cmd = new SqlCommand("SELECT * FROM customers WHERE customer_name = @Name");
AddInputParam(ref cmd, "@Name", SqlDbType.NVarChar, customer.Name);

The code went on to execute the query.  But that last line really had me confused.  Under what circumstances would I be getting a different SqlCommand object out of "AddInputParam"?  After some investigation, it turned out that this was just an incorrect use of the ref parameter.

So what are the ref and out keywords?

To understand what the ref and out keywords are, you have to know a little about pointers and reference types in .NET.  In the snippet above, the variable "cmd" holds a reference to a SqlCommand object.  When you specify the "ref" keyword on a method parameter, you are notifying the caller that the reference to the object they passed in can change.  What this told me in the above snippet is that "cmd" could be pointing to a completely different SqlCommand object when the method returned.  I'm pretty sure that's not what the intention of this code is supposed to be.  I don't want to execute a different SqlCommand object, I want to execute the one I created.

With the "out" keyword, it is akin to extra return variables.  It signifies that something extra is passed out of the method, and the caller should initialize the variable they are passing in as null.

  • Out params should be passed in as a null reference, and have to assign the value before exiting the method
  • Ref params should be passed in as an instantiated object, and may re-assign the value before exiting the method

The problem with the snippet above was that the "ref" keyword was completely unnecessary.  When you pass in a reference type by value to a method (the default), the variable reference itself can't change, but the object itself can change.  I could remove the "ref" keyword, and change the SqlCommand object all I wanted, and changes would get reflected in that object when the method returned.  But if I set the "cmd" variable inside the method to a new SqlCommand object, the original SqlCommand object will still point to the original instance.

Example using ref and out

Let's look at a trivial case highlighting the differences between ref, out, and value parameters.  I have a simple Customer class that looks like this:

    
public class Customer
{
    private string _name;

    public Customer(string name)
    {
        _name = name;
    }

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

}

Pretty simple, just a customer with a name.  Now, some code with methods with out, ref, and value parameters:

public void RefAndOutParamExample()
{
    Customer customer = new Customer("Bob");
    Debug.WriteLine(customer.Name);

    Test1(customer);
    Debug.WriteLine(customer.Name);

    Test2(ref customer);
    Debug.WriteLine(customer.Name);

    Test3(out customer);
    Debug.WriteLine(customer.Name);
}

private void Test1(Customer customer)
{
    customer.Name = "Billy";
    customer = new Customer("Sally");
}

private void Test2(ref Customer customer)
{
    customer.Name = "Larry";
    customer = new Customer("Joe");
}

private void Test3(out Customer customer)
{
    // customer.Name = "Suzie"; // Compile error, I can't reference an
                                // out param without assigning it first
    customer = new Customer("Chris");
}

The output of the RefAndOutParamExample would be:

Original:    Bob
Value param: Billy
ref param: Joe
out param: Chris

So what happened?

In all of these methods, I reassign the customer.Name property, then reassign the customer parameter to a new instance of a Customer object.  All of the methods successfully change the Name property of the original customer instance, but only methods with the out and ref parameter can change what Customer object the original variable referenced.  The final Test3 method can't assign the Name property, and will get a compile error if I try to access it before assigning it.

When to use ref and out parameters

From Framework Design Guidelines, pages 156 and 157, I see two guidelines:

  • AVOID using out or ref parameters
  • DO NOT pass reference types by reference

Framework Design Guidelines has 4 types of recommendations related to guidelines:

  • DO - should be always followed
  • CONSIDER - should generally be followed, unless you really know what's going on and have a good reason to break the rule
  • DO NOT - should almost never do
  • AVOID - generally not a good idea, but there might be a few known cases where it makes sense

So the FDG tells me that in general I should avoid out and ref parameters, and should never pass in reference types with the "ref" or "out" keyword.  The problem with these keywords is that they require some knowledge of pointers and reference types, which can be easily confused.  It also forces the caller to declare temporary variables.  They hurt the readability of the code since it violates the common pattern of assigning a variable the result of a method call.

If you feel the need to add a ref param, I'd suggest taking a look at FDG to see the recommendations for these parameters in depth.  You could also consider refactoring your code to return the entire result into a single object, instead of splitting the results into two objects in a return parameter and a ref parameter.  The only time I've ever justified the need of a ref parameter was in the Tester-Doer pattern, which is for a very specific scenario.  To me, ref and out params remind me of Mr. Miyagi's advice about karate in The Karate Kid, "You learn karate so that you never need to use it".

No comments: