C#数组初始化

5

作为一个具有C、C++和汇编语言背景的人,C#让我感到困扰的一件事是我不能像这样做:

struct OperatorType
{
    string Operator;
    TokenType Type;
}

protected static OperatorType[] Operators = {
    { "{", TokenType.OpenBrace }
};

我希望在不需要在运行时进行分配和初始化的情况下声明此内容,但 C# 不允许这样做。
是的,我知道可以使用 new OperatoryType() { Operator = "{", Type = TokenType.OpenBrace } 进行初始化。但这不是涉及运行时分配和初始化内存吗?我知道这并不是很繁琐,但我不明白为什么这里需要这样做。
有人能解释一下为什么 C# 需要这额外的开销,或者可能有一种方法可以在不运行时分配的情况下完成吗?
5个回答

9

无论是在C#还是C++中,分配内存都需要在运行时进行。重要的是分配发生的时间。

如果您在C#中的静态构造函数中进行分配,则将在类型第一次使用之前的某个时间进行分配。这应该是安全的,并且与您的C++版本相比没有任何额外的开销。

此外,有一点需要注意- C#中的运行时内存分配往往比C++便宜得多。这是垃圾收集器的巨大优势。很可能这是过早优化的典型案例。我建议不要担心这个问题,除非您发现了真正的、可衡量的性能问题。


3
C++开发人员也不需要过分担心优化问题。两组程序员(C++和C#)在这个问题上应该都要关注到适当的程度,他们在这个问题上的共性可能比你想象的要多。 - jason
@Jason:如果我看起来不太感兴趣,我很抱歉,但我已经编程了足够长的时间,知道什么时候应该担心优化问题。 - Jonathan Wood
@Reed:你是说操作系统在C++中进行分配吗?在C++中,没有为单个静态变量分配内存。它只为数据映像分配一个更大的内存块。通常为分配稍大的内存块而产生的开销非常小。我不确定C#是怎么做的,但我的new使用让我认为每个静态变量都会调用一个分配例程。这怎么可能比完全没有开销更快呢? - Jonathan Wood
1
@Jonathan:我并不是在建议C#开发人员不需要担心优化,也不希望你有这样的想法。我经常花费大量时间来优化C#代码。我建议C#开发人员(像所有开发人员一样)应该专注于优化正确的事情,而且,在我看来,担心这种非常小的分配可能不是最有效的优化资源使用方式。然而,我认为当人们从C/C++/Assembly背景转向C#时,存在一个常见的缺陷——C#的性能特征截然不同... - Reed Copsey
1
C++和其他语言有很多共同点。许多常见的C++优化实际上会导致C#代码运行效率降低。由于更好的工具支持,关注应该优化的内容以及在不同层面进行优化的机会,这些都真正改变了我们应该如何处理这种类型的问题,我个人认为。 - Reed Copsey
显示剩余10条评论

5
从二进制文件简单映射静态数据的问题在于,它要求所有数据的格式在编译时被冻结。由于运行时确定结构的布局(包括字符串和数组),编译器无法知道布局将是什么样子。即使编译器发出了当前运行时的布局,它也可能会被未来的运行时打破。这意味着只有在该程序集中定义的具有显式布局的结构才能从文件静态映射,这实际上并不太有用。
C#语言规范4.0第7.6.10.4节说:

除非在不安全的上下文中(§18.1),否则数组的布局未指定。

在18.5.8中:

成员打包到结构中的顺序未指定。

.NET 3.5和4.0之间的string布局实际上已经改变(他们删除了一个字段)。
[NonSerialized]
private int m_arrayLength;
[NonSerialized]
private char m_firstChar;
[NonSerialized]
private int m_stringLength;

to

[NonSerialized, ForceTokenStabilization]
private char m_firstChar;
[NonSerialized]
private int m_stringLength;

在C或C++中,这不是问题,因为编译器确定结构体的布局。当然,这也意味着您必须重新编译使用结构体/类的所有内容才能更改其布局。

你知道有没有任何参考文献记录了“运行时确定结构的布局(包括字符串和数组)”这一事实吗?谢谢。 - Jonathan Wood
1
@Jonathan:我有C#规范,其中指出布局是未指定的。这意味着必须由运行时来决定,否则您将无法在使用不同编译器编译的代码上运行在框架上编译的代码。 - Gabe
@Jonathan:我已经编辑了我的答案,展示了在运行时版本4.0中string的定义如何改变。 - Gabe

2
您可以使用非默认构造函数来获得比使用对象初始化语法稍微更高效的版本。实际上除了实际的操作符数组之外,没有运行时分配,构造函数只会为该数组中的每个位置调用一次。
enum TokenType { OpenBrace, T2 };

struct OperatorType
{
    string Operator;
    TokenType Type;

    public OperatorType(string op, TokenType type)
    {
        Operator = op;
        Type = type;
    }
}

static OperatorType[] Operators = {
    new OperatorType( "{", TokenType.OpenBrace )
};

谢谢,但我不确定我理解你的第二句话。对我来说,在数组中为每个项目使用new表示必须为数组中的每个项目进行运行时分配。当然,如果有人能向我展示不同的情况,我也很乐意接受。但是,new不就是这样做的吗? - Jonathan Wood
@BrandonAGr:有趣。根据这个页面,“new运算符也用于调用值类型的默认构造函数。”当然,有时我希望能够避免调用初始化程序。但也许在这种情况下new不会分配内存。我好像在找到关于这一点的确切信息方面遇到了麻烦。 - Jonathan Wood
+1 针对提供更多关于底层发生的信息以及有趣链接的支持。(我确实读了一些相关文章,但是找到的文章并没有解决这里提出的具体问题。) - Jonathan Wood
2
@BrandonAGr,@Jonathan:并不完全正确,当调用值类型的构造函数时,并非没有分配新的存储空间。事实上,值类型的实例并不“拥有”自己的存储空间(因为没有对象标识来进行所有!)这实际上并不相关;相关的问题是构造函数对存储空间的变异是否对其他代码可见。这决定了是否分配(堆栈)存储空间。请参阅http://blogs.msdn.com/b/ericlippert/archive/2010/10/11/debunking-another-myth-about-value-types.aspx获取更多详细信息。 - Eric Lippert
1
еҲҶй…Қе°ұжҳҜеҲҶй…ҚпјӣеҰӮжһңеҲҶй…ҚжҳҜESPзҡ„移еҠЁиҝҳжҳҜGCе ҶйЎ¶йғЁзҡ„移еҠЁпјҢжңүд»Җд№ҲеҢәеҲ«е‘ўпјҹдҪ жҳҜеҗҰжӢ…еҝғ收йӣҶеҺӢеҠӣпјҹ - Eric Lippert
显示剩余3条评论

0
一个简单的回答:出于设计。我相信你问题中的语法在理论上是可以实现的。但正如Eric Lippert经常回答那些像“为什么我不能在C#中……”这样的问题:
因为从未有人设计、说明、实现、测试、文档化和发布该功能。这六项都是必要的,才能让一个功能实现。

我想是这样。这可能是我通常可以判断一个大型应用程序是使用.NET应用程序编写的原因之一,因为它需要很长时间才能加载。有时性能似乎已经不再重要,如果我提出问题,就会得到“不要过早优化”的寻常合唱。我想我认为可能有一些逻辑在决定不支持此功能背后,但也许我太乐观了。 - Jonathan Wood
1
@Gabe:我认为主要原因是因为加载了大量库。然而,我的观点是,性能似乎并不是.NET开发人员普遍关注的问题。 - Jonathan Wood
2
@Gabe:我向您保证,许多初创公司的性能问题都是由于初始化静态数据引起的。我花了很多时间分析 VSTO 的启动性能问题,其中很大一部分原因就是静态初始化器的性能问题。 - Eric Lippert
4
@Jonathan:我向您保证,许多.NET开发人员非常关心性能问题。如果您在启动时遇到性能问题,我建议您使用性能分析工具来确定实际原因;除了加载代码需要大量页面错误外,可能还有很多其他可能性。JIT时间是首要的。可能是静态初始化器、安全检查或任何其他问题。获取一个分析器并找出原因。 - Eric Lippert
1
@Jonathan:我只是想指出,告诉你这些话的人并不代表整个.NET开发者群体。我敢说,他们可能不是讨论这类问题的正确人选。然而,如果他们建议你使用性能分析器,那么他们给出了明智的建议。我们大多数人都很糟糕地猜测性能瓶颈实际上在哪里。 - Robert Harvey
显示剩余5条评论

0
如果您不希望在运行时初始化它,则可以在静态构造函数中创建它。这样,直到您第一次调用类操作符时才会创建它。

是的,这是我经常做的事情。但我仍然不明白为什么这是必要的。 - Jonathan Wood
如果你想知道为什么编程语言开发者采用某种方式,那么你应该在MSDN论坛上提出你的问题。 - Chuck Savage

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接