Search code examples
c#propertiesref

Properties and ref


Consider class A with private field _data of any type (eg. int) and property Data to work with that field:

public class A
{
    private int _data;

    public int Data
    {
        get => _data;
        set => _data = value;
    }

    // Constructor is redudant, I created that for testing purposes...
    public A(int data)
    {
        _data = data;
    }
}

Now consider class B with the same private field _data and property Data that only returns reference to our field:

public class B
{
    private int _data;

    public ref int Data
    {
        get => ref _data;
    }

    // Constructor is redudant, I created that for testing purposes...
    public B(int data)
    {
        _data = data;
    }
}

And now the question for which I can't find the answer: Why I'm able to change the value of _data in the instance of class B if there is no set modifier for Data?

B b = new B(50);

// This line doesn't produce any warnings or errors
b.Data = 100;

Console.WriteLine(b.Data == 100); // True

Does that work like pointers in C/C++ and compiler understands that this property is just a pointer, so it automatically assings value to what this pointer point to (without any special operators and/or casting) or I'm missing something?

I couldn't find an answer on this on learn.microsoft.com (searching both for Properties and ref), so any explanation would be appreciated.


Solution

  • Indeed, using ref we get a reference and we can change the value.

    Considering this code:

    A a = new A();
    B b = new B();
    
    a.Data = 100;
    b.Data = 200;
    

    The IL generated code is:

    // A a = new A();
    IL_0001: newobj instance void ConsoleApp.A::.ctor()
    IL_0006: stloc.0
    
    // B b = new B();
    IL_0007: newobj instance void ConsoleApp.B::.ctor()
    IL_000c: stloc.1
    
    // a.Data = 100;
    IL_000d: ldloc.0
    IL_000e: ldc.i4.s 100
    IL_0010: callvirt instance void ConsoleApp.A::set_Data(int32)
    
    // b.Data = 200;
    IL_0016: ldloc.1
    IL_0017: callvirt instance int32& ConsoleApp.B::get_Data()
    IL_001c: ldc.i4 200
    IL_0021: stind.i4
    
    • Using the setter, we call a method, and pass the int32 value as a parameter.

    • Using the by ref getter, we got the reference, to an integer here : int32&.

    Properties having a getter by ref can't have a setter, says the compiler.

    Because it is useless and redundant.

    Thus, a treatment can only be done in a by ref getter, for example to return one or the other reference according to certain conditions:

    CurrentColor of a control that is Enabled will returns ColorEnabled, else ColorDisabled.


    The native x86 machine code transcribed for a.Data = 100 is:

    mov         rcx,qword ptr [rbp+40h]  
    mov         edx,64h  
    cmp         dword ptr [rcx],ecx  
    call        00007FF7E93B0568  
    

    For b.Data = 200 is:

    mov         rcx,qword ptr [rbp+38h]  
    cmp         dword ptr [rcx],ecx  
    call        00007FF7E93B0580  
    mov         qword ptr [rbp+20h],rax  
    mov         rax,qword ptr [rbp+20h]  
    mov         dword ptr [rax],0C8h