C# ref is it like a pointer in C/C++ or a reference in C++?

Secret picture Secret · Apr 25, 2013 · Viewed 45.2k times · Source

I'm working with the ref and don't understand clearly "Is it like a pointer as in C/C++ or it's like a reference in C++?"

Why did I ask such a weak question as you thought for a moment? Because, when I'm reading C#/.NET books, msdn or talking to C# developers I'm becoming confused by the following reasons:

  • C# developers suggest NOT to use ref in the arguments of a function, e.g. ...(ref Type someObject) doesn't smell good for them and they suggest ...(Type someObject), I really don't understand clearly this suggestion. The reasons I heard: better to work with the copy of object, then use it as a return value, not to corrupt memory by a reference etc... Often I hear such explanation about DB connection objects. As on my plain C/C++ experience, I really don't understand why to use a reference is a bad stuff in C#? I control the life of object and its memory allocations/re-allocations etc... I read in books and forums only advises it's bad, because you can corrupt your connection and cause a memory leak by a reference lose, so I control the life of object, I may control manually what I really want, so why is it bad?
  • Nowadays reading different books and talk to different people, I don't clearly understand is ref a pointer (*) or a reference like in C++ by & ? As I remember pointers in C/C++ always do allocate a space with a size of void* type - 4 bytes (the valid size depends on architecture), where hosts an address to a structure or variable. In C++ by passing a reference & there is no new allocations from the heap/stack and you work with already defined objects in memory space and there is no sub-allocating memory for a pointer externally like in plain C. So what's the ref in C#? Does .NET VM handle it like a pointer in plain C/C++ and its GC allocates temporary space for a pointer or it does a work like reference in C++? Does ref work only with a managed types correctly or for value types like bool, int it's better to switch an unsafe code and pass through a pointer in unmanaged style?

Answer

Billy ONeal picture Billy ONeal · Apr 25, 2013

In C#, when you see something referring to a reference type (that is, a type declared with class instead of struct), then you're essentially always dealing with the object through a pointer. In C++, everything is a value type by default, whereas in C# everything is a reference type by default.

When you say "ref" in the C# parameter list, what you're really saying is more like a "pointer to a pointer." You're saying that, in the method, that you want to replace not the contents of the object, but the reference to the object itself, in the code calling your method.

Unless that is your intent, then you should just pass the reference type directly; in C#, passing reference types around is cheap (akin to passing a reference in C++).

Learn/understand the difference between value types and reference types in C#. They're a major concept in that language and things are going to be really confusing if you try to think using the C++ object model in C# land.

The following are essentially semantically equivalent programs:

#include <iostream>

class AClass
{
    int anInteger;
public:
    AClass(int integer)
        : anInteger(integer)
    {  }

    int GetInteger() const
    {
        return anInteger;
    }

    void SetInteger(int toSet)
    {
        anInteger = toSet;
    }
};

struct StaticFunctions
{
    // C# doesn't have free functions, so I'll do similar in C++
    // Note that in real code you'd use a free function for this.

    static void FunctionTakingAReference(AClass *item)
    {
        item->SetInteger(4);
    }

    static void FunctionTakingAReferenceToAReference(AClass **item)
    {
        *item = new AClass(1729);
    }
};

int main()
{
    AClass* instanceOne = new AClass(6);
    StaticFunctions::FunctionTakingAReference(instanceOne);
    std::cout << instanceOne->GetInteger() << "\n";

    AClass* instanceTwo;
    StaticFunctions::FunctionTakingAReferenceToAReference(&instanceTwo);
    // Note that operator& behaves similar to the C# keyword "ref" at the call site.
    std::cout << instanceTwo->GetInteger() << "\n";

    // (Of course in real C++ you're using std::shared_ptr and std::unique_ptr instead,
    //  right? :) )
    delete instanceOne;
    delete instanceTwo;
}

And for C#:

using System;

internal class AClass
{
    public AClass(int integer)
        : Integer(integer)
    {  }

    int Integer { get; set; }
}

internal static class StaticFunctions
{
    public static void FunctionTakingAReference(AClass item)
    {
        item.Integer = 4;
    }

    public static void FunctionTakingAReferenceToAReference(ref AClass item)
    {
        item = new AClass(1729);
    }
}

public static class Program
{
    public static void main()
    {
        AClass instanceOne = new AClass(6);
        StaticFunctions.FunctionTakingAReference(instanceOne);
        Console.WriteLine(instanceOne.Integer);

        AClass instanceTwo  = new AClass(1234); // C# forces me to assign this before
                                                // it can be passed. Use "out" instead of
                                                // "ref" and that requirement goes away.
        StaticFunctions.FunctionTakingAReferenceToAReference(ref instanceTwo);
        Console.WriteLine(instanceTwo.Integer);
    }
}