Attention: cette question est un peu hérétique ... les programmeurs religieux respectant toujours les bonnes pratiques, ne la lisez pas. :)
Est-ce que quelqu'un sait pourquoi l'utilisation de TypedReference est si déconseillée (implicitement, par manque de documentation)?
J'en ai trouvé de bonnes utilisations, par exemple lors du passage de paramètres génériques via des fonctions qui ne devraient pas être génériques (lors de l'utilisation d'un object
peut être excessif ou lent, si vous avez besoin d'un type valeur), lorsque vous avez besoin d'un pointeur opaque, ou pour quand vous avez besoin d'accéder rapidement à un élément d'un tableau, dont vous trouvez les spécifications au moment de l'exécution (en utilisant Array.InternalGetReference
). Puisque le CLR n'autorise même pas une utilisation incorrecte de ce type, pourquoi est-il déconseillé? Cela ne semble pas dangereux ou quoi que ce soit ...
D'autres utilisations que j'ai trouvées pour TypedReference
:
"Spécialisation" des génériques en C # (c'est du type sécurisé):
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); }
}
Écrire du code qui fonctionne avec des pointeurs génériques (c'est très dangereux en cas d'utilisation abusive, mais rapide et sûr s'il est utilisé correctement):
//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);
}
Ecrire une version méthode de l' sizeof
instruction, qui peut être parfois 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)); }
}
}
Ecriture d'une méthode qui passe un paramètre "state" qui veut éviter la 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*/ }
}
Alors pourquoi des utilisations comme celle-ci sont-elles «découragées» (faute de documentation)? Des raisons de sécurité particulières? Cela semble parfaitement sûr et vérifiable s'il n'est pas mélangé avec des pointeurs (qui ne sont pas sûrs ou vérifiables de toute façon) ...
Mettre à jour:
Exemple de code pour montrer que, en effet, TypedReference
peut être deux fois plus rapide (ou plus):
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
}
}
(Edit: j'ai édité le benchmark ci-dessus, puisque la dernière version du post utilisait une version de débogage du code [j'ai oublié de le changer en release], et je n'ai mis aucune pression sur le GC. Cette version est un peu plus réaliste, et sur mon système, c'est plus de trois fois plus rapide avec TypedReference
en moyenne.)
int
-> DockStyle
). Cela boit pour de vrai, et est presque dix fois plus lent.
TypedReference: 203 ticks
,boxing/unboxing: 31 ticks
. Peu importe ce que j'essaye (y compris différentes façons de faire le chronométrage), la boxe / déballage est toujours plus rapide sur mon système.