__makeref 作为在 C# 中获取参考值的一种方式?

Roy*_*mir 4 c# clr

我见过这段代码,用于显示参考值:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

结果(如预期):

在此输入图像描述

(我知道字符串实习在这里起作用,但这不是问题)。

问题:

虽然看起来它确实完成了这项工作,但使用__makeref这是在 C# 中获取参考值的正确方法吗?

或者是否有任何情况下这个 ^ 会失败......?

Mar*_*ell 6

虽然看起来它确实完成了这项工作,但使用__makeref这是在 C# 中获取参考值的正确方法吗?

在 C# 中没有“正确的方法”来做到这一点 - 这不是你应该尝试做的事情,但是:就它正在做的事情而言- 这本质上依赖于内部布局TypedReference和类型强制; 它会起作用(只要TypedReference内部不改变 - 例如Type和Value字段的顺序发生变化),但是......它很讨厌。

有一个更直接的方法;在 IL 中,您可以默默地从托管指针转换为非托管指针。这意味着你可以做一些令人讨厌的事情,比如:

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));
}
Run Code Online (Sandbox Code Playgroud)

现在你可以这样做:

var ptr = new IntPtr(GetRef(o));
Console.WriteLine(ptr);
Run Code Online (Sandbox Code Playgroud)

这太可怕了,你永远不应该这样做 - 当然 GC 可以在你不看的时候(甚至在你看的时候)移动东西,但是......它有效。

ref-emit 是否比未记录和不受支持的语言功能(例如__makeref类型强制)“更好”:这是一个有争议的问题。希望纯粹是学术辩论!