2 votes

Structure enveloppant les types de valeurs et permettant l'accès avec un index

J'ai besoin d'encapsuler un tableau fixe d'un type de valeur défini par l'utilisateur (appelons-le struct2) à l'intérieur d'une autre structure (struct1), mais les tableaux fixes ne peuvent être déclarés que pour les types de valeur natifs. J'ai donc pensé à créer une troisième structure (struct wrapper) qui doit fonctionner comme un tableau de la struct2 en définissant l'opérateur []. Donc

    struct Struct2
    {
       int a;
       int b
    }

     struct wrapper
        {
            Struct2 str0;
            Struct2 str1;
            public Struct2 this[int index]
            {
                get
                {
                    switch ( index )
                    {
                        case 0: return str0;
                        case 1: return str1;
                        default: return str0;
                    }
                }
            } 
        }

        static void Main()
        {
           wrapper wr = new wrapper();
           wr[0].a = 123; // i would like to do this but this doesnt work
           //but if we do like this 
           Struct2 [] arr = new Struct2[5];
            arr[0].a = 123 ;// this works , we are effectively modifying the object               
            //contained in the array (not just a copy)
        }

Ok, ce code ne fonctionne pas parce que Struct2 est un type de valeur et quand l'opérateur retourne, il retourne une copie et non l'objet réel str0 qu'il contient. J'aimerais accéder à ce champ en utilisant un indexeur ! est-ce possible ? Pourquoi est-ce possible avec la classe Array ? Je sais que c'est possible en retournant un pointeur mais cela implique d'utiliser le mot clé fixed à l'intérieur de la définition de l'opérateur et j'aimerais éviter cela parce que le 'tableau' doit être accédé de manière extensive et je finirais par utiliser l'instruction fixed 2 fois (à l'intérieur et à l'extérieur pour garder l'adresse fixe). De plus, j'ai déjà envisagé d'utiliser des pointeurs en déclarant simplement N champs Struct2 adjacents dans Struct1 et en utilisant un pointeur sur le premier comme un tableau, mais je préférerais utiliser le wrapper pour émuler le comportement d'un tableau. Encore une fois, est-ce possible ?

Editer Il semble qu'il ne soit pas possible d'implémenter un tableau personnalisé qui fonctionne avec les types de valeurs (comme le font les tableaux ordinaires). La solution la plus proche que j'ai pu trouver est celle-ci, mais comme je l'ai écrit, j'aurais aimé éviter les pointeurs.

    struct Struct2
    {
        int a;
        int b
    }
    struct Struct1 
    {
        public Struct2 * str2arr ; //this can be avoided
        public Struct2 str0;
        //wrapper is not needed anymore as Struct1 is wrapping the Struct2 array by itself
        //and a pointer is used for indexing operations
        //struct layout needs to be sequential
        private Struct2 str1;
        private Struct2 str2;
        private Struct2 str3;
        private Struct2 str4;

    }
   static void Main()
   {
     Struct1 myStruct = new Struct1();
     fixed(myStruct.str2arr = &myStruct.str0)
     {
         myStruct.str2arr[1] = 123;
     }
    }

1voto

Pierre Arnaud Points 2306

Vous ne pouvez pas réaliser ce que vous voulez faire sans avoir au moins une classe racine contenant vos données. Voici une solution qui fonctionne pour une version simplifiée de votre problème, à savoir la possibilité d'accéder au champ a de votre Struct2 dans votre pseudo-réseau, comme par exemple :

wrapper wr = new wrapper ();  // needs to be a class
wr[0].a = 123;
wr[1].a = 456;

System.Console.WriteLine ("wr[0].a = {0}", wr[0].a); // displays 123
System.Console.WriteLine ("wr[1].a = {0}", wr[1].a); // displays 456

Votre wrapper doit renvoyer un type de référence si vous voulez pouvoir modifier son contenu, sinon vous vous heurterez toujours à la copie de type valeur qui a lieu lorsque vous accédez aux structures. Mais votre wrapper peut toujours stocker ses données en interne sous la forme d'une série de structs.

Voici ma solution :

struct Struct2
{
    public int a;
}

class wrapper // sorry, cannot use 'struct' here ...
{
    Struct2 str0;
    Struct2 str1;

    public helper this[int index]
    {
        get
        {
            return new helper (this, index);
        }
    }

    int GetValueA(int index)
    {
        switch (index)
        {
            case 0:  return str0.a;
            case 1:  return str1.a;
            default: throw new System.IndexOutOfRangeException ();
        }
    }

    void SetValueA(int index, int value)
    {
        switch (index)
        {
            case 0: str0.a = value; break;
            case 1: str1.a = value; break;
        }
    }

    public class helper
    {
        public helper(wrapper host, int index)
        {
            this.host  = host;
            this.index = index;
        }

        public int a
        {
            get { return this.host.GetValueA (index); }
            set { this.host.SetValueA (index, value); }
        }

        private readonly wrapper host;
        private readonly int index;
    }
}

Comme votre préoccupation semble être la rapidité, aucun emballage ne vous satisfera. Je reconsidérerais le problème dans son ensemble et, si possible, j'écrirais une classe pour gérer votre structure de données.

Si toutes vos données peuvent être représentées sous la forme int Vous devriez peut-être envisager d'utiliser un énorme tableau d'entiers et d'ajouter des classes qui accèdent à ce tableau central pour localiser les champs que vous souhaitez manipuler, en indexant l'élément approprié.

class Wrapper
{
    ...

    int[] data;

    public StructWrapper1 this[int index]
    {
        get
        {
            return new StructWrapper1 (this, index);
        }
    }

    public class StructWrapper1
    {
        public StructWrapper1(Wrapper wrapper, int index)
        {
            this.wrapper = wrapper;
            this.index   = index;
        }
        public int A
        {
            get { return this.wrapper[this.index*2+0]; }
            set { this.wrapper[this.index*2+0] = value; }
        }
        public int B
        {
            get { return this.wrapper[this.index*2+1]; }
            set { this.wrapper[this.index*2+1] = value; }
        }

        private readonly Wrapper wrapper;
        private readonly int index;
    }
}

Si vous devez représenter plusieurs types de données, vous pouvez envisager d'utiliser un tableau pour chaque type de champ.

1voto

Reniuz Points 5951

La structure est connue sous le nom de sémantique des valeurs c'est pourquoi vous ne pouvez pas modifier la valeur directement.

Avec les classes, il est possible que deux variables fassent référence au même objet, et donc que les opérations sur une variable affectent l'objet référencé par l'autre variable. Avec les structures, les variables ont chacune leur propre copie des données (sauf dans le cas des variables paramètres ref et out), et il n'est pas possible que les opérations sur l'une affectent l'autre.

Vous devriez envisager d'utiliser des classes plutôt que des structures.

EDIT Si vous avez vraiment besoin d'avoir Struct2 en tant que struct, vous pouvez créer un wrapper en tant que classe. Dans la classe, vous pouvez avoir un tableau fixe de structures et le modifier par le biais d'une méthode.

class wrapper
{
   Struct2[] structArray = new Struct2[10];
   public void setValues(int index, int a, int b)
   {
      structArray[index].a = a;
      structArray[index].b = b;
   }
}

wrapper wr = new wrapper();
wr.setValues(0, 2, 3);

1voto

Yhrn Points 1045

Je pense que vous devez renoncer à l'indexeur. Même si cela semble identique lorsqu'on l'utilise sur une instance d'un type défini par l'utilisateur et sur un tableau, ce n'est pas le cas. Lorsque vous définissez un indexeur getter, c'est juste du sucre syntaxique pour une méthode Get(int index) et lorsque vous retournez un type de valeur à partir d'une méthode, il est retourné par la valeur, c'est tout l'intérêt d'un type de valeur.

Par exemple :

struct wrapper {
    public Struct2 str0;
    public Struct2 str1 { get; set; }
    public Struct2 this[int index] {
        get {
            switch ( index ) {
                    case 1: return str1;
                    default: return str0;
            }
        }
    }
}

static void Main(string[] args) {
    wrapper wr = new wrapper();
    wr.str0.a = 123; // Works, accessing fields only.
    wr.str1.a = 123; // Does not work, str1 is a property, which is really a method
    wr[0].a = 123; // Also does not work

}

Je n'ai trouvé aucun moyen de faire ce que vous voulez avec un indexeur sans créer d'instances intermédiaires de type référence. Il ne reste donc plus qu'à créer des méthodes pour définir les valeurs de la structure interne en fonction de l'index (comme le suggère Renius).

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