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.)
la source
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.int
->DockStyle
). Cela boit pour de vrai, et est presque dix fois plus lent.Réponses:
Réponse courte: portabilité .
Bien que
__arglist
,__makeref
et__refvalue
sont des extensions de langage et sont en situation irrégulière dans le langage C # Spécification, les constructions utilisées pour les mettre en œuvre sous le capot (vararg
convention d' appel,TypedReference
typearglist
,refanytype
,mkanyref
etrefanyval
instructions) sont parfaitement documentées dans la spécification CLI (ECMA-335) en la bibliothèque Vararg .Le fait d'être défini dans la bibliothèque Vararg montre clairement qu'ils sont principalement destinés à prendre en charge les listes d'arguments de longueur variable et pas grand-chose d'autre. Les listes d'arguments variables ont peu d'utilité dans les plates-formes qui n'ont pas besoin de s'interfacer avec du code C externe utilisant des varargs. Pour cette raison, la bibliothèque Varargs ne fait partie d'aucun profil CLI. Les implémentations CLI légitimes peuvent choisir de ne pas prendre en charge la bibliothèque Varargs car elle n'est pas incluse dans le profil du noyau CLI:
Mise à jour (réponse au
GetValueDirect
commentaire):FieldInfo.GetValueDirect
neFieldInfo.SetValueDirect
font pas partie de la bibliothèque de classes de base. Notez qu'il existe une différence entre la bibliothèque de classes .NET Framework et la bibliothèque de classes de base. BCL est la seule chose requise pour une implémentation conforme de la CLI / C # et est documentée dans ECMA TR / 84 . (En fait,FieldInfo
lui-même fait partie de la bibliothèque Reflection et n'est pas non plus inclus dans le profil du noyau CLI).Dès que vous utilisez une méthode en dehors de BCL, vous renoncez un peu à la portabilité (et cela devient de plus en plus important avec l'avènement des implémentations CLI non.NET comme Silverlight et MonoTouch). Même si une implémentation voulait augmenter la compatibilité avec la bibliothèque de classes Microsoft .NET Framework, elle pourrait simplement fournir
GetValueDirect
etSetValueDirect
prendre unTypedReference
sans faire leTypedReference
spécialement géré par le runtime (en gros, ce qui les rend équivalents à leursobject
homologues sans avantage en termes de performances).S'ils l'avaient documenté en C #, cela aurait eu au moins quelques implications:
la source
FieldInfo.GetValueDirect
etFieldInfo.SetValueDirect
? Ils font partie de la BCL et pour les utiliser, vous en avez besoinTypedReference
, alors cela ne force-t-il pas fondamentalementTypedReference
à toujours être définis, quelle que soit la spécification du langage? (Aussi, une autre note: même si les mots-clés n'existaient pas, tant que les instructions existaient, vous pouviez toujours y accéder en émettant dynamiquement des méthodes ... donc tant que votre plate-forme interagit avec les bibliothèques C, vous pouvez les utiliser, si C # a ou non les mots-clés.)TypedReference
était documenté juste pour un langage - disons, géré C ++ - mais si aucun langage ne le documente et donc si personne ne peut vraiment l'utiliser, alors pourquoi même s'embêter à définir la fonctionnalité?)[DllImport("...")] void Foo(__arglist);
) et ils l'ont implémentée en C # pour leur propre usage. La conception de l'interface de ligne de commande est influencée par de nombreux langages (les annotations «The Common Language Infrastructure Annotated Standard» démontrent ce fait.) Être un environnement d'exécution approprié pour autant de langages que possible, y compris les imprévus, a certainement été un objectif de conception (d'où le name) et c'est une fonctionnalité dont, par exemple, une implémentation hypothétique de C géré pourrait probablement bénéficier.Eh bien, je ne suis pas Eric Lippert, donc je ne peux pas parler directement des motivations de Microsoft, mais si je devais oser une supposition, je dirais que
TypedReference
et al. ne sont pas bien documentés car, franchement, vous n'en avez pas besoin.Chaque utilisation que vous avez mentionnée pour ces fonctionnalités peut être accomplie sans elles, bien que dans certains cas, les performances soient pénalisées. Mais C # (et .NET en général) n'est pas conçu pour être un langage hautes performances. (Je suppose que "plus rapide que Java" était l'objectif de performances.)
Cela ne veut pas dire que certaines considérations de performances n'ont pas été prises en compte. En effet, des fonctionnalités telles que les pointeurs
stackalloc
et certaines fonctions de framework optimisées existent en grande partie pour améliorer les performances dans certaines situations.Les génériques, qui, je dirais, ont le principal avantage de la sécurité des types, améliorent également les performances de la même manière
TypedReference
qu'en évitant la boxe et le déballage. En fait, je me demandais pourquoi vous préféreriez ceci:pour ça:
Les compromis, comme je les vois, sont que le premier nécessite moins de JIT (et, il s'ensuit, moins de mémoire), tandis que le second est plus familier et, je suppose, légèrement plus rapide (en évitant le déréférencement du pointeur).
J'appellerais
TypedReference
et les détails d'implémentation d'amis. Vous avez souligné certaines utilisations intéressantes pour eux, et je pense qu'ils valent la peine d'être explorés, mais la mise en garde habituelle de se fier aux détails d'implémentation s'applique - la prochaine version peut casser votre code.la source
call()
: C'est parce que le code n'est pas toujours aussi cohérent - je faisais davantage référence à un exemple plus semblable à celui deIAsyncResult.State
, où l'introduction de génériques ne serait tout simplement pas faisable car tout à coup, cela introduirait des génériques pour chaque classe / méthode impliquée. +1 pour la réponse, cependant ... surtout pour avoir souligné la partie "plus rapide que Java". :]TypedReference
ne subira probablement pas de changements de rupture de sitôt, étant donné que FieldInfo.SetValueDirect , qui est public et probablement utilisé par certains développeurs, en dépend. :)TypedReference
ni l' un ni l'autre. (La syntaxe atroce et la lourdeur globale le disqualifient, dans mon esprit, de la catégorie agréable à avoir.) Je dirais que c'est juste une bonne chose à avoir lorsque vous avez vraiment besoin de couper quelques microsecondes ici et là. Cela dit, je pense à quelques endroits de mon propre code que je vais examiner tout de suite, pour voir si je peux les optimiser en utilisant les techniques que vous avez indiquées.TypedReference
s, mais IIRC, le seul endroit où j'ai pu éviter la boxe quelque part était avec les éléments de tableaux unidimensionnels de primitives. Le léger avantage de vitesse ici ne valait pas la complexité qu'il ajoutait à l'ensemble du projet, alors je l'ai retiré.delegate void ActByRef<T1,T2>(ref T1 p1, ref T2 p2);
qu'une collection de typeT
pourrait fournir une méthodeActOnItem<TParam>(int index, ActByRef<T,TParam> proc, ref TParam param)
, mais le JITter devrait créer une version différente de la méthode pour chaque type de valeurTParam
. L'utilisation d'une référence typée permettrait à une version JITted de la méthode de fonctionner avec tous les types de paramètres.Je ne peux pas comprendre si le titre de cette question est censé être sarcastique: il est établi depuis longtemps que
TypedReference
c'est le cousin lent, gonflé et laid des `` vrais '' pointeurs gérés, ce dernier étant ce que nous obtenons avec C ++ / CLIinterior_ptr<T>
, ou même les paramètres par référence (ref
/out
) traditionnels en C # . En fait, il est assez difficile d'TypedReference
atteindre les performances de base en utilisant simplement un entier pour réindexer à chaque fois le tableau CLR d'origine.Les tristes détails sont là , mais heureusement, rien de tout cela n'a d'importance maintenant ...
Ces nouvelles fonctionnalités de langage fournissent une prise en charge de premier ordre en C # pour la déclaration, le partage et la manipulation de vrais
CLR
types de types de référence gérés dans des situations soigneusement prédéfinies.Les restrictions d'utilisation ne sont pas plus strictes que ce qui était auparavant requis
TypedReference
(et les performances sautent littéralement du pire au meilleur ), donc je ne vois aucun cas d'utilisation imaginable en C # pourTypedReference
. Par exemple, auparavant, il n'y avait aucun moyen de conserver unTypedReference
dans leGC
tas, donc la même chose est vraie pour les pointeurs gérés supérieurs maintenant n'est pas un retrait.Et évidemment, la disparition de
TypedReference
- ou sa dépréciation presque complète au moins - signifie également jeter__makeref
à la poubelle.la source