Warnung: Diese Frage ist etwas ketzerisch ... religiöse Programmierer halten sich immer an gute Praktiken, bitte lesen Sie sie nicht. :) :)
Weiß jemand, warum von der Verwendung von TypedReference so abgeraten wird (implizit aufgrund fehlender Dokumentation)?
Ich habe großartige Verwendungsmöglichkeiten dafür gefunden, z. B. wenn generische Parameter über Funktionen übergeben werden, die nicht generisch sein sollten (wenn Sie einen verwenden, der object
möglicherweise übertrieben oder langsam ist, wenn Sie einen Werttyp benötigen), wenn Sie einen undurchsichtigen Zeiger benötigen, oder für den Fall, dass Sie schnell auf ein Element eines Arrays zugreifen müssen, dessen Spezifikationen Sie zur Laufzeit finden (mit Array.InternalGetReference
). Warum wird davon abgeraten, da die CLR nicht einmal eine falsche Verwendung dieses Typs zulässt? Es scheint nicht unsicher zu sein oder so ...
Andere Verwendungen, die ich gefunden habe für TypedReference
:
"Spezialisierung" von Generika in C # (dies ist typsicher):
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); }
}
Schreiben von Code, der mit generischen Zeigern funktioniert (dies ist sehr unsicher, wenn es missbraucht wird, aber schnell und sicher, wenn es richtig verwendet wird):
//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);
}
Schreiben einer Methodenversion der sizeof
Anweisung, die gelegentlich nützlich sein kann:
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)); }
}
}
Schreiben einer Methode, die einen "state" -Parameter übergibt, der das Boxen vermeiden möchte:
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*/ }
}
Warum werden solche Verwendungen "entmutigt" (mangels Dokumentation)? Irgendwelche besonderen Sicherheitsgründe? Es scheint vollkommen sicher und überprüfbar zu sein, wenn es nicht mit Zeigern gemischt ist (die sowieso nicht sicher oder überprüfbar sind) ...
Aktualisieren:
Beispielcode, der zeigt, dass er tatsächlich TypedReference
doppelt so schnell (oder mehr) sein kann:
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
}
}
(Bearbeiten: Ich habe den obigen Benchmark bearbeitet, da in der letzten Version des Beitrags eine Debug-Version des Codes verwendet wurde [ich habe vergessen, ihn in Release zu ändern] und keinen Druck auf den GC ausgeübt hat. Diese Version ist etwas realistischer und auf meinem System ist es TypedReference
im Durchschnitt mehr als dreimal schneller .)
int
-> DockStyle
) umwandeln . Diese Box ist echt und fast zehnmal langsamer.
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Egal was ich versuche (einschließlich verschiedener Möglichkeiten, das Timing durchzuführen), das Boxen / Unboxen ist auf meinem System immer noch schneller.