一尘不染

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

c#

警告:这个问题有点异端…宗教程序员始终恪守良好实践,请不要阅读它。:)

有谁知道为什么不鼓励使用TypedReference(隐式地,由于缺乏文档)?

我发现它有很好的用途,例如当通过不应该是泛型的函数传递泛型参数时(object如果需要使用值类型,则使用over可能会导致过时或缓慢),需要不透明指针时,或者用于何时需要快速访问数组元素的信息,您可以在运行时找到其规格(使用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上,平均速度要快三倍以上。)


阅读 268

收藏
2020-05-19

共1个答案

一尘不染

简短答案: 可移植性

同时__arglist__makeref__refvalue语言扩展
,并在C#语言规范没有证件,用于实现它们的罩下的构建体(vararg调用约定,TypedReference类型,arglistrefanytypemkanyref,和refanyval指令)是完全在记录CLI规范(ECMA-335)在在 可变参数库

通过在Vararg库中进行定义,可以很清楚地看出它们主要是为了支持可变长度的参数列表,而没有太多其他功能。变量参数列表在不需要与使用varargs的外部C代码进行接口的平台中几乎没有用。因此,Varargs库不是任何CLI配置文件的一部分。合法的CLI实现可能选择不支持Varargs库,因为它不包含在CLI内核配置文件中:

4.1.6瓦拉格

所述 可变参数的功能集 支持可变长度参数列表和运行时类型的指针。

如果省略:
尝试使用vararg调用约定或与vararg方法关联的签名编码(请参阅分区II)引用方法的任何尝试都将引发System.NotImplementedException异常。使用CIL指令的方法arglistrefanytypemkrefany,并refanyval应抛出System.NotImplementedException异常。没有指定异常的确切时间。类型System.TypedReference不需要定义。

更新(回复GetValueDirect评论):

FieldInfo.GetValueDirectFieldInfo.SetValueDirect不是 基类库的一部分。请注意,.NET
Framework类库和基类库之间存在差异。BCL是实现CLI / C#的唯一必要条件,并且在ECMA TR /
84中进行了记录
。(实际上,FieldInfo它本身是反射库的一部分,并且也不包含在CLI内核配置文件中)。

一旦在BCL之外使用了一种方法,就会放弃一些可移植性(随着非Silverlight和MonoTouch等非.NET
CLI实现的出现,这一点变得越来越重要)。即使实现想要增加与Microsoft .NET
Framework类库的兼容性,它也可以简单地提供GetValueDirectSetValueDirect接受一个,TypedReference而不必进行TypedReference运行时的特殊处理(基本上,使它们等效于它们的object对应物,而没有性能上的好处)。

如果他们用C#对其进行了记录,则至少会产生以下几点影响:

  1. 像任何功能一样,它 可能会 成为新功能的障碍,尤其是因为该功能实际上并不适合C#的设计,并且需要怪异的语法扩展和运行时对类型的特殊处理。
  2. C#的所有实现都必须以某种方式实现此功能,对于完全不在CLI之上运行或在没有Varargs的CLI之上运行的C#实现而言,它不一定是琐碎/可能的。
2020-05-19