$符号在字符串前面是什么意思?

307

我本来想使用原样字符串,但我误打了$而不是@

但编译器没有报错,成功编译通过。

我想知道它是什么以及它的作用。我搜索了一下,但是没有找到任何相关信息。

然而,它与原样字符串不同,因为我不能这样写:

string str = $"text\";

在C#中,字符串前面的$字符代表什么意思?

string str = $"text";

我正在使用 Visual Studio 2015 CTP。

11个回答

492

$String.Format的简写,并与字符串插值一起使用,这是C# 6的新功能。在您的情况下,它不起作用,就像string.Format()一样无效。

当用于基于其他值构建字符串时,才能充分发挥其作用。以前必须编写如下内容:

var anInt = 1;
var aBool = true;
var aString = "3";
var formated = string.Format("{0},{1},{2}", anInt, aBool, aString);

现在变成:

var anInt = 1;
var aBool = true;
var aString = "3";
var formated = $"{anInt},{aBool},{aString}";

还有一种不太常见的字符串插值方式——使用$@(两个符号的顺序很重要)。它允许混合使用@""字符串的特性和$""以支持字符串插值,而无需在整个字符串中使用\\。因此,以下两行代码:

var someDir = "a";
Console.WriteLine($@"c:\{someDir}\b\c");

将输出:

c:\a\b\c

41
请注意,实际上并没有使用String.Format,而是一种基于编译器而非运行时的特性。 - Shahar Prish
2
今天我学到了一个小技巧,如果你使用$@,那么你必须通过使用""来转义"字符。但是当你只使用$时,情况并非如此。 - Flater
4
@Flater 那与$符号无关。这与$符号存在之前的行为相同。 - BVernon
3
关于您提到的verbatim(@)和插值($)符号顺序的重要性问题,C# 8正在进行改正,使得顺序不再重要。详情请见:http://www.devsanon.com/uncategorized/c-8-is-fixing-interpolated-verbatim-strings/ - elkaz
非常好的解释。对于如此全面的方法(包括简单主题),总是点赞。 - A. Dzebo

43

它创建了一个插值字符串

来自MSDN

用于构造字符串。插值字符串表达式看起来像一个包含表达式的模板字符串。插值字符串表达式通过将包含的表达式替换为表达式结果的ToString表示形式来创建一个字符串。

例如:

 var name = "Sam";
 var msg = $"hello, {name}";

 Console.WriteLine(msg); // hello, Sam

你可以在插值字符串中使用表达式

 var msg = $"hello, {name.ToLower()}";
 Console.WriteLine(msg); // hello, sam

它的好处在于,与使用 String.Format 时一样,您不需要担心参数的顺序。

  var s = String.Format("{0},{1},{2}...{88}",p0,p1,..,p88);

现在如果你想要删除某些参数,你必须去更新所有计数,但这不再是必须的。

注意,如果你想在格式化中指定区域设置信息,那么传统的string.format仍然相关。


请注意,如果您在 $ 表达式内部将数据转换为字符串并使用正确的区域设置,则仍然可以使用 $,例如 {somevar.ToString(...,[在此处插入区域设置])} - jrh

22

示例代码

public class Person {
    public String firstName { get; set; }
    public String lastName { get; set; }
}

// Instantiate Person
var person = new Person { firstName = "Albert", lastName = "Einstein" };

// We can print fullname of the above person as follows
Console.WriteLine("Full-Name - " + person.firstName + " " + person.lastName);
Console.WriteLine("Full-Name - {0} {1}", person.firstName, person.lastName);
Console.WriteLine($"Full-Name - {person.firstName} {person.lastName}");

输出

Full-Name - Albert Einstein
Full-Name - Albert Einstein
Full-Name - Albert Einstein

这是插值字符串,你可以在任何可以使用字符串字面量的地方使用插值字符串。当运行程序时,代码执行具有插值字符串字面量的代码,通过计算插值表达式来生成新的字符串字面量。每次执行带有插值字符串的代码时,都会进行这个计算。

以下示例生成一个字符串值,其中所有字符串插值值都已计算。它是最终结果,并具有字符串类型。所有双大括号 (“{{“ and “}}”) 的出现都会转换为单个大括号。

string text = "World";
var message = $"Hello, {text}";

执行以上两行代码后,变量 message 包含 "Hello, World"。

Console.WriteLine(message); // Prints Hello, World

参考资料 - MSDN


1
不错的回答。但是你不能在声明常量字符串时使用它(const string constantString = $"Default is {someValue}" 不支持这种写法)。 - Late Starter

11

请注意,您还可以组合这两者,这非常酷(虽然看起来有点奇怪):

// simple interpolated verbatim string
WriteLine($@"Path ""C:\Windows\{file}"" not found.");

5
如果你可以决定输入“$@”或“@$”的顺序,那该多好啊。不过很遗憾,只能是“$@”。 - Bauss
3
这很有道理。@定义了字符串字面量的表示方式。 $string.Format的快捷方式。将其视为$(@""); - marsze
4
我只是在说$基本上是一个隐式的函数调用,而@是字面量的一部分,就像十进制字面量中的m一样。这就是为什么只有一个逻辑顺序。 - marsze
2
我知道这主要是为了展示 $,但为了最大兼容性,不硬编码目录分隔符是 '/' 还是 '',并且避免不可避免的错误导致双斜杠或缺少应有的斜杠,我建议在处理目录和文件时使用Path.Combine()而不是使用字符串连接。 - jrh
@marsze 不得不说一下(我正在处理一个代码库,作者只使用字符串拼接而没有用到 Path.Combine())。 - jrh
显示剩余2条评论

11
Cool功能。我只想指出为什么这比string.format更好的强调,如果对某些人不明显的话。
我看到有人说要将string.format的顺序设置为“ {0} {1} {2}”以匹配参数。您不必在string.format中按顺序排列“ {0} {1} {2}”,您也可以这样做“ {2} {0} {1}”。但是,如果您有很多参数,比如20个,您真的希望将字符串序列化为“ {0} {1} {2} ... {19}”。如果它是一团乱麻,你会很难排列你的参数。
使用$,您可以内联添加参数,而无需计算参数。这使得代码更易于阅读和维护。 $的缺点是,您无法轻松地在字符串中重复使用参数,您必须键入它。例如,如果您厌倦了输入System.Environment.NewLine,则可以执行string.format("... {0} ... {0} ... {0} ",System.Environment.NewLine),但是在$中,您必须重复它。您不能执行`${0}`并将其传递给string.format,因为`${0}`返回“ 0”。
附带说一句,在另一个重复的主题中,我已经阅读了一条评论。我无法发表评论,所以在这里。他说
string msg = n + " sheep, " + m + " chickens";

创建了多个字符串对象。实际上这不是真的。如果您在单行中执行此操作,它只会创建一个字符串并放置在字符串缓存中。

1) string + string + string + string;
2) string.format()
3) stringBuilder.ToString()
4) $""

它们都返回一个字符串,并且只在缓存中创建一个值。

另一方面:

string+= string2;
string+= string2;
string+= string2;
string+= string2;

因为有四个“;”,所以在缓存中创建了4个不同的值。

因此,编写以下代码将更加容易,但是您将创建五个插值字符串,就像Carlos Muñoz所更正的那样:

string msg = $"Hello this is {myName}, " +
  $"My phone number {myPhone}, " +
  $"My email {myEmail}, " +
  $"My address {myAddress}, and " +
  $"My preference {myPreference}.";

这将在缓存中创建一个单一的字符串,同时您可以编写非常易于阅读的代码。我不确定性能如何,但我确信如果尚未进行优化,微软将对其进行优化。


1
你的最后一个例子是错误的:实际上你创建了两个字符串:一个来自插值字符串,另一个来自其余的字符串。请注意,只有带有 {myName} 的那个被插值,其他的不会按预期工作。 - Carlos Muñoz
1
如果在这5个字符串前加上$符号,那么它会创建5个带有自己的String.Format()的插值字符串,并在运行时使用String.Concat连接起来。因此最好不要将其拆分为多行。 - Carlos Muñoz
1
你是对的@Carlos Muñoz,我已经纠正了它。感谢你发现了这个错误。 - BoBoDev

7
以下示例突出了使用插值字符串(interpolated strings)相对于 string.Format() 的各种优点,就代码的清晰度和可读性而言。它还表明,在 {} 内部的代码会像任何其他函数参数一样被评估,就像调用 string.Format() 时一样。
using System;

public class Example
{
   public static void Main()
   {
      var name = "Horace";
      var age = 34;
      // replaces {name} with the value of name, "Horace"
      var s1 = $"He asked, \"Is your name {name}?\", but didn't wait for a reply.";
      Console.WriteLine(s1);

      // as age is an integer, we can use ":D3" to denote that
      // it should have leading zeroes and be 3 characters long
      // see https://learn.microsoft.com/en-us/dotnet/standard/base-types/how-to-pad-a-number-with-leading-zeros
      //
      // (age == 1 ? "" : "s") uses the ternary operator to 
      // decide the value used in the placeholder, the same 
      // as if it had been placed as an argument of string.Format
      //
      // finally, it shows that you can actually have quoted strings within strings
      // e.g. $"outer { "inner" } string"
      var s2 = $"{name} is {age:D3} year{(age == 1 ? "" : "s")} old.";
      Console.WriteLine(s2); 
   }
}
// The example displays the following output:
//       He asked, "Is your name Horace?", but didn't wait for a reply.
//       Horace is 034 years old.

7

$语法很好,但有一个缺点。

如果你需要像字符串模板这样的东西,它在类级别上作为字段声明...应该只有一个地方。

然后你必须在同一级别上声明变量...这不太好。

对于这种情况,使用string.Format语法要好得多。

class Example1_StringFormat {
 string template = $"{0} - {1}";

 public string FormatExample1() {
   string some1 = "someone";
   return string.Format(template, some1, "inplacesomethingelse");
 }

 public string FormatExample2() {
   string some2 = "someoneelse";
   string thing2 = "somethingelse";
   return string.Format(template, some2, thing2);
 }
}

使用全局变量不是一个很好的做法,而且它也不能与全局变量一起工作。

 static class Example2_Format {
 //must have declaration in same scope
 static string some = "";
 static string thing = "";
 static string template = $"{some} - {thing}";

//This returns " - " and not "someone - something" as you would maybe 
//expect
 public static string FormatExample1() {
   some = "someone";
   thing = "something";
   return template;
 }

//This returns " - " and not "someoneelse- somethingelse" as you would 
//maybe expect
 public static string FormatExample2() {
   some = "someoneelse";
   thing = "somethingelse";
   return template;
 }
}

这个答案很重要,因为它指出插值发生在你“调用”$string时,而不是在你声明它时。 - dx_over_dt
1
你对在类作用域中声明插值变量的反模式是正确的,但是如果这些变量已经属于类属性,那么这种模式就很有效。 - dx_over_dt
@dx_over_dt,您错了。插值字符串在声明时就会被计算。这就是为什么示例代码毫无意义的原因。它也不会编译。 - NineBerry
@NineBerry 你说得对,插值字符串在声明时就被评估了,Example_$Format无法编译,示例代码也没有意义 :) 我已经纠正了示例以更好地解释它。 - Tom

7

它表示字符串内插。

它将保护您,因为它在字符串评估时添加了编译时保护。

您将不再使用 string.Format("{0}{1}", secondParamIsMissing) 抛出异常。


7

这比使用string.Format更方便,并且您也可以在此处使用智能感知。

enter image description here

这是我的测试方法:

[TestMethod]
public void StringMethodsTest_DollarSign()
{
    string name = "Forrest";
    string surname = "Gump";
    int year = 3; 
    string sDollarSign = $"My name is {name} {surname} and once I run more than {year} years."; 
    string expectedResult = "My name is Forrest Gump and once I run more than 3 years."; 
    Assert.AreEqual(expectedResult, sDollarSign);
}

6

我不知道它是如何工作的,但你也可以使用它来制表你的值!

示例:

Console.WriteLine($"I can tab like {"this !", 5}.");

当然,你可以把“this !”替换为任何变量或有意义的内容,同样你也可以更改制表符。

是的,您也可以格式化字符串。请参考https://msdn.microsoft.com/zh-cn/library/dn961160.aspx。 - M.kazem Akhgary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接