为什么插入const字符串会导致编译器错误

本文关键字:编译器 错误 插入 const 字符串 为什么 | 更新日期: 2023-09-27 18:06:51

为什么c#中的字符串插值不能与const字符串一起工作?例如:

private const string WEB_API_ROOT = "/private/WebApi/";
private const string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";

在我看来,在编译时一切都是已知的。或者这是一个稍后会添加的功能?

编译器信息:

分配给'DynamicWebApiBuilder '的表达式。WEB_API_PROJECT'必须为常量。

非常感谢!

为什么插入const字符串会导致编译器错误

插入的字符串被简单地转换为对string.Format的调用。所以上面这行实际上是

private const string WEB_API_PROJECT = string.Format("{0}project.json", WEB_API_ROOT);

这不是编译时常数,因为包含了方法调用。


另一方面,字符串连接(简单的常量字符串字面值)可以由编译器完成,所以这将工作:
private const string WEB_API_ROOT = "/private/WebApi/";
private const string WEB_API_PROJECT = WEB_API_ROOT + "project.json";

或从const切换到static readonly:

private static readonly string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";

,因此字符串在第一次访问声明类型的任何成员时被初始化(并调用string.Format)。

字符串插值表达式不被认为是常量的另一个解释是它们不是常量,即使它们的所有输入都是常量。具体来说,它们根据当前的文化而有所不同。尝试执行以下代码:

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine($"{3.14}");
CultureInfo.CurrentCulture = new CultureInfo("cs-CZ");
Console.WriteLine($"{3.14}");

输出为:

3.14
3,14

请注意,输出是不同的,即使在这两种情况下字符串插值表达式是相同的。因此,对于const string pi = $"{3.14}",编译器应该生成什么代码并不清楚。

UPDATE:Net 6,只包含const字符串的字符串插入可以是const。所以问题中的代码不再是错误了。

Roslyn项目中有一个讨论,最终得出以下结论:

阅读节选:

这不是一个bug,它被明确地设计成这样的功能。你不喜欢它不代表它就是bug。字符串。连接字符串不需要格式,但这不是必需的你在做什么。你在插入它们,还有字符串。格式是需要基于规范和实现如何插值在c#中工作。

如果你想连接字符串,去吧并使用c# 1.0以来的相同语法。根据使用情况改变实现的不同行为产生意想不到的结果:

  const string FOO = "FOO";
  const string BAR = "BAR";
  string foobar = $"{FOO}{BAR}";
  const string FOOBAR = $"{FOO}{BAR}"; // illegal today
  Debug.Assert(foobar == FOOBAR); // might not always be true

即使是语句:

  private static readonly string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";

编译器抛出错误:

 "The name 'WEB_API_ROOT' does not exist in the current context". 

变量'WEB_API_ROOT'应该在相同的上下文中定义

那么,对于OP的问题:为什么字符串插值不能与const字符串一起工作?答:这是c# 6的规范。要了解更多细节,请参阅。net编译器平台("Roslyn") - c#的字符串插值

c# 10(在撰写本文时目前处于预览阶段)将包括使用const插入字符串的能力,只要使用不涉及文化可能影响结果的场景(如本例)。

因此,如果插值只是简单地将字符串连接在一起,它将在编译时工作。

const string Language = "C#";
const string Platform = ".NET";
const string Version = "10.0";
const string FullProductName = $"{Platform} - Language: {Language} Version: {Version}";

如果选择preview语言版本(在项目文件中设置<LanguageVersion>preview</LanguageVersion>),现在允许在VS 2019版本16.9中这样做。

https://github.com/dotnet/csharplang/issues/2951#issuecomment-736722760

在c# 9.0或更早的版本中,我们不允许使用const插入字符串。如果你想将常量字符串合并在一起,你必须使用串联而不是插值。

const string WEB_API_ROOT = "/private/WebApi/";
const string WEB_API_PROJECT = WEB_API_ROOT + "project.json";

但是从c# 10.0开始允许const内插字符串作为c#语言的特性和增强。

c# 10.0特性在。net 6.0框架中可用,这样我们就可以使用它了。见下面的代码,目前为c# 10.0(预览5)

const string WEB_API_ROOT = "/private/WebApi/";
const string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";

你也可以从官方网站查看文档

string.Format使用的常量,从本质上讲,是打算使用特定数量的参数,每个参数都有一个预定的含义。

换句话说,如果你创建了这个常量:

const string FooFormat = "Foo named '{0}' was created on {1}.";

然后为了使用它,你必须有两个参数,可能应该是stringDateTime

所以即使在字符串插值之前,我们在某种意义上使用常数作为函数。换句话说,与其将常量分开,不如将其放在函数中更有意义,就像这样:

string FormatFooDescription(string fooName, DateTime createdDate) =>
    string.Format("Foo named '{0}' was created on {1}.", fooName, createdDate);

仍然是一样的,除了常量(字符串字面量)现在位于使用它的函数和参数中。它们也可以放在一起,因为格式字符串对于任何其他目的都是无用的。更重要的是,现在您可以看到应用于格式字符串的参数的意图。

当我们这样看它的时候,类似的字符串插值的使用就变得很明显了:

string FormatFooDescription(string fooName, DateTime createdDate) =>
    $"Foo named '{fooName}' was created on {createdDate}.";

如果我们有多个格式字符串,我们想在运行时选择一个特定的字符串,该怎么办?

不需要选择使用哪个字符串,而可以选择一个函数:

delegate string FooDescriptionFunction(string fooName, DateTime createdDate);

那么我们可以这样声明实现:

static FooDescriptionFunction FormatFoo { get; } = (fooName, createdDate) => 
    $"Foo named '{fooName}' was created on {createdDate}.";

或者更好:

delegate string FooDescriptionFunction(Foo foo);
static FooDescriptionFunction FormatFoo { get; } = (foo) => 
    $"Foo named '{foo.Name}' was created on {foo.CreatedDate}.";
}