Indexeurs dans la liste vs tableau

comment les indexeurs sont définis dans la liste et les tableaux.

List<MyStruct> lists=new List<MyStruct>();MyStruct est une Structure. Considérons Maintenant MyStruct[] arr=new MyStruct[10];

arr[0] fait référence au premier élément de la Structure.Mais lists[0] m'en donne une copie. Est-il une raison pourquoi c'est fait comme ça. Aussi depuis Int32 est la structure List<Int32> list1 =new List<Int32>(); comment il est possible pour moi d'accéder list1[0] ou assigner list1[0]=5 où comme il n'est pas possible de faire lists[0]._x=5

9
demandé sur CodesInChaos 2011-07-15 14:19:48

6 réponses

bien qu'ils se ressemblent, l'indexeur de tableau et l'indexeur de liste font des choses complètement séparées.

l'indexeur List<T> est déclaré comme propriété avec un paramètre:

public T this[int index] { get; set; }

cette méthode est compilée en méthodes get_Item et set_Item qui sont appelées comme n'importe quelle autre méthode lorsque le paramètre est accédé.

le tableau indexeur a un support direct dans le CLR; il y a un IL spécifique instruction ldelema (adresse de l'élément de charge) pour obtenir un pointeur géré vers le n'ème élément d'un tableau. Ce pointeur peut alors être utilisé par n'importe laquelle des autres instructions IL qui prennent un pointeur pour modifier directement la chose à cette adresse.

par exemple, l'instruction stfld (store field value) peut prendre un pointeur géré spécifiant l'instance 'this' pour stocker le champ dans, ou vous pouvez utiliser le pointeur pour appeler des méthodes directement sur la chose dans le tableau.

dans le langage C#, l'indexeur de tableau renvoie une variable , mais l'indexeur de liste renvoie une valeur .

9
répondu thecoop 2011-07-15 10:26:14

le dernier point:

lists[0]._x=5

n'est en fait qu'une reformulation de votre point précédent:

arr[0] fait référence au premier élément de la Structure.Mais lists[0] m'en donne une copie.

si vous avez édité une copie de celui-ci, le changement serait perdu dans l'éther, i.e.

var throwawayCopy = lists[0];
throwawayCopy._x = 5;
// (and never refer to throwawayCopy again, ever)

puisque ce n'est presque certainement pas ce que vous l'aviez prévu, le compilateur ne vous le permet pas. Cependant, les structures mutables sont mauvaises . Une meilleure option ici serait n'utilisez pas de structures mutables . Ils mordent.


descendre ce niveau, à un exemple simple mais concret:

using System;
struct Evil
{
    public int Yeuch;
}
public class Program
{
    public static void Main()
    {
        var pain = new Evil[] { new Evil { Yeuch = 2 } };
        pain[0].Yeuch = 27;
        Console.WriteLine(pain[0].Yeuch);
    }
}

Cette compile (en regardant les 2 dernières lignes ici):

L_0026: ldloc.0 <== pain
L_0027: ldc.i4.0 <== 0
L_0028: ldelema Evil <== get the MANAGED POINTER to the 0th element
                           (not the 0th element as a value)
L_002d: ldc.i4.s 0x1b <== 27
L_002f: stfld int32 Evil::Yeuch <== store field

L_0034: ldloc.0 <== pain
L_0035: ldc.i4.0 <== 0
L_0036: ldelema Evil <== get the MANAGED POINTER to the 0th element
                           (not the 0th element as a value)
L_003b: ldfld int32 Evil::Yeuch <== read field
L_0040: call void [mscorlib]System.Console::WriteLine(int32) <== writeline
L_0045: ret 

Ce n'est jamais réelle parle à la structure comme une valeur - pas de copies, etc

5
répondu Marc Gravell 2011-07-15 11:55:58

List<T> a un indexeur normal qui se comporte comme une propriété. L'accès passe par les fonctions d'accesseur, et ceux-ci sont de valeur secondaire.

T this[int index]
{
    get{return arr[index];}
    set{arr[index]=value;}}
}
Les matrices

sont des types spéciaux, et leur indexeur est semblable à un champ. Tant le runtime que le compilateur C# ont une connaissance spéciale des tableaux, et cela permet ce comportement. Vous ne pouvez pas avoir le tableau comme comportement sur les types personnalisés.

heureusement, il s'agit rarement d'un problème dans la pratique. Puisque vous n'utilisez des structures mutantes que dans de rares cas spéciaux(haute performance ou natif interop), et dans ceux que vous préférez généralement tableaux de toute façon en raison de leur faible hauteur.


vous obtenez le même comportement avec propriétés vs. champs. Vous obtenez une sorte de référence lors de l'utilisation d'un champ, mais une copie lorsque vous utilisez une propriété. Ainsi, vous pouvez écrire aux membres des champs de type valeur, mais pas aux membres des propriétés de type valeur.

2
répondu CodesInChaos 2011-07-15 10:27:39

j'ai rencontré ceci aussi, quand j'inspectais les types d'expression lambda. Lorsque la lambda est compilée dans un arbre d'expression, vous pouvez inspecter le type d'expression pour chaque noeud. Il s'avère qu'il existe un type de noeud spécial ArrayIndex pour l'indexeur Array :

Expression<Func<string>> expression = () => new string[] { "Test" }[0];
Assert.Equal(ExpressionType.ArrayIndex, expression.Body.NodeType);

alors que l'indexeur List est du type Call :

Expression<Func<string>> expression = () => new List<string>() { "Test" }[0];
Assert.Equal(ExpressionType.Call, expression.Body.NodeType);

c'est juste pour illustrer que nous pouvons raisonner sur l'architecture sous-jacente avec des expressions lambda.

2
répondu Michiel van Oosterhout 2012-01-28 14:06:50

votre problème n'est pas avec List<>, c'est avec les structures elles-mêmes.

par exemple:

public class MyStruct
{
    public int x;
    public int y;
}

void Main()
{
    var someStruct = new MyStruct { x = 5, y = 5 };

    someStruct.x = 3;
}

ici, vous ne modifiez pas la valeur de x de la structure originale, vous créez un nouvel objet avec y = y, et x = 3. La raison pour laquelle vous ne pouvez pas modifier directement ceci avec une liste, est que l'indexeur de liste est une fonction (par opposition à l'indexeur de tableau), et qu'il ne sait pas comment 'définir' la nouvelle structure dans la liste.

modifiez le mot-clé struct en class et vous verrez qu'il fonctionne très bien (avec les classes, vous ne créez pas un tout nouvel objet à chaque fois que vous le muter).

0
répondu Rob 2011-07-15 11:33:10

l'une des limites malheureuses des langages .net est qu'ils n'ont aucune notion d'une propriété faisant quoi que ce soit d'autre que retourner une valeur, qui peut alors être utilisé comme l'appelant le souhaite. Il serait très utile (et si j'avais un moyen de demander des fonctionnalités linguistiques, je chercherais cela) s'il y avait un moyen standard supporté par le compilateur d'exposer les propriétés en tant qu'appelant délégué, tel qu'une déclaration comme:

  MyListOfPoints[4].X = 5;

pourrait être traduit par le compilateur en quelque chose comme:

  MyListOfPoints.ActOnItem(4, (ref Point it) => it.X = 5);

un tel code pourrait être relativement efficace, et ne pas créer de pression GC, si ActOnItem prenait un paramètre de réf supplémentaire de type générique, et le passait à un délégué qui prenait aussi un paramètre de ce type. Cela permettrait à la fonction appelée d'être statique, éliminant la nécessité de créer des fermetures ou des délégués pour chaque exécution de la fonction d'enclos. S'il y avait un moyen pour ActOnItem d'accepter un nombre variable de paramètres génériques 'ref', il serait même possible de traiter des constructions comme:

  SwapItems(ref MyListOfPoints[4].X, ref MyListofPoints[4].Y);

avec des combinaisons arbitraires de paramètres 'ref', mais même avoir la possibilité de gérer les cas où la propriété est" impliquée dans " la gauche d'une tâche, ou une fonction est appelée avec une propriété unique-ish 'ref' paramètre, serait utile.

notez que pouvoir faire les choses de cette façon offrirait un avantage supplémentaire au-delà de la capacité d'accéder à des champs de structures. Cela signifierait également que l'objet exposant la propriété recevrait notification que le consommateur a été fait avec elle (puisque le délégué du consommateur reviendrait). Imaginez, par exemple, que l'on a un contrôle qui affiche une liste d'éléments, chacun avec une chaîne et une couleur, et l'on veut être en mesure de faire quelque chose comme:

  MyControl.Items(5).Color = Color.Red;

un énoncé facile à lire, et la façon la plus naturelle de lire pour changer la couleur de la cinquième point de la liste, mais essayer de faire un tel travail de déclaration exigerait que l'objet retourné par Items(5) avoir un lien vers MyControl , et lui envoyer une sorte de notification quand il a changé. Plutôt compliquée. En revanche, si le style d'appel-par indiqué ci-dessus était soutenu, une telle chose serait beaucoup plus simple. La méthode ActOnItem(index, proc, param) devait savoir qu'une fois que proc serait revenu, il faudrait retravailler l'article spécifié par index . D'une certaine importance, si Items(5) étaient un appel-par proc et ne supportait aucune méthode de lecture directe, on pourrait éviter des scénarios comme:

  var evil = MyControl.Items(5);
  MyControl.items.DeleteAt(0);
  // Should the following statement affect the item that used to be fifth,
  // or the one that's fifth now, or should it throw an exception?  How
  // should such semantics be ensured?
  evil.Color = Color.Purple;

la valeur de MyControl.Items(5) ne resterait liée à MyControl que pour la durée de l'appel l'impliquant. Après cela, ce serait simplement une valeur détachée.

0
répondu supercat 2012-01-29 19:06:24