【问题标题】:Why does interpolating a const string result in a compiler error?为什么插入 const 字符串会导致编译器错误?
【发布时间】:2017-01-19 23:40:05
【问题描述】:

为什么 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调用。
  • 要解决方法,请将const 替换为static readonly
  • @juharr:这并没有真正解释为什么编译器无法检查您传递的参数是否是另一个可用于初始化此常量的常量。允许使用串联的字符串文字。那么为什么不允许使用组合字符串文字,编译器可以使用相同的技术。
  • @TimSchmelter 这就是我要说的。感谢您指出!
  • 当字符串被传递到像 SwaggerResponseAttribute 这样的编译时约束时,不能使用静态只读。试图将 Description 属性设置为嵌入常量的字符串,并且不能使用新的 $"" 格式,因为它不支持常量。跛脚。

标签: c# .net c#-6.0 string-interpolation


【解决方案1】:

内插字符串被简单地转换为对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)。

【讨论】:

  • 这是有道理的。非常感谢:)
  • 那个简单的连接很清楚,只是想我也可以在这里使用这个新的 c# 6 功能。
  • @Rene,即使使用 [private static readonly string] ,编译器也会引发错误“当前上下文中不存在名称 'WEB_API_ROOT'”。变量 'WEB_API_ROOT' 应该在相同的上下文中定义。
  • @M.Hassan,你做错了什么。它对我有用。
  • @OlivierJacot-Descombes,是的,我使用最新版本 c#7.3(与 2017.9.2 相比)进行测试,它正在工作 private static readonly string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json",看来它可以由 Roslyn 解决
【解决方案2】:

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

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。所以问题中的代码不再是错误了。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 请注意,这里的要点似乎或多或少是这样的:可以定义(和设置)一个假设的默认区域性来修改所有格式化的字符串,这甚至会改变 string.Format() 的返回值仅包含 const strings 的调用,在运行时。由于该区域性检查最多可能在静态初始化期间发生,但 const 字符串值集仅在编译器时发生,const 内插字符串可能在运行时变得不等于内插字符串的值,如果编译时的文化不是运行时的文化。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      C# 10(目前在撰写本文时处于预览状态)将 include the ability 使用 const 插值字符串,只要使用不涉及文化可能影响结果的场景(例如 this example)。

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

      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

      【讨论】:

        【解决方案6】:

        在 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# 语言的功能和增强功能。

        .NET 6.0 框架中提供了 C# 10.0 功能,因为我们可以使用它。 见下面的代码,目前是 C# 10.0 (Preview 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查看文档

        【讨论】:

          猜你喜欢
          • 2011-08-01
          • 2014-01-19
          • 2020-04-26
          • 2016-05-13
          • 2019-11-18
          • 1970-01-01
          • 2017-06-05
          • 2011-06-09
          相关资源
          最近更新 更多