Получить ссылку на структуру внутри массива

Я хочу изменить поле структуры, находящееся внутри массива, без необходимости устанавливать целую структуру. В приведенном ниже примере я хочу установить одно поле элемента 543 в массиве. Я не хочу копировать весь элемент (потому что копирование MassiveStruct повредило бы производительность).

class P
{
    struct S
    {
      public int a;
      public MassiveStruct b;
    }

    void f(ref S s)
    {
      s.a = 3;
    }

    public static void Main()
    {
      S[] s = new S[1000];
      f(ref s[543]);  // Error: An object reference is required for the non-static field, method, or property
    }
}

Есть ли способ сделать это на С#? Или мне всегда приходится копировать всю структуру из массива, изменять копию, а затем возвращать измененную копию в массив.

Ответ 1

Единственная проблема заключается в том, что вы пытаетесь вызвать метод экземпляра из статического метода без экземпляра P.

Сделайте f статический метод (или создайте экземпляр P, на котором его можно вызвать), и все будет хорошо. Все о чтении ошибки компилятора:)

Сказав это, я настоятельно советую вам:

  • Избегайте создания массивных структур, если это вообще возможно.
  • Избегайте создания изменчивых структур, если это вообще возможно.
  • Избегайте публичных полей

Ответ 2

[ изменить 2017: см. важные комментарии относительно С# 7 в конце этого сообщения]

После многих лет борьбы с этой точной проблемой я подведу несколько методов и решений, которые я нашел. Стилистические вкусы в стороне, массивы структур - действительно единственный способ хранения данных, доступный на С#. Если ваше приложение действительно обрабатывает миллионы объектов среднего размера в условиях высокой пропускной способности, выбора нет.

Я согласен с @kaalus, что заголовки объектов и давление в GC могут быстро монтироваться; моя система обработки грамматики может обрабатывать 8-10 гигабайт (или более) структурных анализов менее чем за минуту при разборе или генерации длинных предложений на естественном языке. Cue хор "С# не предназначен для этих проблем, переключается на язык ассемблера, завершает проводку FPGA и т.д. Вместо этого запустите несколько тестов.

Прежде всего, важно иметь полное представление о полном спектре проблем управления типа (struct) и компромиссных классах. Также конечно бокс, пиннинг/небезопасный код, фиксированные буферы, GCHandle, IntPtr, и многое другое, но самое главное, на мой взгляд, разумное использование управляемых указателей.

Ваше мастерство в этой теме также будет включать в себя знание того факта, что если вы включите в свою структуру одну или несколько ссылок на управляемые типы (в отличие от просто смягчаемых примитивов), то ваши варианты доступа к структуре с помощью unsafe значительно уменьшены. Это не проблема для метода управляемого указателя, о котором я расскажу ниже. Таким образом, в целом, в том числе ссылки на объекты хорошо и не сильно меняются в отношении этого обсуждения.

О, и если вам действительно нужно сохранить ваш доступ unsafe, вы можете использовать GCHandle в режиме "Обычный", чтобы хранить ссылки на объекты в своей структуре на неопределенный срок. К счастью, включение GCHandle в вашу структуру не приводит к запрету небезопасного доступа. (Обратите внимание, что GCHandle сам по себе является ценностным типом, и вы даже можете определить и перейти в город с помощью

var gch = GCHandle.Alloc("spookee",GCHandleType.Normal);
GCHandle* p = &gch;
String s = (String)p->Target;

... и так далее. В качестве типа значения GCHandle отображается непосредственно в вашу структуру, но, очевидно, любые типы ссылок, которые он хранит, не являются. Они находятся в куче, не включены в физическую компоновку вашего массива. Наконец, на GCHandle, остерегайтесь его семантики копирования, хотя, потому что у вас будет утечка памяти, если вы не со временем Free каждый GCHandle, который вы выделите.

@Ani напоминает нам, что некоторые люди считают изменчивые структуры "злыми", но это действительно тот факт, что они случайно подвержены этой проблеме. Действительно, ссылаясь на пример OP,

s[543].a = 3;

- это именно то, чего мы пытаемся достичь: доступ к нашим записям данных на месте. (Обратите внимание, что синтаксис для jagged-массива идентичен, но я специально обсуждаю здесь только не-зубчатые массивы определяемых пользователем типов значений.) Для моих собственных программ я обычно считаю это серьезной ошибкой, если я сталкиваюсь с негабаритным blittable struct, которая (случайно) была полностью отображена из строки хранения массива:

rec no_no = s[543];   // don't do
no_no.a = 3           // it like this

Насколько велика (широкая) ваша структура может или должна быть, это не имеет значения, потому что вы будете осторожны, чтобы позволить им делать то, что я только что показал, то есть мигрировать из своего массива. Легко определить структуру для наложения нашего массива (на самом деле, мышление о структуре как пустой шаблон памяти) - в отличие от контейнера данных, инкапсулятора - поощряет правильное мышление.)

public struct rec
{
    public int a, b, c, d, e, f;
}

У этого есть 6 int всего 24 байта. Вы захотите рассмотреть и знать о вариантах упаковки, чтобы получить удобный для выравнивания размер. Но чрезмерное заполнение может сократиться в вашем бюджете памяти, потому что более важным фактором является ограничение на 85 000 байт для объектов, отличных от LOH. Убедитесь, что размер записи, умноженный на ожидаемое количество строк, не превышает этого предела.

Итак, для этого примера вам будет лучше всего поддерживать ваш массив rec не более 3000 строк каждый. Надеюсь, ваше приложение может быть спроектировано вокруг этого сладкого пятна. Это не так ограничивает, когда вы помните это - альтернативно - каждая строка будет отдельным объектом, собранным с мусором, а не только одним массивом. Вы сократили распространение своего объекта на три порядка, что хорошо для дневной работы. Таким образом, среда .NET здесь сильно управляет нами с довольно определенным ограничением: кажется, что если вы нацеливаете свой дизайн памяти приложения на монолитные распределения в диапазоне 30-70 КБ, то вам действительно удастся с большим и большим количеством из них, и на самом деле вместо этого вы будете ограничены более узким узлом узких мест производительности (а именно, пропускной способностью на аппаратной шине).

Итак, теперь у вас есть единственный ссылочный тип .NET(массив) с 3 000 6-кортежей в физически смежных табличных хранилищах. Прежде всего, мы должны быть очень осторожными, чтобы никогда не "поднимать" одну из структур. Как отмечает Джон Скит, "массовые структуры часто будут хуже, чем классы", и это абсолютно правильно. Нет лучшего способа парализовать вашу шину памяти, чем начинать бросать пустые значения типов волей-неволей.

Итак, давайте воспользуемся нередко упомянутым аспектом массива структур: все объекты (и поля этих объектов или структур) всех строк всего массива всегда инициализируются значениями по умолчанию. Вы можете начать подключать значения, по одному за раз, в любой строке или столбце (поле) в любом месте массива. Вы можете оставить некоторые поля по умолчанию или заменить соседние поля, не нарушая их посередине. Gone - это досадная ручная инициализация, требуемая с помощью структур-резидентных (локальных переменных) структур перед использованием.

Иногда трудно поддерживать подход "за каждым полем", потому что .NET всегда пытается заставить нас взорваться во всей структуре new d-up, но для меня эта так называемая "инициализация" это просто нарушение нашего табу (против выщипывания всей структуры из массива), в другом обличье.

Теперь мы перейдем к сути дела. Очевидно, что доступ к вашим табличным данным in-situ сводит к минимуму перетасовку данных. Но часто это неудобная хлопот. Доступ к массивам может быть медленным в .NET из-за проверки границ. Итак, как вы поддерживаете "рабочий" указатель во внутреннюю часть массива, чтобы избежать постоянной перекомпоновки системы с помощью смещений индексации.

Оценка

Позвольте оценить производительность пяти различных методов для обработки отдельных полей в рядах хранения массива значений. Нижеприведенный тест предназначен для измерения эффективности интенсивного доступа к полям данных структуры, расположенным в некотором индексе массива in situ, то есть "где они лежат", без извлечения или перезаписи всей структуры (элемента массива). Сравниваются пять различных методов доступа, при этом все остальные факторы сохраняются.

Пять методов заключаются в следующем:

  • Обычный, прямой доступ к массиву через квадратные скобки и поле спецификатора поля. Обратите внимание, что в .NET массивы являются особым и уникальным примитивом Common Type System. Как упоминает выше @Ani, этот синтаксис не может использоваться для изменения отдельного поля ссылочного экземпляра, такого как список, даже если он параметризуется с помощью типа значения.
  • Использование недокументированного ключевого слова __makeref С#.
  • Управляемый указатель с помощью делегата, который использует ключевое слово ref
  • "Небезопасные" указатели
  • То же, что и # 3, но с помощью функции С# вместо делегата.

Прежде чем дать результаты теста С#, выполните реализацию тестового жгута. Эти тесты выполнялись на .NET 4.5, сборке AnyCPU, работающей на x64, Workstation gc. (Заметим, что, поскольку тест не интересует эффективность выделения и де-распределения самого массива, упомянутое выше рассмотрение LOH не применяется.)

const int num_test = 100000;
static rec[] s1, s2, s3, s4, s5;
static long t_n, t_r, t_m, t_u, t_f;
static Stopwatch sw = Stopwatch.StartNew();
static Random rnd = new Random();

static void test2()
{
    s1 = new rec[num_test];
    s2 = new rec[num_test];
    s3 = new rec[num_test];
    s4 = new rec[num_test];
    s5 = new rec[num_test];

    for (int x, i = 0; i < 5000000; i++)
    {
        x = rnd.Next(num_test);
        test_m(x); test_n(x); test_r(x); test_u(x); test_f(x);
        x = rnd.Next(num_test);
        test_n(x); test_r(x); test_u(x); test_f(x); test_m(x);
        x = rnd.Next(num_test);
        test_r(x); test_u(x); test_f(x); test_m(x); test_n(x);
        x = rnd.Next(num_test);
        test_u(x); test_f(x); test_m(x); test_n(x); test_r(x);
        x = rnd.Next(num_test);
        test_f(x); test_m(x); test_n(x); test_r(x); test_u(x);
        x = rnd.Next(num_test);
    }
    Debug.Print("Normal (subscript+field):          {0,18}", t_n);
    Debug.Print("Typed-reference:                   {0,18}", t_r);
    Debug.Print("C# Managed pointer: (ref delegate) {0,18}", t_m);
    Debug.Print("C# Unsafe pointer:                 {0,18}", t_u);
    Debug.Print("C# Managed pointer: (ref func):    {0,18}", t_f);
}

Поскольку фрагменты кода, которые реализуют тест для каждого конкретного метода, являются long-ish, я дам результаты в первую очередь. Время - тики; более низкое означает лучше.

Normal (subscript+field):             20,804,691
Typed-reference:                      30,920,655
Managed pointer: (ref delegate)       18,777,666   // <- a close 2nd
Unsafe pointer:                       22,395,806
Managed pointer: (ref func):          18,767,179   // <- winner

Я был удивлен, что эти результаты были настолько однозначными. TypedReferences являются самыми медленными, предположительно потому, что они захватывают информацию о типе вместе с указателем. Учитывая количество IL-кода для продвинутой версии "Normal", он показал удивительно хорошие результаты. Переходы в режимах, похоже, вредят небезопасному коду до такой степени, что вам действительно нужно оправдывать, планировать и измерять каждое место, которое вы собираетесь его развернуть.

Но максимальное время рук достигается за счет использования ключевого слова ref в параметрах функций, предназначенных для указания на внутреннюю часть массива, что устраняет массив "для каждого поля" вычисления индексирования.

Возможно, дизайн моего теста соответствует этому, но тестовые сценарии являются типичными для эмпирических шаблонов использования в моем приложении. Что меня удивило в этих числах, так это то, что преимущество пребывания в управляемом режиме - с указанием ваших указателей тоже - не было отменено, если вы вызывали функцию или вызывали через делегата.

Победитель

Самый быстрый из них: (И, может быть, и простейший?)

static void f(ref rec e)
{
    e.a = 4;
    e.e = e.a;
    e.b = e.d;
    e.f = e.d;
    e.b = e.e;
    e.a = e.c;
    e.b = 5;
    e.d = e.f;
    e.c = e.b;
    e.e = e.a;
    e.b = e.d;
    e.f = e.d;
    e.c = 6;
    e.b = e.e;
    e.a = e.c;
    e.d = e.f;
    e.c = e.b;
    e.e = e.a;
    e.d = 7;
    e.b = e.d;
    e.f = e.d;
    e.b = e.e;
    e.a = e.c;
    e.d = e.f;
    e.e = 8;
    e.c = e.b;
    e.e = e.a;
    e.b = e.d;
    e.f = e.d;
    e.b = e.e;
    e.f = 9;
    e.a = e.c;
    e.d = e.f;
    e.c = e.b;
    e.e = e.a;
    e.b = e.d;
    e.a = 10;
    e.f = e.d;
    e.b = e.e;
    e.a = e.c;
    e.d = e.f;
    e.c = e.b;
}
static void test_f(int ix)
{
    long q = sw.ElapsedTicks;
    f(ref s5[ix]);
    t_f += sw.ElapsedTicks - q;
}

Но недостатком является то, что вы не можете совмещать связанную логику в своей программе: реализация функции делится на две функции С#, f и test_f.

Мы можем решить эту конкретную проблему только с незначительной жертвой производительности. Следующий в основном идентичен вышесказанному, но встраивает одну из функций в другую как функцию лямбда...

A Close Second

Замена статической функции в предыдущем примере встроенным делегатом требует использования аргументов ref, что, в свою очередь, исключает использование синтаксиса Func<T> лямбда; вместо этого вы должны использовать явный делегат из старого .NET.

Добавив эту глобальную декларацию один раз:

delegate void b(ref rec ee);

... мы можем использовать его во всей программе непосредственно ref в элементы массива rec [], обращаясь к ним inline:

static void test_m(int ix)
{
    long q = sw.ElapsedTicks;
    /// the element to manipulate "e", is selected at the bottom of this lambda block
    ((b)((ref rec e) =>
    {
        e.a = 4;
        e.e = e.a;
        e.b = e.d;
        e.f = e.d;
        e.b = e.e;
        e.a = e.c;
        e.b = 5;
        e.d = e.f;
        e.c = e.b;
        e.e = e.a;
        e.b = e.d;
        e.f = e.d;
        e.c = 6;
        e.b = e.e;
        e.a = e.c;
        e.d = e.f;
        e.c = e.b;
        e.e = e.a;
        e.d = 7;
        e.b = e.d;
        e.f = e.d;
        e.b = e.e;
        e.a = e.c;
        e.d = e.f;
        e.e = 8;
        e.c = e.b;
        e.e = e.a;
        e.b = e.d;
        e.f = e.d;
        e.b = e.e;
        e.f = 9;
        e.a = e.c;
        e.d = e.f;
        e.c = e.b;
        e.e = e.a;
        e.b = e.d;
        e.a = 10;
        e.f = e.d;
        e.b = e.e;
        e.a = e.c;
        e.d = e.f;
        e.c = e.b;
    }))(ref s3[ix]);
    t_m += sw.ElapsedTicks - q;
}

Кроме того, хотя может показаться, что при каждом вызове создается новая лямбда-функция, это не произойдет, если вы будете осторожны: при использовании этого метода убедитесь, что вы не "закрываете" любые локальные переменные ( то есть ссылаться на переменные, которые находятся за пределами лямбда-функции, изнутри своего тела) или сделать что-нибудь еще, что препятствует тому, чтобы ваш экземпляр делегата был статическим. Если локальная переменная попадает в вашу лямбда, и поэтому лямбда получает повышение до экземпляра/класса, вы, вероятно, заметите разницу, поскольку она пытается создать пять миллионов делегатов.

Пока вы избегаете лямбда-функции от этих побочных эффектов, не будет нескольких экземпляров; что происходит здесь, когда С# определяет, что лямбда не имеет неявных зависимостей, она лениво создает (и кэширует) статический синглтон. Немного досадно, что чередование производительности настолько радикально скрыто от нашего представления, что это тихая оптимизация. В целом, мне нравится этот метод. Он быстрый и беспорядочный - за исключением странных круглых скобок, ни одна из которых здесь не может быть опущена.

А остальные

Для полноты, вот остальные тесты: обычный брекетинг-плюс-точка; TypedReference; и небезопасные указатели.

static void test_n(int ix)
{
    long q = sw.ElapsedTicks;
    s1[ix].a = 4;
    s1[ix].e = s1[ix].a;
    s1[ix].b = s1[ix].d;
    s1[ix].f = s1[ix].d;
    s1[ix].b = s1[ix].e;
    s1[ix].a = s1[ix].c;
    s1[ix].b = 5;
    s1[ix].d = s1[ix].f;
    s1[ix].c = s1[ix].b;
    s1[ix].e = s1[ix].a;
    s1[ix].b = s1[ix].d;
    s1[ix].f = s1[ix].d;
    s1[ix].c = 6;
    s1[ix].b = s1[ix].e;
    s1[ix].a = s1[ix].c;
    s1[ix].d = s1[ix].f;
    s1[ix].c = s1[ix].b;
    s1[ix].e = s1[ix].a;
    s1[ix].d = 7;
    s1[ix].b = s1[ix].d;
    s1[ix].f = s1[ix].d;
    s1[ix].b = s1[ix].e;
    s1[ix].a = s1[ix].c;
    s1[ix].d = s1[ix].f;
    s1[ix].e = 8;
    s1[ix].c = s1[ix].b;
    s1[ix].e = s1[ix].a;
    s1[ix].b = s1[ix].d;
    s1[ix].f = s1[ix].d;
    s1[ix].b = s1[ix].e;
    s1[ix].f = 9;
    s1[ix].a = s1[ix].c;
    s1[ix].d = s1[ix].f;
    s1[ix].c = s1[ix].b;
    s1[ix].e = s1[ix].a;
    s1[ix].b = s1[ix].d;
    s1[ix].a = 10;
    s1[ix].f = s1[ix].d;
    s1[ix].b = s1[ix].e;
    s1[ix].a = s1[ix].c;
    s1[ix].d = s1[ix].f;
    s1[ix].c = s1[ix].b;
    t_n += sw.ElapsedTicks - q;
}


static void test_r(int ix)
{
    long q = sw.ElapsedTicks;
    var tr = __makeref(s2[ix]);
    __refvalue(tr, rec).a = 4;
    __refvalue(tr, rec).e = __refvalue( tr, rec).a;
    __refvalue(tr, rec).b = __refvalue( tr, rec).d;
    __refvalue(tr, rec).f = __refvalue( tr, rec).d;
    __refvalue(tr, rec).b = __refvalue( tr, rec).e;
    __refvalue(tr, rec).a = __refvalue( tr, rec).c;
    __refvalue(tr, rec).b = 5;
    __refvalue(tr, rec).d = __refvalue( tr, rec).f;
    __refvalue(tr, rec).c = __refvalue( tr, rec).b;
    __refvalue(tr, rec).e = __refvalue( tr, rec).a;
    __refvalue(tr, rec).b = __refvalue( tr, rec).d;
    __refvalue(tr, rec).f = __refvalue( tr, rec).d;
    __refvalue(tr, rec).c = 6;
    __refvalue(tr, rec).b = __refvalue( tr, rec).e;
    __refvalue(tr, rec).a = __refvalue( tr, rec).c;
    __refvalue(tr, rec).d = __refvalue( tr, rec).f;
    __refvalue(tr, rec).c = __refvalue( tr, rec).b;
    __refvalue(tr, rec).e = __refvalue( tr, rec).a;
    __refvalue(tr, rec).d = 7;
    __refvalue(tr, rec).b = __refvalue( tr, rec).d;
    __refvalue(tr, rec).f = __refvalue( tr, rec).d;
    __refvalue(tr, rec).b = __refvalue( tr, rec).e;
    __refvalue(tr, rec).a = __refvalue( tr, rec).c;
    __refvalue(tr, rec).d = __refvalue( tr, rec).f;
    __refvalue(tr, rec).e = 8;
    __refvalue(tr, rec).c = __refvalue( tr, rec).b;
    __refvalue(tr, rec).e = __refvalue( tr, rec).a;
    __refvalue(tr, rec).b = __refvalue( tr, rec).d;
    __refvalue(tr, rec).f = __refvalue( tr, rec).d;
    __refvalue(tr, rec).b = __refvalue( tr, rec).e;
    __refvalue(tr, rec).f = 9;
    __refvalue(tr, rec).a = __refvalue( tr, rec).c;
    __refvalue(tr, rec).d = __refvalue( tr, rec).f;
    __refvalue(tr, rec).c = __refvalue( tr, rec).b;
    __refvalue(tr, rec).e = __refvalue( tr, rec).a;
    __refvalue(tr, rec).b = __refvalue( tr, rec).d;
    __refvalue(tr, rec).a = 10;
    __refvalue(tr, rec).f = __refvalue( tr, rec).d;
    __refvalue(tr, rec).b = __refvalue( tr, rec).e;
    __refvalue(tr, rec).a = __refvalue( tr, rec).c;
    __refvalue(tr, rec).d = __refvalue( tr, rec).f;
    __refvalue(tr, rec).c = __refvalue( tr, rec).b;
    t_r += sw.ElapsedTicks - q;
}

static void test_u(int ix)
{
    long q = sw.ElapsedTicks;

    fixed (rec* p = &s4[ix])
    {
        p->a = 4;
        p->e = p->a;
        p->b = p->d;
        p->f = p->d;
        p->b = p->e;
        p->a = p->c;
        p->b = 5;
        p->d = p->f;
        p->c = p->b;
        p->e = p->a;
        p->b = p->d;
        p->f = p->d;
        p->c = 6;
        p->b = p->e;
        p->a = p->c;
        p->d = p->f;
        p->c = p->b;
        p->e = p->a;
        p->d = 7;
        p->b = p->d;
        p->f = p->d;
        p->b = p->e;
        p->a = p->c;
        p->d = p->f;
        p->e = 8;
        p->c = p->b;
        p->e = p->a;
        p->b = p->d;
        p->f = p->d;
        p->b = p->e;
        p->f = 9;
        p->a = p->c;
        p->d = p->f;
        p->c = p->b;
        p->e = p->a;
        p->b = p->d;
        p->a = 10;
        p->f = p->d;
        p->b = p->e;
        p->a = p->c;
        p->d = p->f;
        p->c = p->b;
    }
    t_u += sw.ElapsedTicks - q;
}

Резюме

Для интенсивной работы с памятью в крупномасштабных приложениях С# с помощью управляемых указателей для прямого доступа к полям элементов значений типа массива in-situ является способ идти.

Если вы действительно серьезно относитесь к производительности, это может быть достаточным основанием для использования C++/CLI (или CIL, если на то пошло) вместо C# для соответствующих частей вашего приложения, поскольку эти языки позволяют вам для прямого объявления управляемых указателей внутри тела функции.

В C# единственный способ создать управляемый указатель - объявить функцию с аргументом ref или out, после чего вызываемый будет наблюдать управляемый указатель. Таким образом, чтобы получить преимущества производительности в С#, вы должны использовать один из методов (два верхних), показанный выше. [см. С# 7 ниже]

К сожалению, они развертывают kludge для разделения функции на несколько частей только для доступа к элементу массива. Несмотря на то, что значительно менее элегантный, чем эквивалентный код C++/CLI, тесты показывают, что даже в С# для высокопроизводительных приложений мы по-прежнему получаем большую производительность по сравнению с наивным доступом к массиву значений.


[ изменить 2017:Хотя, возможно, давая небольшую степень предвидения этим статьям в целом, выпуск С# 7 в Visual Studio 2017 одновременно приводит к тому, что описанные выше специфические методы полностью устарели. Короче говоря, новая функция ref locals на этом языке позволяет вам объявить свой собственный управляемый указатель как локальную переменную и использовать ее для консолидации операция разыменования одиночного массива. Так дается, например, тестовая структура сверху...

public struct rec { public int a, b, c, d, e, f; }
static rec[] s7 = new rec[100000];

... вот как теперь можно записать одну и ту же тестовую функцию сверху:

static void test_7(int ix)
{
    ref rec e = ref s7[ix];         // <---  C#7 ref local
    e.a = 4;  e.e = e.a; e.b = e.d; e.f = e.d; e.b = e.e; e.a = e.c;
    e.b = 5;  e.d = e.f; e.c = e.b; e.e = e.a; e.b = e.d; e.f = e.d;
    e.c = 6;  e.b = e.e; e.a = e.c; e.d = e.f; e.c = e.b; e.e = e.a;
    e.d = 7;  e.b = e.d; e.f = e.d; e.b = e.e; e.a = e.c; e.d = e.f;
    e.e = 8;  e.c = e.b; e.e = e.a; e.b = e.d; e.f = e.d; e.b = e.e;
    e.f = 9;  e.a = e.c; e.d = e.f; e.c = e.b; e.e = e.a; e.b = e.d;
    e.a = 10; e.f = e.d; e.b = e.e; e.a = e.c; e.d = e.f; e.c = e.b;
}

Обратите внимание, что это полностью устраняет необходимость в клодах, таких как те, о которых я говорил выше. Более гладкое использование управляемого указателя позволяет избежать ненужного вызова функции, который использовался в "победителе", наиболее эффективной методологии тех, которые я рассмотрел. Поэтому производительность с новой функцией может быть лучше, чем победитель вышеописанных методов.

Как ни странно, С# 7 также добавляет локальные функции, которая напрямую решает жалобу о плохом инкапсуляции, которую я поднял для двух из вышеупомянутые хаки. достаточно Счастливо целое предприятия пролиферирующего посвященной функции только с целью получения доступа к управляемым указателям теперь полностью спорный вопрос.

Ответ 3

В то время как Джон Скит прав, почему ваша программа не компилируется, вы можете просто:

s[543].a = 3;

... и он будет работать непосредственно на структуре в массиве, а не на копии.

Обратите внимание, что эта идея работает только для массивов, другие коллекции, такие как списки, возвращают копию из индексатора-получателя (давая вам ошибку компилятора, если вы попробуете что-то подобное по результирующему значению).

В другой заметке, изменчивые структуры считаются злыми. Есть ли веская причина, почему вы не хотите делать S класс?

Ответ 4

Вы можете попытаться использовать пересылку пустой структуры, которая не содержит фактические данные, а только хранит индекс для объекта-дататора. Таким образом, вы можете хранить огромные объемы данных, не усложняя граф объектов. Я очень уверен, что в вашем случае должно быть довольно легко заменить вашу гигантскую структуру структурой пересылки emtpy, если вы не пытаетесь ее перевести в неуправляемый код.

Посмотрите на эту структуру. Он может содержать как можно больше данных внутри него. Фокус в том, что вы храните фактические данные в другом объекте. Таким образом, вы получаете ссылочную семантику и преимущества структур, которые потребляют меньше памяти, чем объекты класса, и более быстрые циклы GC из-за более простого графа объектов (если у вас много экземпляров (миллионов) из них).

    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct ForwardingEmptyValueStruct
    {
        int _Row;
        byte _ProviderIdx;


        public ForwardingEmptyValueStruct(byte providerIdx, int row)
        {
            _ProviderIdx = providerIdx;
            _Row = row;
        }

        public double V1
        {
            get { return DataProvider._DataProviders[_ProviderIdx].Value1[_Row];  }
        }

        public int V2
        {
            get { return DataProvider._DataProviders[_ProviderIdx].Value2[_Row];  }
        }
    }