警告:这个问题有点异端…宗教程序员始终恪守良好实践,请不要阅读它。:)
有谁知道为什么不鼓励使用TypedReference(隐式地,由于缺乏文档)?
我发现它有很好的用途,例如当通过不应该是泛型的函数传递泛型参数时(object如果需要使用值类型,则使用over可能会导致过时或缓慢),需要不透明指针时,或者用于何时需要快速访问数组元素的信息,您可以在运行时找到其规格(使用Array.InternalGetReference)。由于CLR甚至不允许这种类型的错误使用,为什么不鼓励这样做?它似乎并不安全或没有任何…
object
Array.InternalGetReference
我发现的其他用途TypedReference:
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,这有时可能有用:
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类型,arglist,refanytype,mkanyref,和refanyval指令)是完全在记录CLI规范(ECMA-335)在在 可变参数库 。
__arglist
__makeref
__refvalue
vararg
arglist
refanytype
mkanyref
refanyval
通过在Vararg库中进行定义,可以很清楚地看出它们主要是为了支持可变长度的参数列表,而没有太多其他功能。变量参数列表在不需要与使用varargs的外部C代码进行接口的平台中几乎没有用。因此,Varargs库不是任何CLI配置文件的一部分。合法的CLI实现可能选择不支持Varargs库,因为它不包含在CLI内核配置文件中:
4.1.6瓦拉格 所述 可变参数的功能集 支持可变长度参数列表和运行时类型的指针。 如果省略: 尝试使用vararg调用约定或与vararg方法关联的签名编码(请参阅分区II)引用方法的任何尝试都将引发System.NotImplementedException异常。使用CIL指令的方法arglist,refanytype,mkrefany,并refanyval应抛出System.NotImplementedException异常。没有指定异常的确切时间。类型System.TypedReference不需要定义。
所述 可变参数的功能集 支持可变长度参数列表和运行时类型的指针。
如果省略: 尝试使用vararg调用约定或与vararg方法关联的签名编码(请参阅分区II)引用方法的任何尝试都将引发System.NotImplementedException异常。使用CIL指令的方法arglist,refanytype,mkrefany,并refanyval应抛出System.NotImplementedException异常。没有指定异常的确切时间。类型System.TypedReference不需要定义。
System.NotImplementedException
mkrefany
System.TypedReference
GetValueDirect
FieldInfo.GetValueDirect是FieldInfo.SetValueDirect是 不是 基类库的一部分。请注意,.NET Framework类库和基类库之间存在差异。BCL是实现CLI / C#的唯一必要条件,并且在ECMA TR / 84中进行了记录。(实际上,FieldInfo它本身是反射库的一部分,并且也不包含在CLI内核配置文件中)。
FieldInfo.GetValueDirect
FieldInfo.SetValueDirect
FieldInfo
一旦在BCL之外使用了一种方法,就会放弃一些可移植性(随着非Silverlight和MonoTouch等非.NET CLI实现的出现,这一点变得越来越重要)。即使实现想要增加与Microsoft .NET Framework类库的兼容性,它也可以简单地提供GetValueDirect并SetValueDirect接受一个,TypedReference而不必进行TypedReference运行时的特殊处理(基本上,使它们等效于它们的object对应物,而没有性能上的好处)。
SetValueDirect
如果他们用C#对其进行了记录,则至少会产生以下几点影响: