c# - 为什么 TypedReference 在幕后?它是如此快速和安全......几乎是神奇的!

标签 c# typedreference

警告:这个问题有点邪门……信教的程序员一向恪守优良作法,请勿阅读。 :)

有谁知道为什么使用TypedReference如此气馁(隐含地,由于缺乏文档)?

我发现它有很好的用途,例如当通过不应该是通用的函数传递通用参数时(如果您需要一个值类型,使用 object 可能会过大或缓慢),当您需要一个不透明的指针时,或者当您需要快速访问数组元素时,您可以在运行时找到其规范(使用 Array.InternalGetReference)。既然 CLR 甚至不允许不正确地使用这种类型,为什么不鼓励这样做呢?好像没有什么不安全之类的……


我发现 TypedReference 的其他用途:

C# 中的“特化”泛型(这是类型安全的):

static void foo<T>(ref T value)
{
    //This is the ONLY way to treat value as int, without boxing/unboxing objects
    if (value is int)
    { __refvalue(__makeref(value), int) = 1; }
    else { value = default(T); }
}

编写使用通用指针的代码(如果使用不当,非常不安全,但如果使用正确,则快速且安全):

//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
    var obj = default(T);
    var tr = __makeref(obj);

    //This is equivalent to shooting yourself in the foot
    //but it's the only high-perf solution in some cases
    //it sets the first field of the TypedReference (which is a pointer)
    //to the address you give it, then it dereferences the value.
    //Better be 10000% sure that your type T is unmanaged/blittable...
    unsafe { *(IntPtr*)(&tr) = address; }

    return __refvalue(tr, T);
}

编写 sizeof 指令的方法版本,偶尔会有用:

static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }

static uint SizeOf<T>()
{
    unsafe 
    {
        TypedReference
            elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
            elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
        unsafe
        { return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
    }
}

编写一个传递“状态”参数的方法,希望避免装箱:

static void call(Action<int, TypedReference> action, TypedReference state)
{
    //Note: I could've said "object" instead of "TypedReference",
    //but if I had, then the user would've had to box any value types
    try
    {
        action(0, state);
    }
    finally { /*Do any cleanup needed*/ }
}

那么为什么这样的使用“不被鼓励”(由于缺乏文档)?有什么特别的安全原因吗?如果它不与指针混合(无论如何都不安全或不可验证),它似乎是完全安全和可验证的......


更新:

显示 TypedReference 确实可以快两倍(或更多)的示例代码:

using System;
using System.Collections.Generic;
static class Program
{
    static void Set1<T>(T[] a, int i, int v)
    { __refvalue(__makeref(a[i]), int) = v; }

    static void Set2<T>(T[] a, int i, int v)
    { a[i] = (T)(object)v; }

    static void Main(string[] args)
    {
        var root = new List<object>();
        var rand = new Random();
        for (int i = 0; i < 1024; i++)
        { root.Add(new byte[rand.Next(1024 * 64)]); }
        //The above code is to put just a bit of pressure on the GC

        var arr = new int[5];
        int start;
        const int COUNT = 40000000;

        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set1(arr, 0, i); }
        Console.WriteLine("Using TypedReference:  {0} ticks",
                          Environment.TickCount - start);
        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set2(arr, 0, i); }
        Console.WriteLine("Using boxing/unboxing: {0} ticks",
                          Environment.TickCount - start);

        //Output Using TypedReference:  156 ticks
        //Output Using boxing/unboxing: 484 ticks
    }
}

(编辑:我编辑了上面的基准测试,因为帖子的最后一个版本使用了代码的调试版本[我忘记将其更改为发布],并且对GC没有压力。这个版本有点多现实,并且在我的系统上,使用 TypedReference 平均要快三倍以上。)

最佳答案

简短回答:可移植性

虽然 __arglist__makeref__refvalue语言扩展并且未记录在 C# 语言规范中,用于在幕后实现它们的构造(vararg 调用约定、TypedReference 类型、arglistrefanytypemkanyrefrefanyval 指令)完美记录在 CLI Specification (ECMA-335) 中在 Vararg 库中。

在 Vararg 库中定义很清楚,它们主要用于支持可变长度参数列表,仅此而已。可变参数列表在不需要与使用可变参数的外部 C 代码交互的平台中几乎没有用处。因此,Varargs 库不是任何 CLI 配置文件的一部分。合法的 CLI 实现可能会选择不支持 Varargs 库,因为它不包含在 CLI 内核配置文件中:

4.1.6 Vararg

The vararg feature set supports variable-length argument lists and runtime-typed pointers.

If omitted: Any attempt to reference a method with the vararg calling convention or the signature encodings associated with vararg methods (see Partition II) shall throw the System.NotImplementedException exception. Methods using the CIL instructions arglist, refanytype, mkrefany, and refanyval shall throw the System.NotImplementedException exception. The precise timing of the exception is not specified. The type System.TypedReference need not be defined.

更新(回复GetValueDirect评论):

FieldInfo.GetValueDirectFieldInfo.SetValueDirect 不是 基类库的一部分。请注意,.NET Framework 类库和基类库之间存在差异。 BCL 是 CLI/C# 的一致性实现所需的唯一东西,记录在 ECMA TR/84 中。 . (事实上​​,FieldInfo 本身是 Reflection 库的一部分,它也不包含在 CLI 内核配置文件中)。

一旦您使用 BCL 之外的方法,您就放弃了一点可移植性(随着 Silverlight 和 MonoTouch 等非 .NET CLI 实现的出现,这一点变得越来越重要)。即使实现想要提高与 Microsoft .NET Framework 类库的兼容性,它也可以简单地提供 GetValueDirectSetValueDirect 获取 TypedReference 而无需进行由运行时专门处理的 TypedReference(基本上,使它们等同于它们的 object 对应物,但没有性能优势)。

如果他们用 C# 记录它,它至少会产生一些影响:

  1. 与任何功能一样,它可能成为新功能的障碍,特别是因为这个功能并不真正适合 C# 的设计,需要奇怪的语法扩展和特殊的类型处理运行时。
  2. C# 的所有实现都必须以某种方式实现此功能,对于根本不在 CLI 之上运行或在没有 Varargs 的 CLI 之上运行的 C# 实现来说,这不一定是微不足道的/可能的。

关于c# - 为什么 TypedReference 在幕后?它是如此快速和安全......几乎是神奇的!,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4764573/

相关文章:

c# - ReadToEndAsync 是阻塞的,如何让它异步运行?

c# - 为什么 ASP.NET 的“记住我”功能如此健忘?

c# - 使 TypedReference 在方法 block 之外保持事件状态而不返回它

c# - 使用反射访问对象中结构的字段

c# - 当我将它存储到静态变量时,我无法访问 OperationContext.Current

c# - 当用户为文本框选择正确的自动完成选项时设置其他字段

c# - Entity Framework 核心 : Adding an object with a List to a DbContext results in an empty List