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

警告:这个问题有点邪教…宗教程序员总是遵守良好的做法,请不要读它。 🙂

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

我发现它有很好的用处,比如当通过不应该是generics的函数传递generics参数时(如果你需要一个值types,使用object可能是矫枉过正或慢),当你需要一个不透明的指针时,或者当您需要快速访问数组的元素时,您可以在运行时查找其规格(使用Array.InternalGetReference )。 由于CLR甚至不允许这种types的不正确的使用,为什么不鼓励? 它似乎不是不安全或任何东西…


我发现TypedReference其他用途:

C#中的“专门化”generics(这是types安全的):

 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); } } 

编写与generics指针一起工作的代码(如果误用,这是非常不安全的,但是如果使用正确的话,则是快速和安全的):

 //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*/ } } 

那么为什么这样的使用“不鼓励”(由于缺乏文档)呢? 任何特殊的安全原因? 它似乎是完全安全的,如果它没有与指针混合(这是不安全的或无论如何可以validation)…


更新:

示例代码显示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 } } 

(编辑:我编辑了上面的基准,因为最后一个版本的post使用了代码的debugging版本(我忘了把它改成release),并且没有给GC一个压力,这个版本更实际一些,在我的系统上,它的平均速度比TypedReference快三倍以上。)

简短的回答: 可移植性

虽然__arglist__makeref__refvalue语言扩展 ,并且在C#语言规范中没有__refvalue ,但是用于实现它们的构造( vararg调用约定, TypedReferencetypes, arglistrefanytypemkanyrefrefanyval指令)完全logging在Vararg库中的CLI规范(ECMA-335) 。

在Vararg图书馆中定义的内容很明显,它们主要是为了支持可变长度的参数列表,而不是其他的。 在不需要与使用可变参数的外部C代码接口的平台中,variables参数列表几乎没有用处。 因此,Varargs库不是任何CLIconfiguration文件的一部分。 合法的CLI实现可能会select不支持Varargs库,因为它不包含在CLI内核configuration文件中:

4.1.6 Vararg

varargfunction集支持可变长度参数列表和运行时types指针。

如果省略:任何尝试引用vararg调用约定的方法或与可变参数方法相关的签名编码(请参阅分区II)都应抛出System.NotImplementedExceptionexception。 使用CIL指令arglistrefanytypemkrefanyrefanyval应抛出System.NotImplementedExceptionexception。 没有指定例外的确切时间。 typesSystem.TypedReference不需要定义。

更新(回复GetValueDirect评论):

FieldInfo.GetValueDirectFieldInfo.SetValueDirect 不是基类库的一部分。 请注意,.NET Framework类库和基类库之间有区别。 BCL是CLI / C#的一致性实现所需的唯一东西,并logging在ECMA TR / 84中 。 (事实上​​, FieldInfo本身是reflection库的一部分,并且不包含在CLI内核configuration文件中)。

只要您在BCL之外使用方法,就会放弃一些可移植性(随着Silverlight和MonoTouch等非.NET CLI实现的出现,这变得越来越重要)。 即使一个实现想要增加与Microsoft .NET Framework类库的兼容性,它也可以简单地提供GetValueDirectSetValueDirect接受TypedReference而不使TypedReference专门由运行时处理(基本上,使它们等同于它们的object而没有性能优势)。

如果他们用C#来logging它,那至less会有一些含义:

  1. 像任何function一样,它可能会成为新function的障碍,尤其是因为这个function并不适合C#的devise,并且需要运行时types的奇怪语法扩展和特殊处理。
  2. C#的所有实现都必须以某种方式实现此function,对于不在CLI之上运行的C#实现,或者在没有可变参数的CLI的基础上运行,并不一定是微不足道的/可能的。

那么,我不是Eric Lippert,所以我不能直接说出微软的动机,但是如果我冒险猜测的话,那么TypedReference等人 没有很好的logging,因为,坦率地说,你不需要他们。

您提到的这些function的使用都可以在没有这些function的情况下完成,尽pipe在某些情况下性能会受到影响。 但是C#(和.NET一般)并不是一个高性能的语言。 (我猜测“比Java更快”是性能目标。)

这并不是说没有提供某些性能方面的考虑。 事实上,指针, stackalloc和某些优化的框架function等特性主要是为了提高某些情况下的性能。

generics,我会说有types安全的主要好处,也提高性能类似于TypedReference避免拳击和拆箱。 事实上,我想知道为什么你更喜欢这个:

 static void call(Action<int, TypedReference> action, TypedReference state){ action(0, state); } 

对此:

 static void call<T>(Action<int, T> action, T state){ action(0, state); } 

正如我所看到的,权衡是前者需要更less的JIT(并且随之而来,更less的内存),而后者更为熟悉,而且我认为,略快(通过避免指针取消引用)。

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