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

Why does interpolating a const string result in a compiler error?

为什么 c# 中的字符串插值不适用于 const 字符串?例如:

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

在我看来,一切都在编译时已知。还是以后会添加的功能?

编译器消息:

The expression being assigned to 'DynamicWebApiBuilder.WEB_API_PROJECT' must be constant.

非常感谢!

内插字符串只是转换为对 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}",编译器应该生成什么代码并不清楚。

更新: 在 C# 10/.Net 6 中,仅包含 const 个字符串的字符串插值可以是 const。所以题中的代码已经不是错误了

roslyn 的 Roslyn 项目中进行了讨论,最终得出以下结论:

阅读摘录:

It's not a bug, it was explicitly designed to function like this. You not liking it doesn't make it a bug. String.Format isn't needed for concatenating strings, but that's not what you're doing. You're interpolating them, and String.Format is needed for that based on the spec and implementation of how interpolation works in C#.

If you want to concatenate strings, go right ahead and use the same syntax that has worked since C# 1.0. Changing the implementation to behave differently based on usage would produce unexpected results:

  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 Compiler Platform ("Roslyn") -String Interpolation for C#

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}.";

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

我们可以select一个函数,而不是select使用哪个字符串:

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}.";
}

C# 10(在撰写本文时目前处于预览状态)将 include the ability to use const interpolated strings, as long as the usage does not involve scenarios where culture may affect the outcome (such as ).

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

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";

您还可以从官方网站查看文档 What's new in C# 10.0