Исходя из C-фона, какой хороший способ реализовать таблицы/структуры опорных данных const в С#?

Я расскажу о том, что мне известно при реализации, используя C. Основное внимание я считаю о том, как можно использовать данные, а не столько, что я делаю с ним в примере:)

typedef struct  
{
  const char  *description;
  uint32_t    colour_id;      
  uint32_t    quantity;
} my_data_t;

const my_data_t ref_data[] =
{
  {"Brown Bear", 0x88,   10},
  {"Blue Horse", 0x666,  42},
  {"Purple Cat", 123456, 50},
};

void show_animals( void )
{
  my_data_t *ptr;

  ptr = &ref_data[2];

  console_write("Animal: %s, Colour: 0x%8X, Num: %d", 
      ptr->description,
      ptr->colour_id,
      ptr->quantity);
}

Итак, я ищу совет о том, как аналогичные таблицы данных или справочные данные реализованы на С#. Я получаю зависание на стороне более высокого уровня, но пока не разбираюсь в каких-либо табличных методах данных.

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

Это действительно простой пример, но если я могу определить хороший способ его реализации, я могу экстраполировать его на то, что я на самом деле делаю.

Ответ 1

Я бы использовал ReadOnlyCollection<T> неизменяемого класса.

public class MyData
{
    public MyData(string description, int colorId, int quantity)
    {
        Description = description;
        ColorId = colorId;
        Quantity = quantity;
    }
    public string Description {get; private set; }
    public int ColorId {get; private set; }
    public int Quantity {get; private set; }
}


...

public static readonly ReadOnlyCollection<MyData> refData =
    new ReadOnlyCollection<MyData>(
        new [] {
            new MyData("Brown Bear", 0x88,   10),
            new MyData("Blue Horse", 0x666,  42),
            new MyData("Purple Cat", 123456, 50)
            });

Ответ 2

Это

const my_data_t ref_data[] =
{
  {"Brown Bear", 0x88,   10},
  {"Blue Horse", 0x666,  42},
  {"Purple Cat", 123456, 50},
};

можно заменить модификатором readonly в C#, например

//INITIALIZED ONES AND NEVER CHANGED, BY CONVENTION
public static readonly ref_data[] my_data_t = new ref_data[] =
{
  new ref_data{Animal = "Brown Bear", Code = 0x88,   Index = 10},
  new ref_data{Animal = "Blue Horse", Code = 0x666,  Index = 42},
  new ref_data{Animal = "Purple Cat", Code = 123456, index = 50},
};

где ref_data (в данном случае) что-то вроде

public class ref_data
{
   public string Animal {get;set;}
   public int    Code   {get;set;}  //GUESS, PUT APPROPRIATE NAME
   public int    Index  {get;set;}  //GUESS, PUT APPROPRIATE NAME
}

То же самое верно для константы const char *description, используйте readonly.

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

В C# нет понятия указателя константы, поскольку указатели (в управляемой памяти) постоянно перемещали все arround, поскольку Garbage Collector непрерывно сжимает (дефрагментирует) память, чтобы избежать фрагментации памяти, что приносит нам пользу быстрых распределений.

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

Ответ 3

Я отправляю этот ответ в ответ на ваш запрос о том, что я подробно расскажу о своем комментарии в ответе Джо.

Первая точка: если вам нужна my_data_t, чтобы быть структурой по какой-либо причине, С# поддерживает ее. Вам не нужно обновлять его до класса, как это сделал Джо, если вы этого не хотите.

public struct MyData
{
    public string Description;
    public uint ColourID;
    public uint Quantity;
} 

Это изменчивая структура. Учитывая экземпляр этой структуры, я могу изменить ее значения, если я так хочу. Большинство людей скажут, что изменчивые структуры являются злыми. Как разработчик игры, я бы сказал, что изменчивые структуры чрезвычайно важны, но также опасны.

Изменчивые поля и свойства любого объекта могут быть инициализированы с использованием синтаксиса инициализатора объекта, который, вероятно, наиболее похож на то, что вы делаете в C:

MyData x = { Description = "Brown Bear", ColourID = 0x88, Quantity = 10 };

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

Вы можете изменить структуру, которая будет неизменной, добавив модификаторы readonly в свои поля:

public struct MyData
{
    public MyData(string description, uint colourID, uint quantity)
    {
        this.Description = description;
        this.ColourID = colourID;
        this.Quantity = quantity;
    }
    public readonly string Description;
    public readonly uint ColourID;
    public readonly uint Quantity;
} 

Обратите внимание, что readonly только предотвращает изменение ссылок на объекты. Это не мешает объектам быть мутированными, если они изменяемы.

Обратите внимание, что я также добавил конструктор. Это связано с тем, что поля readonly могут быть установлены только в статическом инициализаторе или внутри конструктора объекта (запрет на некоторые обманки). Здесь вы должны инициализировать новый экземпляр MyData, как в ответе Джо:

MyData x = new MyData("Brown Bear", 0x88, 10);

Вторая точка: константа или ее отсутствие. С# не поддерживает константу C-стиля, поскольку константа C-стиля нарушена. Эрик Липперт, ранее разработчик в языковой команде С# в Microsoft, подробно остановился на этом здесь.

Намного лучше, я думаю, не беспокоиться о том, чтобы подражать константе С-стиля, если у тебя действительно нет действительно повод для этого. В конечном счете, Constessa - это способ предотвратить подделку вашего кода вредоносными или невежественными. Злоумышленники смогут изменять ваши данные независимо от того, нравится вам это или нет - на C и на С#, и у нас есть гораздо лучший инструмент для защиты себя от незнания других: encapsulation!

Оберните функциональность, которая использует эту таблицу внутри класса, сделайте таблицу приватным членом этого класса, а затем не измените ее. Если в какой-то момент вам нужно выставить эту таблицу во внешний мир, вы можете использовать ReadOnlyCollection, как предложил Джо:

public static readonly ReadOnlyCollection<MyData> ReferenceTable = new ReadOnlyCollection<MyData>(new []
{
    new MyData(/* whatever */),
    new MyData(/* whatever */),
    new MyData(/* whatever */),
});

ReadOnlyCollection - это всего лишь тонкая оболочка вокруг какой-либо другой коллекции, в данном случае, вашей таблицы данных. Он может обернуть любой объект, который реализует интерфейс IList<T>, который включает в себя массивы и несколько встроенных коллекций.

Еще одно замечание: в одном из ваших комментариев вы упомянули, что одна из причин объявления таблицы const в C заключается в том, что она влияет на то, где объект выделяется в памяти. Здесь не так. В приведенном выше примере RefData будет объявлен в управляемой куче, потому что массив и массивы являются ссылочными типами.