78 votes

Union C++ en C#

Je suis en train de traduire une bibliothèque écrite en C++ en C#, et le mot clé "union" n'existe qu'une fois. Dans un struct.

Quelle est la manière correcte de le traduire en C# ? Et que fait-il ? Cela ressemble à quelque chose comme ceci ;

struct Foo {
    float bar;

    union {
        int killroy;
        float fubar;
    } as;
}

0 votes

C'est peut-être plus sûr, mais lorsque vous interagissez avec des bibliothèques C qui fournissent ces types de structures de données, cette décision en C# brise même l'encapsulation rudimentaire de vos structures C/C++. J'essaie de traiter quelque chose comme ceci : struct LibrarySType { AnotherType *anotherTypeBuff ; int oneSetOfFlags ; int anotherSetOfFlags ; union { struct { int structMember1 ; ... } oneUseOfThisLibraryType ; struct { char *structMember2 ; ... } anotherUseOfThisLibraryType ; ... } u ; int64 *moreStuff ; ... vous voyez l'idée } Je n'ai pas inventé cette structure de données intelligente, mais elle fait partie de l'API d'un fournisseur dont j'ai besoin.

84voto

Armin Ronacher Points 16894

Vous pouvez utiliser des modèles de champs explicites pour cela :

[StructLayout(LayoutKind.Explicit)] 
public struct SampleUnion
{
    [FieldOffset(0)] public float bar;
    [FieldOffset(4)] public int killroy;
    [FieldOffset(4)] public float fubar;
}

Non testé. L'idée est que deux variables ont la même position dans votre structure. Vous ne pouvez bien sûr utiliser qu'une seule d'entre elles.

Plus d'informations sur les syndicats en Tutoriel sur les structures

7 votes

Notez que cela ne fonctionne que si les types concernés sont des types primitifs (comme int et float). Si des objets sont impliqués, vous ne pourrez pas le faire.

12 votes

Est-ce que cela fonctionne avec les types de valeurs (comme les enum) ou seulement avec les types primitifs ?

1 votes

Si vous avez d'autres types complexes, le second pourrait être une propriété qui se convertit en/depuis l'autre. Cela dépend de l'utilisation de l'Union et de la façon dont elle est utilisée dans le code que vous portez.

22voto

Steve Fallows Points 4059

Vous ne pouvez pas vraiment décider de la manière de traiter cette question sans savoir comment elle est utilisée. S'il est simplement utilisé pour gagner de l'espace, vous pouvez l'ignorer et vous contenter d'utiliser un struct.

Toutefois, ce n'est généralement pas la raison pour laquelle les syndicats sont utilisés. Il existe deux raisons courantes de les utiliser. La première est de fournir deux ou plusieurs façons d'accéder aux mêmes données. Par exemple, l'union d'un int et d'un tableau de 4 octets est l'une des nombreuses façons de séparer les octets d'un entier de 32 bits.

L'autre cas est celui où les données de la structure proviennent d'une source externe, comme un paquet de données réseau. En général, l'un des éléments de la structure qui contient l'union est un ID qui vous indique quel type d'union est en vigueur.

Dans aucun de ces cas, vous ne pouvez ignorer aveuglément l'union et la convertir en une structure où les deux champs (ou plus) ne coïncident pas.

3 votes

Ce que je pense être important dans la réponse de Steve, c'est que vous devez vraiment comprendre l'utilisation de l'union particulière que vous portez. Il peut y avoir d'autres constructions en C# qui répondront aux besoins d'une manière plus élégante.

3 votes

Bien que je comprenne ce que @AaronLS voulait dire, ce serait bien d'avoir un lien vers une documentation ou quelque chose sur les façons courantes de faire cela. élégamment en C#. Je peux deviner certaines des méthodes les plus basiques, en fonction de l'utilisation, comme l'utilisation de propriétés pour retourner un "cast" d'une valeur de l'union comme s'il s'agissait de l'autre membre. Mais je me demande quels autres moyens élégants pourraient être utilisés pour les scénarios les plus courants.

4voto

Nir Points 18250

En C/C++, l'union est utilisée pour superposer différents membres dans le même emplacement mémoire. Ainsi, si vous avez une union d'un int et d'un float, ils utilisent tous deux les mêmes 4 octets de mémoire pour être stockés, et il est évident que l'écriture dans l'un corrompt l'autre (puisque int et float ont une disposition de bits différente).

Dans .Net, Microsoft a opté pour le choix le plus sûr et n'a pas inclus cette fonctionnalité.

EDIT : sauf pour l'interop

0 votes

Cela pourrait être dû au fait que .NET est de plus haut niveau et que vous n'avez probablement pas à vous soucier de sérialiser explicitement les données et de les transférer entre les machines little-endian et big-endian. Les Unions fournissent un bon moyen de conversion, en utilisant l'astuce mentionnée dans un commentaire précédent.

1 votes

L'utilisation de structures à disposition explicite ne se limite pas à l'interopérabilité. Les primitives et les membres publics de types non référencés peuvent recouvrir d'autres primitives et membres publics de types non référencés de manière arbitraire avec un comportement entièrement défini, que le code transmette ou non les structures en question à du code extérieur. À cet égard, .NET supporte des constructions de plus bas niveau que le C "moderne".

1voto

ckramer Points 7315

Personnellement, je ne tiendrais pas compte de l'UNION et j'implémenterais Killroy et Fubar comme des champs séparés.

public struct Foo
{
    float bar;
    int Kilroy;
    float Fubar;
}

L'utilisation d'une UNION permet d'économiser 32 bits de mémoire allouée par l'adresse int...., ce qui ne va pas faire ou défaire une application de nos jours.

8 votes

Cela peut ne pas fonctionner en fonction de la façon dont les autres parties de la bibliothèque y accèdent. Dans certains cas, vous pouvez écrire dans une instance et lire dans l'autre pour obtenir les mêmes données mais dans un format légèrement différent, cette fonctionnalité sera interrompue si vous la divisez en deux variables distinctes.

1 votes

@KPexEA Je suis d'accord. Cependant, s'il s'agit d'un type d'union dans lequel un drapeau indique lequel des deux champs est le plus approprié, alors cela fonctionnera parfaitement. Je pense que ce qui est vraiment important est de comprendre l'utilisation et l'objectif de l'union dans chaque cas, avant de décider comment la porter.

4 votes

Si vous voulez créer une structure avec 10 ou 20 types syndiqués, comme les structs sont parval, il est beaucoup plus rapide de faire circuler quelques octets que 1k par argument.

0voto

Aubin Points 6990

Voici une tentative pour contenir plusieurs messages complexes (simplifiés ici) dans une Union. Les messages sont mutuellement exclusifs, nous n'en avons qu'un seul disponible à la fois, voire aucun. J'aborde ici la forme conceptuelle de l'union, pas la forme technique (binaire) qui est abordée par d'autres messages dans cette page.

   public class Msg1 {
      public int v;
   }

   public class Msg2 {
      public double v;
   }

   public class Msg3 {
      public string v;
   }

   public class Union {

      public enum selector_t {
         NONE,
         MSG1,
         MSG2,
         MSG3,
      }

      private selector_t _selector;
      private Object     _value;

      public Union() {
         reset();
      }

      public Union( Msg1 value ) {
         set( value );
      }

      public Union( Msg2 value ) {
         set( value );
      }

      public Union( Msg3 value ) {
         set( value );
      }

      private void set( selector_t selector, Object value ) {
         _selector = selector;
         _value    = value;
      }

      public selector_t getSelector() {
         return _selector;
      }

      public bool isNone() {
         return _selector == selector_t.NONE;
      }

      public bool isMsg1() {
         return _selector == selector_t.MSG1;
      }

      public bool isMsg2() {
         return _selector == selector_t.MSG2;
      }

      public bool isMsg3() {
         return _selector == selector_t.MSG3;
      }

      public void reset() {
         set( selector_t.NONE, null );
      }

      public void set( Msg1 value ) {
         set( selector_t.MSG1, value );
      }

      public void set( Msg2 value ) {
         set( selector_t.MSG2, value );
      }

      public void set( Msg3 value ) {
         set( selector_t.MSG3, value );
      }

      public Msg1 getMsg1() {
         return (Msg1)_value;
      }

      public Msg2 getMsg2() {
         return (Msg2)_value;
      }

      public Msg3 getMsg3() {
         return (Msg3)_value;
      }
   }

   class Program {
      static void Main(string[] args) {
         Union u = new Union( new Msg2());
         Console.WriteLine( "isMsg1: " + u.isMsg1());
         Console.WriteLine( "isMsg2: " + u.isMsg2());
         Console.WriteLine( "isMsg3: " + u.isMsg3());
         Console.WriteLine( "getMsg2: " + u.getMsg2());
         try {
            Console.WriteLine( "getMsg3: " + u.getMsg3());
         }
         catch( InvalidCastException x ) {
            Console.WriteLine(x);
         }
         u.set( new Msg3());
         try {
            Console.WriteLine( "getMsg3: " + u.getMsg3());
         }
         catch( InvalidCastException x ) {
            Console.WriteLine(x);
         }
      }
   }

Journal d'exécution :

isMsg1: False
isMsg2: True
isMsg3: False
getMsg2: Union.Msg2
System.InvalidCastException: Impossible d'effectuer un cast d'un objet de type 'Union.Msg2' en type 'Union.Msg3'.
   à Union.Union.getMsg3() dans f:\dev\C#\2014\Union\Program.cs:ligne 99
   à Union.Program.Main(String[] args) dans f:\dev\C#\2014\Union\Program.cs:ligne 111
getMsg3: Union.Msg3

El System.InvalidCastException est attendu, car nous tentons d'utiliser un Msg2 como Msg3 .

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