【问题标题】:Why can't structs be declared as const? [duplicate]为什么不能将结构声明为 const? [复制]
【发布时间】:2011-06-03 05:04:27
【问题描述】:

【问题讨论】:

  • 您可以将结构声明为 const。类和结构是可互换的,它适用于类。
  • @templatetypedef:我应该提到“除了 null”吗?
  • @templatetypedef:我不确定您使用的是哪个版本的 C#,但在我见过的任何版本上都不是这样。
  • 天啊!我完全误解了这一点。对不起!
  • structs 从什么时候开始就必须不可变了?

标签: c# struct stack constants


【解决方案1】:

因为值类型构造函数可能会做任何事情——例如,根据一天中的时间切换逻辑。常量值类型在智力上是有意义的,但由于构造函数可以随心所欲地做任何事情,因此它根本无法在实践中用于自定义值类型。 (请记住,在编译时评估常量,这意味着您的构造函数必须在编译时运行。)

【讨论】:

  • 谢谢,当 S/O 允许时会接受。太糟糕了,如果我可以说,我有一个“合法”的构造函数,我很想拥有它。但是哦,好吧,我猜我会找到另一种设计。
  • @Lazlo:即便如此,编译器仍必须在编译时调用它。但从技术上讲,编译器不应该运行您的代码。
  • 我想我正在寻找 C++ 的聚合或 POD 结构的等价物:en.wikipedia.org/wiki/C%2B%2B_classes 只是不同的值类型或数据组的东西。哦,好吧。
  • 可以在没有构造函数的情况下创建结构。可以访问结构字段的代码可以简单地用所需的值填充它们;具有字段和值列表的编译器可以创建适当配置的结构实例。
  • @cdhowie:问题不在于性能,而在于语义,因为const 值可以用于静态只读字段不能使用的情况(例如,作为默认参数值)。它们也被“烘焙”到编译后的代码中,这在某些情况下是一件坏事,但在其他情况下可能很有用。
【解决方案2】:

为了让 C# 编译器生成结构类型的 const 值,它必须知道在其所有字段中应该包含哪些值。 C# 编译器本质上知道如何初始化某些类型的字段,例如 Decimal,但对于大多数值类型,它没有这样的知识。

编译器可以提供一种在所有 struct 字段都暴露的上下文中声明结构类型的常量值的方法。如果结构的字段是private,则该类型的常量只能在结构内声明;如果字段为internal,则可以在程序集中的任何位置声明常量;如果public,它们可以在任何地方声明。

虽然我希望看到这样的功能,但我不希望任何主流 .net 语言实现它。编译器本身知道的类型的命名常量可以参与其他常量表达式,而static readonly 变量不能。如果NumRows 是一个等于4 的常数,那么Arr[3*NumRows+7] 之类的表达式可以替换为Arr[19],即使NumRows 是在外部程序集中定义的。与static readonly 变量相比,这使此类常量具有显着优势。但是,如果常量是编译器本质上无法识别的类型,那么它参与任何常量表达式的能力将非常有限,从而首先有效地否定了它作为常量的优势。如果一个值类型常量有一个暴露的字段,编译器就有可能将该字段的值用作常量,但是由于 .net 语言的创建者在哲学上反对具有暴露字段的结构,我不希望他们允许这样的使用,即使他们可以。

有一些潜在的用例倾向于使用常量而不是static readonly 变量,但是使用现有类型可以可接受地处理许多此类情况。例如,一个库可能会公开一个const Int64 来编码有关其版本的信息,并将该值用作GetLinkedVersionInfo 方法的默认参数值。有问题的值会在编译时“融入”调用代码,从而允许该方法报告调用者链接的库版本,并可能确定它正在运行的版本是否存在任何兼容性问题。

【讨论】:

    【解决方案3】:

    我刚刚用一个简单的可变结构测试了readonly 关键字:

    struct Test
    {
       public int value;
    
       public void setInt(int val)
       {
          value = val;
       }
    }
    
    static class Program
    {
       public static readonly Test t = new Test();
    
       static void Main()
       {
          Console.WriteLine(t.value); // Outputs "0"
          t.setInt(10);
          //t.value = 10;  //Illegal, will not let you assign field of a static struct
          Console.WriteLine(t.value); // Still outputs "0"
       }
    }
    

    尽管readonly 结构在技术上不是编译时间常数,但运行时不会让它改变。甚至步进抛出了setInt() 方法它看起来像值变化,但没有显示Main 中的变化。

    我猜结构本身被放置在“只读”内存中,不允许它们更改。不像一个类,它只是保持指针不变,允许类字段本身随心所欲地改变。

    所以看起来static readonly 实际上是const,即使对于可变结构也是如此。

    【讨论】:

    • 结构上的所有实例方法、属性和事件都接收到对它们应该操作的结构的引用——相当于接收this 作为ref 参数——但是对于一个烦人的vb.net 和 C# 中的怪癖:如果在只读结构实例上调用结构方法或属性,编译器将默默地制作该实例的副本并将其传递给相关代码。在代码不修改结构的情况下,这会产生缓慢但正确的语义,而在代码确实修改结构的情况下会产生破坏的语义。我希望微软能定义一个属性...
    • ...这将允许结构属性和方法指示它们是否影响传入的方法,以便编译器可以允许在读取时使用不修改this 的方法只有结构,但禁止使用这样做的方法。否则,最好的办法是忍受烦人且丑陋的语法 public static void setInt(ref Test it, int val) {it.value = val;} 和 Test.setInt(ref t, 10);,如果你在只读变量 t 上尝试它,它们会正确地尖叫。
    • @supercat,所以我们真的只是希望 MS 完成将 C 的 const 的语义复制到 C# 中:-D
    • @binki:C++ 中的常量正确性在声明为 const 的指针不存在时会引起很多麻烦。也许部分是因为这个,Java 避免了任何“只读引用”类型的概念。然而,批评 const 正确性的人未能认识到的是,在编写正确的代码时,有必要知道一个方法是否可以修改传入的对象,假设传入的对象永远不会被其他任何东西修改,或者两者都不会;语言没有提供表达这些东西的方法并不意味着程序员不需要知道它们......
    • ...或者关于哪些方法可以处理或期望传入的对象的错误信念不会导致错误。然而,关于 ref 参数与对象引用,我认为 .NET 会更好地使用不同的复制输入、复制输出、复制输入输出、只读引用和读写引用参数类型。
    【解决方案4】:

    C# 中的 const 意味着它可以在编译时确定,这就是为什么只有非常原始的类型,例如 int 和 string 可以是 const。

    如果您有 C 背景,readonly 关键字可能更适合您。

    【讨论】:

    • 这才是有C/C++背景的程序员的真正答案。
    • @Eonil 不是 100%; C/C++ const 关键字同时满足 C# 的 const 和 readonly 的目的——例如,在 C# 中添加两个 readonly int 字段不会导致常量折叠,而在 C 中添加两个 const int 变量/C++ 将(如果值在编译时已知)。 C# 的 const 更接近于 C++11 的 constexpr 修饰符,而 C# 的 readonly 在 C/C++ 中没有单一的模拟。
    • @cdhowie,我认为类似于readonly T where T : struct 将是const T&(在C++ 中)。然而,大多数时候人们会在 C# 中使用 readonly 关键字,实际上是在寻找 C++ 对 C# 中不存在的“const 正确性”的支持(并且新的一组不可变类/模式试图提供一个类似但相当笨拙且劳动密集型的替代方案)。
    • @binki 不同意。 const 引用是存在于其他地方的另一个对象的别名。 C# 中的 readonly T 创建一个不可变字段,其中值存储在该字段本身中 - 而不是其他地方,就像 C++ 引用的情况一样。
    • "...这就是为什么只有非常原始的类型(例如 int 和 string)才能成为 const。我不同意,C++ 也是如此(自 C++11 起)。我不明白为什么我们不能在 C# 中使用像 C++ constexpr 这样的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 2018-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多