Advertencia: esta pregunta es un poco herética ... los programadores religiosos siempre respetan las buenas prácticas, por favor no la lean. :)
¿Alguien sabe por qué se desaconseja tanto el uso de TypedReference (implícitamente, por falta de documentación)?
He encontrado excelentes usos para él, como al pasar parámetros genéricos a través de funciones que no deberían ser genéricas (cuando se usa un object
puede ser excesivo o lento, si necesita un tipo de valor), para cuando necesita un puntero opaco, o para cuando necesita acceder a un elemento de una matriz rápidamente, cuyas especificaciones encuentra en tiempo de ejecución (usando Array.InternalGetReference
). Dado que el CLR ni siquiera permite el uso incorrecto de este tipo, ¿por qué se desaconseja? No parece ser inseguro ni nada ...
Otros usos que he encontrado para TypedReference
:
Genéricos "especializados" en C # (esto es de tipo seguro):
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); }
}
Escribir código que funcione con punteros genéricos (esto es muy inseguro si se usa mal, pero rápido y seguro si se usa correctamente):
//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);
}
Escribir una versión del método de la sizeof
instrucción, que en ocasiones puede ser útil:
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)); }
}
}
Escribir un método que pase un parámetro de "estado" que quiera evitar el boxeo:
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*/ }
}
Entonces, ¿por qué se desalientan usos como este (por falta de documentación)? ¿Alguna razón de seguridad en particular? Parece perfectamente seguro y verificable si no se mezcla con punteros (que de todos modos no son seguros ni verificables) ...
Actualizar:
Código de muestra para mostrar que, de hecho, TypedReference
puede ser el doble de rápido (o más):
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
}
}
(Editar: edité el punto de referencia anterior, ya que la última versión de la publicación usaba una versión de depuración del código [olvidé cambiarlo para liberarlo], y no presioné al GC. Esta versión es un poco más realista, y en mi sistema, es más de tres veces más rápido con TypedReference
un promedio).
int
-> DockStyle
). Esta cajas de verdad, y es casi diez veces más lenta.
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. No importa lo que intente (incluidas las diferentes formas de cronometrar), el boxeo / unboxing es aún más rápido en mi sistema.