Avvertenza: questa domanda è un po 'eretica ... i programmatori religiosi rispettano sempre le buone pratiche, per favore non leggerlo. :)
Qualcuno sa perché l'uso di TypedReference è così scoraggiato (implicitamente, dalla mancanza di documentazione)?
Ho trovato grandi usi per questo, come quando si passano parametri generici attraverso funzioni che non dovrebbero essere generiche (quando si usa un object
potrebbe essere eccessivo o lento, se è necessario un tipo di valore), per quando è necessario un puntatore opaco, oppure per quando è necessario accedere rapidamente a un elemento di un array, le cui specifiche sono disponibili in fase di esecuzione (utilizzando Array.InternalGetReference
). Poiché il CLR non consente nemmeno un utilizzo errato di questo tipo, perché è scoraggiato? Non sembra essere pericoloso o niente ...
Altri usi che ho trovato per TypedReference
:
"Specializzazione" generici in C # (questo è sicuro per i tipi):
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); }
}
Scrivere codice che funziona con puntatori generici (questo è molto pericoloso se usato in modo improprio, ma veloce e sicuro se usato correttamente):
//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);
}
Scrivere una versione del metodosizeof
dell'istruzione, che può essere occasionalmente utile:
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)); }
}
}
Scrivere un metodo che passa un parametro "state" che vuole evitare il pugilato:
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*/ }
}
Allora perché gli usi come questo "scoraggiati" (per mancanza di documentazione)? Qualche particolare motivo di sicurezza? Sembra perfettamente sicuro e verificabile se non è mescolato con puntatori (che non sono sicuri o verificabili comunque) ...
Aggiornare:
Codice di esempio per mostrare che, in effetti, TypedReference
può essere due volte più veloce (o più):
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
}
}
(Modifica: ho modificato il benchmark sopra, poiché l'ultima versione del post ha usato una versione di debug del codice [ho dimenticato di cambiarlo per rilasciarlo] e non ho fatto pressione sul GC. Questa versione è un po 'più realistica e sul mio sistema, è TypedReference
in media più di tre volte più veloce ).
int
-> DockStyle
). Questo box per davvero, ed è quasi dieci volte più lento.
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Indipendentemente da ciò che provo (compresi diversi modi per eseguire il cronometraggio) la boxe / unboxing è ancora più veloce sul mio sistema.