129 votes

Pourquoi TypedReference est-il en coulisse? C'est tellement rapide et sûr ... presque magique!

Avertissement: Cette question est un peu hérétique... religieux programmeurs toujours en respectant les bonnes pratiques, s'il vous plaît ne pas le lire. :)

Personne ne sait pourquoi l'utilisation de TypedReference est donc déconseillée (implicitement, par manque de documentation)?

J'ai trouvé une grande utilité pour elle, comme lors du passage de paramètres génériques grâce à des fonctions qui ne devrait pas être génériques (lors de l'utilisation d'un object peut-être exagéré ou lent, si vous avez besoin d'un type de valeur), si vous avez besoin d'un pointeur opaque, ou lorsque vous en aurez besoin pour accéder à un élément d'un tableau rapidement, dont les spécifications que vous trouvez au moment de l'exécution (à l'aide d' Array.InternalGetReference). Depuis le CLR ne même pas permettre une utilisation incorrecte de ce type, pourquoi est-il découragé? Il ne semble pas dangereux ou quoi que ce soit...


D'autres utilisations que j'ai trouvé pour TypedReference:

"Spécialisé" génériques en C# (c'est le type-safe):

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 (ce qui est très dangereux si mal utilisé, mais rapide et sans danger 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);
}

L'écriture d'une méthode de version de l' sizeof instruction, qui peut parfois être 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)); }
    }
}

Écrire une méthode qui passe par un "état" paramètre que l'on veut éviter de 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 utilise comme ceci "découragé" par le manque de documentation)? Tout particulier des raisons de sécurité? Il semble tout à fait sûr et vérifiable si il n'est pas mélangé avec des pointeurs (qui ne sont pas sûrs ou vérifiable de toute façon)...


Mise à 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é la référence ci-dessus, depuis la dernière version de la poste utilisé une version de débogage du code [j'ai oublié de changer pour la libération], et de mettre 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 vite avec TypedReference en moyenne.)

44voto

Mehrdad Afshari Points 204872

Réponse courte: la portabilité.

Alors qu' __arglist, __makeref, et __refvalue sont des extensions de langage et sont des sans-papiers dans la Spécification du Langage C#, les constructions utilisées pour les mettre en œuvre sous le capot (vararg convention d'appel, TypedReference type arglist, refanytype, mkanyref, et refanyval instructions) sont parfaitement documentés dans la Spécification CLI (ECMA-335) dans le Vararg de la bibliothèque.

En cours de définition dans le Vararg Bibliothèque rend tout à fait clair qu'ils sont principalement destinés à l'appui de longueur variable des listes d'arguments et pas grand chose d'autre. Variable listes d'arguments ont peu d'utilité dans les plates-formes qui n'ont pas besoin d'interface avec l'extérieur de code en C qui utilise varargs. Pour cette raison, les Varargs bibliothèque ne fait pas partie de la CLI de profil. Légitime CLI implémentations peuvent choisir de ne pas soutenir Varargs bibliothèque comme il n'est pas inclus dans la CLI du Noyau de profil:

4.1.6 Vararg

Le vararg ensemble de fonctionnalités supporte à longueur variable des listes d'arguments et d'exécution de type de pointeurs.

Si ce paramètre est omis: Toute tentative de faire référence à une méthode avec l' vararg convention d'appel ou de la signature encodages associés avec vararg méthodes (voir la Partition II), doit jeter l' System.NotImplementedException d'exception. Les méthodes utilisant le CIL instructions arglist, refanytype, mkrefany, et refanyval doit jeter l' System.NotImplementedException d'exception. Le moment précis de l'exception n'est pas spécifié. Le type System.TypedReference n'ont pas besoin d'être défini.

Mise à jour (répondre à l' GetValueDirect commentaire):

FieldInfo.GetValueDirect sont FieldInfo.SetValueDirect sont pas partie de la Bibliothèque de classes de Base. Noter qu'il y a une différence entre les deux .NET Framework Bibliothèque de la Classe et de la Bibliothèque de classes de Base. BCL est la seule chose nécessaire pour une mise en œuvre conforme de la CLI/C# et est documenté dans l'ECMA TR/84. (En fait, FieldInfo lui-même fait partie de la Réflexion de la bibliothèque et qui n'est pas incluse dans l'interface de ligne de Noyau de profil).

Dès que vous utilisez une méthode en dehors de la BCL, vous donnant un peu de portabilité (et c'est de plus en plus important avec l'avènement de non-.NET CLI implémentations comme Silverlight et MonoTouch). Même si une mise en œuvre voulais augmenter compatiblility avec Microsoft .NET Framework Bibliothèque de Classe, il pourrait simplement fournir de l' GetValueDirect et SetValueDirect prendre un TypedReference sans TypedReference spécialement traitées par le moteur d'exécution (en gros, faisant d'eux l'équivalent de leur object leurs homologues sans l'avantage de performance).

Avaient-ils documentés en C#, il aurait eu au moins deux conséquences:

  1. Comme toute fonction, il peut devenir un obstacle à de nouvelles fonctionnalités, surtout depuis que celui-ci ne rentre pas vraiment dans la conception de C# et nécessite bizarre syntaxe des extensions spéciales et de la remise d'un type à l'exécution.
  2. Toutes les implémentations de C# ont en quelque sorte de mettre en œuvre cette fonctionnalité et il n'est pas forcément triviale/possible pour C# implémentations qui ne sont pas sur le haut d'une CLI, ou courir sur un CLI sans Varargs.

14voto

P Daddy Points 14228

Eh bien, je ne suis pas Eric Lippert, donc je ne peux pas parler directement de Microsoft motivations, mais si je me risque deviner, je dirais que TypedReference et coll. ils ne sont pas bien documentés parce que, franchement, vous n'en avez pas besoin.

Après chaque utilisation, vous l'avez mentionné, ces fonctions peuvent être accomplies sans eux, mais à une pénalité sur les performances dans certains cas. Mais C# (et .NET en général) n'est pas conçu pour être un haut rendement de la langue. (Je suppose que "plus rapide que Java" était l'objectif de performance.)

Cela ne veut pas dire que certains des facteurs de performance n'ont pas été accordée. En effet, des fonctionnalités telles que des pointeurs, stackalloc, et certains optimisé cadre de fonctions existent en grande partie pour améliorer les performances dans certaines situations.

Les génériques, je dirais avoir le principal avantage de la sécurité de type, également d'améliorer les performances de la même façon à l' TypedReference en évitant boxing et unboxing. En fait, je me demandais pourquoi vous préférez ce:

static void call(Action<int, TypedReference> action, TypedReference state){
    action(0, state);
}

pour cela:

static void call<T>(Action<int, T> action, T state){
    action(0, state);
}

Les compromis, comme je les vois, ne sont que la première nécessite moins d'équipes communes d'enquête (et, il suit, en moins de mémoire), tandis que le second est plus familière et, je suppose, un peu plus rapide (en évitant le déréférencement du pointeur).

Je dirais TypedReference et les amis de la mise en œuvre de détails. Vous l'avez souligné bien des utilisations pour eux, et je pense qu'ils sont la peine d'étudier, mais la mise en garde habituelle de s'appuyer sur les détails de mise en œuvre s'applique-la prochaine version peut casser votre code.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X