Search code examples
c#clr

__makeref as a way to get a reference value in C#?


I've seen this code which was used showing the reference value :

static void Main(string[] args)
{
    string s1 = "ab";
    string s2 = "a"+"b";
    string s3 = new StringBuilder("a").Append("b").ToString();

    Console.WriteLine(GetMemoryAddress(s1));
    Console.WriteLine(GetMemoryAddress(s2));
    Console.WriteLine(GetMemoryAddress(s3));
}

static IntPtr GetMemoryAddress(object s1)
{
    unsafe
    {
        TypedReference tr = __makeref(s1);
        IntPtr ptr = **(IntPtr**) (&tr);
        return ptr;
    }
}

Result (as expected):

enter image description here

(I know that string interning kicks in here, but that's not the question).

Question:

Although it seems that it does do the job, Does using __makeref is this the right way of getting the reference value in c#?

Or are there any situations in which this ^ would fail ....?


Solution

  • Although it seems that it does do the job, Does using __makeref is this the right way of getting the reference value in c#?

    There is no "right way" of doing this in C# - it isn't something you're meant to try and do, but: in terms of what it is doing - this is essentially relying on the internal layout of TypedReference and a type coercion; it'll work (as long as TypedReference doesn't change internally - for example the order of the Type and Value fields changes), but... it is nasty.

    There is a more direct approach; in IL, you can convert from a managed pointer to an unmanaged pointer silently. Which means you can do something nasty like:

    unsafe delegate void* RefToPointer(object obj);
    static RefToPointer GetRef { get; } = MakeGetRef();
    static RefToPointer MakeGetRef()
    {
        var dm = new DynamicMethod("evil", typeof(void*), new[] { typeof(object) });
        var il = dm.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        return (RefToPointer)dm.CreateDelegate(typeof(RefToPointer));
    }
    

    and now you can just do:

    var ptr = new IntPtr(GetRef(o));
    Console.WriteLine(ptr);
    

    This is horrible, and you should never do it - and of course the GC can move things while you're not looking (or even while you are looking), but... it works.

    Whether ref-emit is "better" than undocumented and unsupported language features like __makeref and type-coercion: is a matter of some debate. Hopefully purely academic debate!