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
调用约定, TypedReference
types, arglist
, refanytype
, mkanyref
和refanyval
指令)完全logging在Vararg库中的CLI规范(ECMA-335) 。
在Vararg图书馆中定义的内容很明显,它们主要是为了支持可变长度的参数列表,而不是其他的。 在不需要与使用可变参数的外部C代码接口的平台中,variables参数列表几乎没有用处。 因此,Varargs库不是任何CLIconfiguration文件的一部分。 合法的CLI实现可能会select不支持Varargs库,因为它不包含在CLI内核configuration文件中:
4.1.6 Vararg
varargfunction集支持可变长度参数列表和运行时types指针。
如果省略:任何尝试引用
vararg
调用约定的方法或与可变参数方法相关的签名编码(请参阅分区II)都应抛出System.NotImplementedException
exception。 使用CIL指令arglist
,refanytype
,mkrefany
和refanyval
应抛出System.NotImplementedException
exception。 没有指定例外的确切时间。 typesSystem.TypedReference
不需要定义。
更新(回复GetValueDirect
评论):
FieldInfo.GetValueDirect
是FieldInfo.SetValueDirect
不是基类库的一部分。 请注意,.NET Framework类库和基类库之间有区别。 BCL是CLI / C#的一致性实现所需的唯一东西,并logging在ECMA TR / 84中 。 (事实上, FieldInfo
本身是reflection库的一部分,并且不包含在CLI内核configuration文件中)。
只要您在BCL之外使用方法,就会放弃一些可移植性(随着Silverlight和MonoTouch等非.NET CLI实现的出现,这变得越来越重要)。 即使一个实现想要增加与Microsoft .NET Framework类库的兼容性,它也可以简单地提供GetValueDirect
和SetValueDirect
接受TypedReference
而不使TypedReference
专门由运行时处理(基本上,使它们等同于它们的object
而没有性能优势)。
如果他们用C#来logging它,那至less会有一些含义:
- 像任何function一样,它可能会成为新function的障碍,尤其是因为这个function并不适合C#的devise,并且需要运行时types的奇怪语法扩展和特殊处理。
- 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
和朋友的实施细节。 你已经指出了他们的一些巧妙的用途,我认为他们是值得探索的,但是依靠实现细节的通常的警告适用 – 下一个版本可能会破坏你的代码。