【问题标题】:Coming from a C background, what's a good way to implement const reference data tables/structures in C#?来自 C 背景,在 C# 中实现 const 引用数据表/结构的好方法是什么?
【发布时间】:2012-11-29 11:04:45
【问题描述】:

我将给出一个我熟悉的使用 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);
}

所以我正在寻找有关如何在 C# 中实现类似数据表或参考数据的建议。我掌握了更高层次的东西,但我还没有处理任何表驱动的数据方法。

作为一个例子,我可能在 C# 中尝试做的是有一个组合框允许从 description 字段中进行选择,而 color idquantity 可用于更新只读框。

这是一个非常简单的例子,但如果我能确定一种实现它的好方法,我可以将其推断为我实际正在做的事情。

【问题讨论】:

  • 如果你真的来自 C 背景,你很快就会发现 C# 不好... +1 无论如何都是一个好问题。
  • @H2CO3:可争议的评论。每种语言在世界上都有其适当的用途和位置,尤其是广泛使用的语言,例如C#。如果你让F1 超级跑车驶过高尔夫球场,将会导致失败。让我们在合适的地方使用漂亮的工具......
  • @Tigran 这个比喻值得一票。
  • @Tigran 我同意……在很多方面,C 允许优秀的程序员做一些非常强大的事情。它也允许糟糕的程序员做一些真正可怕的事情! :)
  • 我的意思是,使用C# 在 90% 的情况下,您可以比在 CC++ 中做的事情更多,即使您感觉自己在那种语言中的工作效率很高。 .NET非常强大和高效的环境,不要低估它。当然,我再说一遍,如果您正在编写编译器、驱动程序、游戏、CAD 程序,如果可以的话,最好选择其他的。

标签: c# c


【解决方案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)
            });

【讨论】:

  • +1 用于实用的 c# 方法。您甚至可以使用匿名类型以获得更大的灵活性。
  • Crikey... 这真的是一种处理表数据的等价方法吗?谢谢,我要开始复习了。一个很好的例子,说明 C 让您以更少的努力处理原始数据:)
  • @Joe 也许我提到它是 const 实际上并没有必要......我习惯这样做是出于良好的实践(将数据存储在闪存而不是 RAM 中),但也许不是在 C# 中甚至是必要的?在这种情况下,我会只使用 Collection 吗?
  • 为什么要创建一个数组来调用ToList呢?只需使用new List&lt;MyData&gt; 而不是new []
  • C# 不像 C 那样提供 constness 保证,主要是因为 C 的“保证”不是一个。试图在 C# 上强制执行该习惯用法不可避免地会导致额外的代码。如果您真的需要表格不可修改,这是正确的方法。否则,您可能会使用只读数组和适当的封装级别来获得同样的收益。另请注意,此答案通过将您的数据结构扩展为一个类来添加一些代码,这在 C# 中更为惯用;如果您愿意,可以更紧密地匹配您的原始代码。
【解决方案2】:

这个

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

可以用C#中的readonly修饰符代替,比如

//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# 中没有常量指针的概念,因为指针(在托管内存中)不断移动,因为垃圾收集器不断收缩(碎片整理)内存,以避免内存碎片,这给我们带来了快速分配的好处。

还有另一种选择(不知道这是否适合您的情况),您可以使用 unmanged 访问代码,通过 unsafe 修饰符并保留所有C/C++ 里面有指针的东西。这样,你对 Grabage Collector 说:“等等,我知道我在做什么”,所以 all 内存管理必须由你来处理(在非托管 代码)就好像写普通的C/C++ 代码一样。

【讨论】:

  • 我认为 C# 不支持 typedef?
  • 嗨,我认为这并不能真正回答这个问题,抱歉......我感兴趣的是如何使用数据表来驱动 C# 方法,而不是指针是否是常量(例如) .
  • @DarcyWilliams:不,不存在,不存在typedef,也没有#define(就创建强大的宏而言)。您可以使用#define 之类的条件编译指令,仅此而已。
  • @DarcyWilliams:你是什么意思?您询问可能的数据结构,我在回答中提供了它们。我错过了什么吗?
  • @DarcyWilliams - C# 不支持typedef。你需要一个新的结构。
【解决方案3】:

我发布此答案是为了响应您在乔的回答中详细说明我的评论的要求。

第一点:如果您出于某种原因需要 my_data_t 成为结构,C# 确实支持这些。除非您愿意,否则您不必像 Joe 那样将其升级为类。

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 的新实例,如 Joe 的回答:

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

第二点:恒常性,或缺乏恒常性。 C# 不支持 C 风格的 constness,因为 C 风格的 constness 已损坏。 Eric Lippert 曾是微软 C# 语言团队的一名开发人员,他详细阐述了这一点 here

我认为,除非你真的、真的、真的有充分的理由,否则不用担心模拟 C 风格的 constness 会好得多。一致性最终是一种防止您的代码被恶意或无知者篡改的方法。无论您喜欢与否,恶意软件都将能够改变您的数据——在 C 和 C# 中——我们有一个更好的工具来保护自己免受他人的无知:封装!

将使用该表的功能封装在一个类中,使该表成为该类的私有成员,然后不要改变它。如果在某些时候您确实需要将该表暴露给外界,那么您可以按照 Joe 的建议使用 ReadOnlyCollection

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

ReadOnlyCollection 只是一些其他集合的薄包装,在这种情况下,您的数据表。它可以包装任何实现IList&lt;T&gt; 接口的对象,其中包括数组和一些内置集合。

另一个注意事项:在您的一个 cmets 中,您提到在 C 中声明表 const 的原因之一是因为它会影响对象在内存中的分配位置。这不是这里的情况。在上面的示例中,RefData 将在托管堆上声明,因为它是一个数组,而数组是引用类型。

【讨论】:

  • 一些吹毛求疵:“ReadOnlyCollection ... 可以包装任何实现 IEnumerable 接口的对象” - 不,它包装了 IList,没有构造函数采用 IEnumerable。此外,您的“ReferenceTable”属性可能不应该在每次调用时都实例化一个新的 ReadOnlyCollection。
  • 你是对的;我的记忆让我失望了。我更新了我的答案。至于你的第二点,我同意你的观点,但这是一个足够明显的优化,我不想用它来混淆我的例子。
  • 经过进一步考虑,我再次更改了答案以反映您的担忧。
  • @ColeCampbell 谢谢伙计,这与我最终实现的非常相似......减去只读方面。我意识到这更多的是我熟悉在 C 中使用以确保数据从闪存加载而不是复制到 RAM 的遗留问题。谢谢
猜你喜欢
  • 1970-01-01
  • 2017-01-02
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多