为什么插入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'必须为常量。
非常感谢!
插入的字符串被简单地转换为对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}.";
然后为了使用它,你必须有两个参数,可能应该是string
和DateTime
。
所以即使在字符串插值之前,我们在某种意义上使用常数作为函数。换句话说,与其将常量分开,不如将其放在函数中更有意义,就像这样:
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}.";
}