Aviso: Esta pergunta é um pouco herética ... programadores religiosos sempre respeitando as boas práticas, por favor, não a leia. :)
Alguém sabe por que o uso de TypedReference é tão desencorajado (implicitamente, por falta de documentação)?
Encontrei ótimos usos para ele, como ao passar parâmetros genéricos por funções que não devem ser genéricas (ao usar um object
pode ser um exagero ou lento, se você precisar de um tipo de valor), para quando você precisar de um ponteiro opaco ou para quando você precisar acessar rapidamente um elemento de uma matriz, cujas especificações você encontra em tempo de execução (usando Array.InternalGetReference
). Como o CLR nem sequer permite o uso incorreto desse tipo, por que é desencorajado? Não parece ser inseguro ou algo assim ...
Outros usos que encontrei para TypedReference
:
Genéricos "especializados" em C # (isso é seguro para o tipo):
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); }
}
Escrevendo código que funciona com ponteiros genéricos (isso é muito inseguro se mal utilizado, mas rápido e seguro se usado corretamente):
//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);
}
Escrevendo uma versão do método da sizeof
instrução, que pode ser ocasionalmente ú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)); }
}
}
Escrevendo um método que passa um parâmetro "state" que deseja evitar o boxe:
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*/ }
}
Então, por que usos como esse são "desencorajados" (por falta de documentação)? Algum motivo de segurança específico? Parece perfeitamente seguro e verificável se não estiver misturado com ponteiros (que não são seguros ou verificáveis) ...
Atualizar:
Código de exemplo para mostrar que, de fato, TypedReference
pode ser duas vezes mais rápido (ou mais):
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: editei o benchmark acima, pois a última versão do post usou uma versão de depuração do código [esqueci de alterá-lo para liberar] e não pressionei o GC. Essa versão é um pouco mais realista e no meu sistema, é três vezes mais rápido, TypedReference
em média.)
int
-> DockStyle
). Esta caixa é real e é quase dez vezes mais lenta.
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Não importa o que eu tente (incluindo diferentes maneiras de fazer o tempo), o boxe / unboxing ainda é mais rápido no meu sistema.