繁体   English   中英

为什么插入 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";

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

编译器消息:

分配给“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调用)。

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

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}" ,编译器应该生成什么代码就不清楚了。

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

阅读摘录:

这不是一个错误,它被明确设计为这样的功能。 你不喜欢它不会让它成为一个错误。 String.Format 不需要连接字符串,但这不是你在做什么。 您正在对它们进行插值,并且需要 String.Format 根据插值在 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内插字符串的能力,只要该用法不涉及文化可能影响结果的场景(例如本示例)。 换句话说,如果插值只是将字符串连接在一起,它将在编译时工作。

如果选择了preview语言版本,现在在 VS 2019 版本 16.9 中允许这样做。

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

在 C# 9.0 或更早版本中,我们不允许const插字符串使用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";

您还可以从官方网站查看文档C# 10.0 的新增功能

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM