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

Meh*_*dad 127 c# typedreference

警告:这个问题有点异端......宗教程序员总是遵守良好做法,请不要阅读.:)

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

我已经找到了很好的用途,例如通过不应该是通用的函数传递泛型参数(当使用object可能是过度杀手或缓慢,如果你需要值类型时),当你需要一个不透明的指针时,或者当你需要快速访问数组元素时,你在运行时找到它的规范(使用Array.InternalGetReference).由于CLR甚至不允许错误使用此类型,为什么不鼓励?它似乎不安全或任何东西......


我找到的其他用途TypedReference:

C#中的"Specializing"泛型(这是类型安全的):

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

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

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

编写指令的方法版本,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)); }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

那么为什么这样的用途"气馁"(缺乏文档)?任何特殊的安全原因?如果它没有与指针混合(无论如何都不安全或可验证),它似乎是非常安全和可验证的......


更新:

示例代码显示确实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
    }
}
Run Code Online (Sandbox Code Playgroud)

(编辑:我编辑了上面的基准测试,因为帖子的最后一个版本使用了代码的调试版本[我忘了将其更改为发布],并且没有对GC施加压力.这个版本更加真实,并且在我的系统TypedReference上,平均速度提高了三倍多.)

Meh*_*ari 42

简短回答:便携性.

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

在Vararg库中定义,很明显它们主要用于支持可变长度的参数列表而不是其他.变量参数列表在不需要与使用varargs的外部C代码接口的平台中几乎没有用处.因此,Varargs库不是任何CLI配置文件的一部分.合法的CLI实现可能选择不支持Varargs库,因为它未包含在CLI内核配置文件中:

4.1.6 Vararg

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

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

更新(回复GetValueDirect评论):

FieldInfo.GetValueDirectFieldInfo.SetValueDirect不是基类库的一部分.请注意,.NET Framework类库和基类库之间存在差异.BCL是符合CLI/C#实现的唯一要求,并在ECMA TR/84中有记录.(实际上,FieldInfo它本身是Reflection库的一部分,也没有包含在CLI内核配置文件中).

一旦你在BCL之外使用一种方法,你就会放弃一些可移植性(随着像Silverlight和MonoTouch这样的非.NET CLI实现的出现,这变得越来越重要).即使实现想增加与Microsoft .NET Framework类库compatiblility,它可以简单地提供GetValueDirectSetValueDirect采取了TypedReference不使TypedReference运行时特殊处理(基本上,这使得它们等同于他们的object同行没有性能优势).

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

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

  • 便携性的好论据,+ 1.但是`FieldInfo.GetValueDirect`和`FieldInfo.SetValueDirect`怎么样?它们是BCL的一部分,并且要使用它们*需要*`TypedReference`,所以这不是基本上强制`TypedReference`始终被定义,无论语言规范如何?(另外注意:即使关键字不存在,只要存在指令,你仍然可以通过动态发布方法来访问它们......所以只要你的平台与C库交互,就可以使用这些, C#是否有关键字.) (4认同)

P D*_*ddy 15

嗯,我不是Eric Lippert,所以我不能直接谈论微软的动机,但如果我冒险猜测,我会说TypedReference等等.没有详细记录,因为坦白说,你不需要它们.

您提到的这些功能的每一次使用都可以在没有它们的情况下完成,尽管在某些情况下会降低性能.但是C#(和一般的.NET)并不是一种高性能语言.(我猜测"比Java更快"是性能目标.)

这并不是说没有提供某些性能考虑因素.实际上,诸如指针stackalloc和某些优化的框架功能之类的特征在很大程度上存在以在某些情况下提高性能.

我所说的泛型具有类型安全的主要好处,也可以TypedReference通过避免装箱和拆箱来提高性能.事实上,我想知道你为什么喜欢这个:

static void call(Action<int, TypedReference> action, TypedReference state){
    action(0, state);
}
Run Code Online (Sandbox Code Playgroud)

对此:

static void call<T>(Action<int, T> action, T state){
    action(0, state);
}
Run Code Online (Sandbox Code Playgroud)

正如我所看到的那样,权衡取决于前者需要更少的JIT(并且遵循更少的内存),而后者更熟悉,我认为,稍快一些(通过避免指针解除引用).

我打电话TypedReference和朋友实施细节.你已经指出了一些巧妙的用途,我认为它们值得探索,但是依赖于实现细节的常见警告适用 - 下一个版本可能会破坏你的代码.

  • 嗯......"你不需要它们" - 我应该看到即将到来.:-)这是真的但它也不是真的.你认为什么是"需要"?例如,扩展方法是否真的"需要"?关于在`call()中使用泛型的问题:这是因为代码并不总是如此紧密 - 我更多地提到了一个更像'IAsyncResult.State`的例子,其中引入泛型将不可行因为突然间它会为所涉及的每个类/方法引入泛型.但答案是+1,特别是指出"比Java更快"的部分.:] (4认同)