C#中的private const和private readonly变量有什么区别吗?

35
是否在C#中拥有私有的const变量和私有的static readonly变量之间存在差异(除了必须为const分配编译时表达式之外)?
由于它们都是私有的,所以不会与其他库链接。 那么它会有任何区别吗? 例如,它能否产生性能差异? 集合字符串? 任何类似的东西?
9个回答

37

你可以在属性中使用常量,因为它们存在于编译时。你无法预测静态只读变量的值,因为.cctor可能从配置等方面进行初始化。

就使用而言,常量被烧入调用代码中。这意味着如果重新编译一个公共常量所在的 DLL,但不更改使用者,则使用者仍将使用原始值。对于只读变量,这种情况不会发生。相反,常量(非常,非常轻微地)更快,因为它仅加载值(而不必解引用)。

关于内部化;虽然您可以手动执行此操作,但这通常是文字字面量的编译器/运行时特性;如果通过文本字面量初始化只读字段:

someField = "abc";

那么"abc"将被interned。如果你从配置文件中读取它,它将不会被interned。因为常量字符串必须是文字,所以它也会被interned,但它的访问方式不同:再次,从字段中读取是一个解引用,而不是ldstr


2
幸运的是,对于私有常量来说,这不会成为问题——但在正常使用中,这是最重要的区别。 - Jon Skeet
谢谢。我在另一个问题的答案中了解到了相关程序集的影响。加载与取消引用之间的区别很有趣,ldstr 也是如此。非常感谢! - Hosam Aly

14

事实上,在初始化后,这两种类型都不能更改,但它们之间存在一些差异:

  • 'const'必须在声明时(编译时)被初始化,而'readonly'可以在声明时或构造函数内部(运行时)被初始化。

例如,const可在以下情况下使用:

public class MathValues
{
  public const double PI = 3.14159;
}

只读对于这种情况更好:

public class Person
{
    public readonly DateTime birthDate;

    public Person(DateTime birthDate)
    {
        this.birthDate = birthDate;
    }
}
或者
public class Person
{
    public readonly DateTime birthDate = new DateTime(1986, 1, 24);
}
  • 'const' 是静态的,因此它在该类的所有实例之间共享,并且可以直接访问(例如 MathValues.PI),而 'readonly' 不是静态的。因此,类似 'static const' 的声明是非法的,因为 const 是静态的,但是 'static readonly' 是合法的。

  • 'const' 只能保存整数类型(sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal、bool 或 string)、枚举或 null 引用(不包括类或结构,因为它们会在运行时使用 'new' 关键字初始化),而 'readonly' 可以保存复杂类型、结构或类(通过在初始化时使用 new 关键字),但不能保存枚举。


谢谢,但我的问题更多关于副作用,而不是语言结构本身。 - Hosam Aly

5
这里是C# .NET中const、readonly和static readonly字段之间的区别(来自this article)。
常量:
- 默认为静态 - 必须具有编译时值(即:可以有“A”+“B”,但不能有方法调用) - 可以在属性中使用 - 复制到使用它们的每个程序集中(每个程序集都获得值的本地副本) - 可以在函数内声明
只读实例字段:
- 在创建实例时进行评估 - 必须在构造函数退出时设置值
静态只读字段:
  • 当代码执行到类引用时(即创建新实例或执行静态方法时)进行评估
  • 在静态构造函数完成时必须具有已计算的值
  • 您真的不想在这些上放置ThreadStaticAttribute(因为静态构造函数将仅在一个线程中执行,并且它将为其线程设置值;所有其他线程将具有未初始化的值)

5

在C#.Net中,constreadonly字段之间有显著的区别。

const默认为静态,并且需要用常量值进行初始化,以后不能修改该值。构造函数中也不允许更改值。它不能与所有数据类型一起使用,例如DateTime。无法与DateTime数据类型一起使用。

public const DateTime dt = DateTime.Today;  //throws compilation error
public const string Name = string.Empty;    //throws compilation error
public readonly string Name = string.Empty; //No error, legal

readonly可以声明为静态的,但不是必须的。在声明时无需初始化。它的值可以使用构造函数进行赋值或更改。因此,在用作实例类成员时具有优势。两个不同的实例化可能具有不同的只读字段值。 例如 -

class A
{
    public readonly int Id;

    public A(int i)
    {
        Id = i;
    }
}

只读字段可以使用以下方式初始化特定的实例值:
```html

然后 readonly 字段可以使用以下方法初始化特定的实例值:

```
A objOne = new A(5);
A objTwo = new A(10);

在这里,实例objOne的只读字段值为5,而objTwo的值为10。这是使用const不可能实现的。


4

关于常量需要注意的一点是它们实际上存储在您的可执行文件中,因此声明了许多常量会增加您的可执行文件大小。

通常情况下,这不是一个很大的问题,但我的一个朋友曾在一家公司工作,该公司实行“一切都必须是常量”的规定,并成功地显著增加了他们编译后的可执行文件大小。


听起来像是一篇 DailyWTF 的故事。 - Jon Limjap
一个没有常量,全部数据库的方法肯定更糟糕...我应该知道。 - configurator
2
相信我,在奇怪的编码准则方面,这只是冰山一角。其他宝石包括强制 else 块,即使它们是空的,以及始终在内部调用公共属性。 - Soviut
@Stijn,我发表这个答案已经很久了,以至于我已经忘记了上下文。我想我可能是在指大型可执行文件与资源文件之间的区别,以及编译一个与另一个所需的额外时间。 - Soviut

2

使用中?并不完全是这样。常量在编译时计算,而只读变量在运行时计算。您也可以在构造函数中为只读变量分配值。


谢谢。但是没有其他的区别吗?也许const字符串会被interned,但readonly不会?(我不确定,只是在问。) - Hosam Aly
JIT编译器将像const一样对只读变量进行内部化处理。 - Hans Passant

1

static readonly字段的区别在于其值是在运行时设置的,因此可以被包含它的类修改,而const字段的值则被设置为编译时常量。

在静态只读情况下,包含它的类只允许在以下情况下进行修改:

在变量声明中(通过变量初始化器) 在静态构造函数中(如果不是静态,则在实例构造函数中) 通常情况下,如果字段的类型不允许在const声明中使用,或者当值在编译时未知时,会使用static readonly。

实例只读字段也是允许的。

请记住,对于引用类型,在静态和实例两种情况下,readonly修饰符仅防止您将新引用分配给该字段。它特别不会使被引用的对象成为不可变的。

class Program

{

  public static readonly Test test = new Test();

  static void Main(string[] args)

  {

     test.Name = "Program";

     test = new Test(); // Error: A static readonly field cannot be assigned to (except in a static constructor or a variable initializer)

  }

}

class Test

{

   public string Name;

}

区别在于静态只读可以被包含类修改,但常量永远不能被修改,并且必须初始化为编译时常量。进一步说,对于静态只读情况,包含类只能在以下情况下修改它:
- 在变量声明中(通过变量初始化器)。 - 在静态构造函数中(如果不是静态,则在实例构造函数中)。

C# .NET 中的 Const 关键字

示例:public const string abc = “xyz”; 只能在声明时初始化。 值在编译时计算,运行时不能更改。 试图更改它将导致编译错误。 Const 已经是一种静态类型。 由于类和结构体使用 new 关键字在运行时初始化,因此无法将常量设置为类或结构体。但是,它必须是整数类型之一。 C# .NET 中的 Readonly 关键字

示例:public readonly string abc; 可以在声明代码或构造函数代码中初始化。 值在运行时计算。 可以声明为静态或实例级别属性。 只读字段可以通过在运行时使用 new 关键字来保存复杂对象。


1

还有一件事。虽然我可能错过了,但我在上面的评论中没有看到这一点。您无法创建常量数组。

private const int[] values = new int[] { 1, 2, 3 };

但是你可以使用静态只读字段来创建它。

private static readonly int[] values = new int[] { 1, 2, 3 };

如果您需要一个数组常量,比如一组可允许的值,并且枚举不合适的话,那么静态只读是唯一的选择。例如,如果数组是可空整数数组,如下所示:

private static readonly int?[] values = new int?[] { null, 1, 2, 3 };

常量不支持这样的操作,对吧?


数组本身并不是只读的,只有数组变量是只读的。换句话说,values不能被设置为另一个数组,但是可以操作数组的元素:values[0] = 99或者Array.Sort(values)都会改变数组的值,即使它被声明为只读。虽然有一个ImmutableArray类型,但这个话题超出了const和readonly关键字的基本实用性,并且偏离了问题的重点。 - C Perkins

0

只读字段可以在类的声明或构造函数中进行初始化。因此,只读字段可以根据使用的构造函数而具有不同的值。

只读成员也可以用于运行时常量,如下例所示:

public static readonly uint currentTicks = (uint)DateTime.Now.Ticks;

只读字段并不是隐式静态的,因此如果需要,可以(必须)显式地将 static 关键字 应用于只读字段。但对于常量字段,则不允许这样做,因为它们是隐式静态的。

只读成员可以通过在初始化时使用 new 关键字 来持有复杂对象。


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