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.GetValueDirect
是FieldInfo.SetValueDirect
是不是基类库的一部分.请注意,.NET Framework类库和基类库之间存在差异.BCL是符合CLI/C#实现的唯一要求,并在ECMA TR/84中有记录.(实际上,FieldInfo
它本身是Reflection库的一部分,也没有包含在CLI内核配置文件中).
一旦你在BCL之外使用一种方法,你就会放弃一些可移植性(随着像Silverlight和MonoTouch这样的非.NET CLI实现的出现,这变得越来越重要).即使实现想增加与Microsoft .NET Framework类库compatiblility,它可以简单地提供GetValueDirect
并SetValueDirect
采取了TypedReference
不使TypedReference
运行时特殊处理(基本上,这使得它们等同于他们的object
同行没有性能优势).
如果他们用C#记录它,它至少会产生一些影响:
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
和朋友实施细节.你已经指出了一些巧妙的用途,我认为它们值得探索,但是依赖于实现细节的常见警告适用 - 下一个版本可能会破坏你的代码.