Спілка C ++ у C #


78

Я перекладаю бібліотеку, написану на C ++, на C #, і ключове слово 'union' існує один раз. У структурі.

Який правильний спосіб перекласти його на C #? І що це робить? Це виглядає приблизно так;

struct Foo {
    float bar;

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

Це може бути безпечніше, але коли ви взаємодієте з бібліотеками C, які надають такі типи структур даних, це рішення в C # порушує навіть елементарну інкапсуляцію ваших структур C / C ++. Я намагаюся мати справу з приблизно таким чином: struct LibrarySType {AnotherType * anotherTypeBuff; int oneSetOfFlags; int anotherSetOfFlags; об'єднання {struct {int structMember1; ...} oneUseOfThisLibraryType; struct {char * structMember2; ...} anotherUseOfThisLibraryType; ...} u; int64 * moreStuff; ... ви розумієте} Зараз я не вигадав цієї розумної структури даних, але це частина потрібного мені API постачальника
Стів Варт

Відповіді:


82

Ви можете використовувати явні макети полів для цього:

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

Неперевірені. Ідея полягає в тому, що дві змінні мають однакову позицію у вашій структурі. Звичайно, ви можете використовувати лише один із них.

Більше інформації про спілки в навчальному посібнику зі структур


7
Зауважте, що це буде працювати лише в тому випадку, якщо задіяні типи є примітивними типами (наприклад, int та float). Як тільки об’єкти задіяні, це не дозволить вам.
Khoth

12
Чи працює це з типами значень (наприклад, enum) або просто примітивними типами?
Тейлор Ліз,

1
Якщо у вас є інші складні типи, другий може бути властивістю, яка перетворюється в / з іншого. Залежить від використання Union та того, як він використовується в іншому коді, який ви переносите.
AaronLS

1
"Звичайно, ви можете використовувати лише один з них." може бути незрозумілим. Ви можете отримати доступ до них і встановити їх, але вони перезаписують один одного. Я б пояснив цей момент.
Xonatron

@Khoth Ви можете вмістити що-небудь у полі в структурі. Те саме з явними макетами структури.

22

Ви насправді не можете вирішити, як з цим боротися, не знаючи чогось про те, як це використовується. Якщо він просто використовується для економії місця, ви можете ігнорувати його і просто використовувати структуру.

Однак зазвичай це не те, чому використовуються спілки. Існує дві загальні причини їх використання. Один з них полягає у забезпеченні двох або більше способів доступу до одних і тих самих даних. Наприклад, об'єднання int та масиву з 4 байт - це один (з багатьох) способів відокремити байти 32-бітового цілого числа.

Інший - коли дані у структурі надходять із зовнішнього джерела, такого як мережевий пакет даних. Зазвичай одним елементом структури, що охоплює об'єднання, є ідентифікатор, який повідомляє вам, який аромат об'єднання діє.

Ні в одному з цих випадків ви не можете сліпо ігнорувати об'єднання і перетворити його на структуру, де два (або більше) поля не збігаються.


3
Що я вважаю важливим у відповіді Стіва, це те, що вам дійсно потрібно зрозуміти використання конкретного об’єднання, яке ви переносите. У C # можуть бути інші конструкції, які задовольнять потреби більш елегантно.
AaronLS

3
Хоча я розумію, що означав @AaronLS, було б непогано посилатись на документи або щось про загальні способи це елегантно зробити в C #. Я можу здогадуватися про деякі найосновніші способи, залежно від використання, як використання властивостей для повернення "приведення" одного значення об'єднання, як це було іншим членом. Але поцікавтеся, якими ще елегантними способами можуть бути звичайні сценарії.
40 детективів

4

Якщо ви використовуєте unionдля зіставлення байтів одного з типів з іншим, то в C # ви можете використовувати його BitConverter.

float fubar = 125f; 
int killroy = BitConverter.ToInt32(BitConverter.GetBytes(fubar), 0);

або;

int killroy = 125;
float fubar = BitConverter.ToSingle(BitConverter.GetBytes(killroy), 0);

4

У C / C ++ об'єднання використовується для накладання різних членів в одному і тому ж місці пам'яті, тому, якщо у вас є об'єднання int і float, вони обидва використовують однакові 4 байти пам'яті для зберігання, очевидно, запис в один пошкоджує інший (оскільки int та float мають різне розташування бітів).

У .Net Microsoft вибрала більш безпечний вибір і не включила цю функцію.

EDIT: крім взаємодії


це може бути тому, що .NET є вищим рівнем, і вам, мабуть, не доведеться турбуватися про явну серіалізацію даних та передачу між машинами little-endian та big-endian. Профспілки пропонують хороший спосіб конвертувати, використовуючи фокус, зазначений у попередньому коментарі.
tloach

1
Використання структур явного макетування не обмежується взаємодією. Примітиви та загальнодоступні члени нереференційних типів можуть накладати інші примітиви та загальнодоступні члени нереференційних типів довільним чином з повністю визначеною поведінкою, незалежно від того, передає код колись ці структури до стороннього коду чи ні. У зв'язку з цим .NET підтримує конструкції нижчого рівня, ніж "сучасні" C.
supercat

1

Особисто я б ігнорував СОЮЗ разом і застосовував Кілрой та Фубара як окремі поля

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

Використання UNION економить 32 біти пам’яті, виділеної int .... не збирається створювати чи ламати програму в наші дні.


8
це може не працювати, залежно від того, як доступ до нього здійснюється іншими частинами бібліотеки, в деяких випадках ви можете писати в один екземпляр і читати з іншого, щоб отримати ті самі дані, але в дещо іншому форматі, ця функціональність буде порушена, якщо ви розділити його на дві різні змінні
KPexEA

1
@KPexEA Я згоден. Однак, якщо, з іншого боку, це тип об’єднання, де якийсь прапорець вказує, яке з двох полів є відповідним, то це буде справно працювати. Я думаю, що насправді важливо зрозуміти використання та призначення об’єднання в кожному конкретному випадку, перш ніж вирішувати, як його перенести.
AaronLS

4
Якщо ви хочете створити структуру з 10 або 20 унізованими типами, оскільки структури є byval, набагато швидше передавати пару байт, ніж 1k на аргумент.
Brain2000,

-2
public class Foo
{
    public float bar;
    public int killroy;

    public float fubar
    {
        get{ return (float)killroy;}
        set{ killroy = (int)value;}
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.