警告:这个问题有点异端……宗教程序员总是遵守良好的实践,请不要阅读它。 :)
有谁知道为什么使用类型化参考如此沮丧(隐含地,由于缺乏文档)?
我发现它有很大的用途,例如当通过不应该是泛型的函数传递泛型参数时(当使用object
如果您需要值类型,则可能会过度或缓慢),当您需要不透明指针时,或者当您需要快速访问数组元素时,您可以在运行时找到其规格(使用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); }
}
编写使用通用指针的代码(这是very如果使用不当则不安全,但如果使用正确则快速且安全):
//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);
}
写一个method的版本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*/ }
}
那么为什么这样的使用会被“劝阻”(由于缺乏文档)?有什么特别的安全原因吗?如果它不与指针混合(无论如何都不安全或不可验证),它似乎是完全安全和可验证的......
Update:
示例代码确实表明,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
一般。)