Mark Gilbert's Blog

Science and technology, served light and fluffy.

References available upon request – Passing variables by reference in C# and VB

On my most recent MVC project, I hit a weird snag when it came to passing a variable by reference in C#.  I had a class that descended from System.Web.Mvc.Controller, and I was trying to pass it to a method that took a reference to System.Web.Mvc.Controller (not my descendent class).  This resulted in compile-time errors (which I’ll show shortly).  This alone wouldn’t result in a blog post, except that the exact same thing in VB works fine.

While this was happening with the System.Web.Mvc.Controller class, this is actually a difference in how C# handles any variable being passed by reference, so I’ve worked up a very simple solution to illustrate this.  First, here is the working VB sample:

Module MainModule

    Sub Main()
        Dim ChildObject As Child

        ChildObject = New Child
        ChildObject.PersonName = "Mark"
        Console.WriteLine("VB Example" & vbCrLf & "**********" & vbCrLf)
        Console.WriteLine(String.Format("Original Name: {0}", ChildObject.PersonName))
        UpdateName(ChildObject)
        Console.WriteLine(String.Format("New Name:      {0}", ChildObject.PersonName))
        Console.ReadLine()
    End Sub


    Private Sub UpdateName(ByRef ObjectToModify As Parent)
        ObjectToModify.PersonName = "Mark II"
    End Sub


    Public Class Parent
        Public PersonName As String
    End Class

    Public Class Child
        Inherits Parent
    End Class

End Module

And now, my first attempt at porting this to C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

class MainModule
{
    static void Main(string[] args)
    {
        Child ChildObject;

        ChildObject = new Child();
        ChildObject.PersonName = "Mark";
        Console.WriteLine("C# Example\n**********\n");
        Console.WriteLine(String.Format("Original Name: {0}", ChildObject.PersonName));

        // Line in error
        UpdateName(ref TestCSObject);

        Console.WriteLine(String.Format("New Name:      {0}", ChildObject.PersonName));
        Console.ReadLine();
    }

    
    private static void UpdateName(ref Parent ObjectToModify) {
        ObjectToModify.PersonName = "Mark II";
    }


    class Parent
    {
        public string PersonName;
    }
    class Child : Parent
    {
    }
}

The UpdateName() call in the C# version results in two compile errors:

The best overloaded method match for ‘MainModule.UpdateName(ref MainModule.Parent)’ has some invalid arguments   

Argument 1: cannot convert from ‘ref MainModule.Child’ to ‘ref MainModule.Parent’

When I first hit this, I figured it was having a problem casting between objects of type Child and Parent, even though functionally the classes were the same (meaning I did not override Parent properties or methods in Child, nor did I add anything new to Child).  So, in my second attempt I explicitly cast the object on the way in to UpdateName():

static void Main(string[] args)
{
    Child ChildObject;

    ChildObject = new Child();
    ChildObject.PersonName = "Mark";
    Console.WriteLine("C# Example\n**********\n");
    Console.WriteLine(String.Format("Original Name: {0}", ChildObject.PersonName));

    // Line in error
    UpdateName(ref (Parent)TestCSObject);

    Console.WriteLine(String.Format("New Name:      {0}", ChildObject.PersonName));
    Console.ReadLine();
}

Now, the compiler was throwing this on the UpdateName() call:

A ref or out argument must be an assignable variable

So, it looks like the explicit cast worked for getting the variable in, but the compiler was still confused how to handle it on the way back out.  So, my third attempt to was to use an explicitly-casted, and more importantly, separate, Parent object:

static void Main(string[] args)
{
    Child ChildObject;

    ChildObject = new Child();
    ChildObject.PersonName = "Mark";
    Console.WriteLine("C# Example\n**********\n");
    Console.WriteLine(String.Format("Original Name: {0}", ChildObject.PersonName));

    Parent ParentObject;
    ParentObject = (Parent)ChildObject;
    UpdateName(ref ParentObject);
    ChildObject = (Child)ParentObject;

    Console.WriteLine(String.Format("New Name:      {0}", ChildObject.PersonName));
    Console.ReadLine();
}

That worked.  I then pressed my luck and tried leaving out the explicit casts:

static void Main(string[] args)
{
    Child ChildObject;

    ChildObject = new Child();
    ChildObject.PersonName = "Mark";
    Console.WriteLine("C# Example\n**********\n");
    Console.WriteLine(String.Format("Original Name: {0}", ChildObject.PersonName));

    Parent ParentObject;
    ParentObject = ChildObject;
    UpdateName(ref ParentObject);
    ChildObject = ParentObject;

    Console.WriteLine(String.Format("New Name:      {0}", ChildObject.PersonName));
    Console.ReadLine();
}

The ParentObject assignment before UpdateName() worked fine, but the ChildObject assignment threw this error:

Cannot implicitly convert type ‘MainModule.Parent’ to ‘MainModule.Child’. An explicit conversion exists (are you missing a cast?)

I wouldn’t say I was "missing" it, Bob.

I then cracked open the two executables in JustDecompile to see what these two boiled down to.  As it turns out, the key piece of logic – the call to UpdateName – is identical whether I use the working VB sample, or the working C# sample.

VB:

VB in JustDecompile

 

C#:

CS in JustDecompile

So, for better or worse, the VB environment is simply doing more for me than the C# environment.  It knew that I was trying to pass a descendent object, and casted it for me under the covers.

The full source code for this sample can be found in the PassByRef.zip archive on my SkyDrive: http://tinyurl.com/MarkGilbertSource.  The solution requires Visual Studio 2010, but this works the same way with Visual Studio 2008 (which is what we wrote the MVC application in originally, and where I first saw this behavior).  For the C# application, I’ve included all four attempts as complete copies of the Main() routine.  To try one, simply uncomment it, and comment out the others.

Advertisements

August 24, 2011 - Posted by | Visual Studio/.NET

Sorry, the comment form is closed at this time.

%d bloggers like this: