C#中的行继续字符


72

我们有一个包含变量的单元测试,该变量包含一个很长的字符串。

问题是如何用代码编写此代码,而不会出现换行符问题或代码难以阅读。

在VB中有一个行继续符,在C#中有一个对等的字符吗?


1
创建一个数组,然后将其加入。
嬉皮


将字符串放入资源文件中。
phuzi

1
@leppie,不要这样做:那么,您实际上是在创建(运行时)需要串联的多个字符串(在运行时)。由加号分隔的多个字符串将由编译器连接。
bvgheluwe

Answers:


68

C#允许您将字符串分成多行,该术语称为verbatim literal

string myString = @"this is a
                    test
                   to see how long my string
                   can be



                    and it can be quite long";

如果您正在寻找& _VB的替代方案,请使用+来加入您的行。


24
myString将填充空格。不要这样
caiosm1005 2014年

12
如果要将SQL字符串嵌入代码中,则值得这样做。SQL解析器不在乎空白,并且在程序和SQL客户端之间传输此类语句时要容易得多。
Phil Cairns 2014年

3
但是想想字节浪费!
tofutim

1
还要注意,这将在最终字符串中包含所有\ r \ n换行符。
bitbonk

3
@NH。-可以肯定的是,评论简直就是舌头。
rory.ap

62

字符串常量

只需使用+运算符,然后将字符串分成易于理解的行即可。编译器会发现这些字符串是常量,并在编译时将它们连接起来。请参阅此处的《 MSDN C#编程指南》。

例如

const string myVeryLongString = 
    "This is the opening paragraph of my long string. " +
    "Which is split over multiple lines to improve code readability, " +
    "but is in fact, just one long string.";

IL_0003: ldstr "This is the opening paragraph of my long string. Which is split over multiple lines to improve code readability, but is in fact, just one long string."

字符串变量

请注意,在使用字符串插值法将值替换为字符串时,$字符需要在需要进行替换的每一行之前:

var interpolatedString = 
    "This line has no substitutions. " +
    $" This line uses {count} widgets, and " +
    $" {CountFoos()} foos were found.";

但是,这会对string.Format字符串的多次调用和最终串联(标记为***)造成负面的性能影响。

IL_002E:  ldstr       "This line has no substitutions. "
IL_0033:  ldstr       " This line uses {0} widgets, and "
IL_0038:  ldloc.0     // count
IL_0039:  box         System.Int32
IL_003E:  call        System.String.Format ***
IL_0043:  ldstr       " {0} foos were found."
IL_0048:  ldloc.1     // CountFoos
IL_0049:  callvirt    System.Func<System.Int32>.Invoke
IL_004E:  box         System.Int32
IL_0053:  call        System.String.Format ***
IL_0058:  call        System.String.Concat ***

尽管您可以$@用来提供单个字符串并避免性能问题,但是除非将空格放在{}其中(IMO看起来很奇怪),否则此问题与Neil Knight的回答相同,因为它将在行细目表中包括所有空格:

var interpolatedString = $@"When breaking up strings with `@` it introduces
    <- [newLine and whitespace here!] each time I break the string.
    <- [More whitespace] {CountFoos()} foos were found.";

注入的空白很容易发现:

IL_002E:  ldstr       "When breaking up strings with `@` it introduces
    <- [newLine and whitespace here!] each time I break the string.
    <- [More whitespace] {0} foos were found."

另一种方法是还原为string.Format。在这里,按照我的最初答案,格式化字符串是一个常量:

const string longFormatString = 
    "This is the opening paragraph of my long string with {0} chars. " +
    "Which is split over multiple lines to improve code readability, " +
    "but is in fact, just one long string with {1} widgets.";

然后这样评估:

string.Format(longFormatString, longFormatString.Length, CountWidgets());

但是,考虑到格式化字符串和替换标记之间的潜在分隔,要保持这种状态仍然很棘手。


1
实际上,每当您在未终止的字符串文字上按Enter键时,Resharper都会尝试自动执行此操作。
StuartLC 2015年

1
这也是我们处理长字符串文字的方式。特别是对于每个人都应努力做到的有用的错误消息!
乔什·P

对于内插字符串,串联技巧并不总是可行的。例如,某些EntityFrameworkCore API方法现在需要一个FormattableString对象作为参数。这意味着它希望在已被插值并将其转换为普通字符串之前接收一个格式表字符串,但是串联操作不会保留FormattableString对象,而是会强制立即对字符串进行插值/格式化。
C Perkins'4


5

您可以使用逐字文字:

const string test = @"Test
123
456
";

但是第一行的缩进是棘手的/丑陋的。


2

在他的出色回答中,StuartLC引用一个相关问题的答案,并提到在{expression}插值字符串的内部放置换行符“看起来很奇怪”。大多数人都同意,但是通过使用{expression}解析为default(String),即null(特别是不是 String.Empty)的专用块,可以在某种程度上缓解不愉快的源代码效应,并且不会造成任何运行时后果。

(尽管是次要的)重点是不要弄乱或污染您的实际表达式内容,而应为此目的使用专用令牌。因此,如果您声明一个常量,例如:

const String more = null;

...然后一行可能太长而无法在源代码中查看,例如...

var s1 = $"one: {99 + 1} two: {99 + 2} three: {99 + 3} four: {99 + 4} five: {99 + 5} six: {99 + 6}";

...可以这样写。

var s2 = $@"{more
    }one: {99 + 1} {more
    }two: {99 + 2} {more
    }three: {99 + 3} {more
    }four: {99 + 4} {more
    }five: {99 + 5} {more
    }six: {99 + 6}";

或者,您可能更喜欢对同一件事使用不同的“奇数”方法:

// elsewhere:
public const String Ξ = null;  // Unicode '\u039E', Greek 'Capital Letter Xi'

// anywhere:
var s3 = $@"{
           Ξ}one: {99 + 1} {
           Ξ}two: {99 + 2} {
           Ξ}three: {99 + 3} {
           Ξ}four: {99 + 4} {
           Ξ}five: {99 + 5} {
           Ξ}six: {99 + 6}";

实际上,看起来我们也可以不用延续符号来做到这一点:

var s4 = $@"one: {99 + 1
            }two: {99 + 2
            }three: {99 + 3
            }four: {99 + 4
            }five: {99 + 5
            }six: {99 + 6}";

这四个示例string在运行时都产生相同的结果,在这种情况下,它们全部在一行上:

one: 100 two: 101 three: 102 four: 103 five: 104 six: 105

正如Stuart所建议的,这两个示例都通过不使用IL来保留IL性能。 +串联字符串来。尽管在我的新示例中,较长的格式字符串确实存储在IL中,并因此存储在可执行文件中,但它所引用的空占位符并未初始化,并且在运行时没有多余的串联或函数调用。为了进行比较,以下是上述两个示例的IL。

第一个例子的IL

ldstr "one: {0} two: {1} three: {2} four: {3} five: {4} six: {5}"
ldc.i4.6
newarr object
dup
ldc.i4.0
ldc.i4.s 100
box int32
stelem.ref
dup
ldc.i4.1
ldc.i4.s 101
box int32
stelem.ref
dup
ldc.i4.2
ldc.i4.s 102
box int32
stelem.ref
dup
ldc.i4.3
ldc.i4.s 103
box int32
stelem.ref
dup
ldc.i4.4
ldc.i4.s 104
box int32
stelem.ref
dup
ldc.i4.5
ldc.i4.s 105
box int32
stelem.ref
call string string::Format(string, object[])

第二个例子

ldstr "{0}one: {1} {2}two: {3} {4}three: {5} {6}four: {7} {8}five: {9} {10}six: {11}"
ldc.i4.s 12
newarr object
dup
ldc.i4.1
ldc.i4.s 100
box int32
stelem.ref
dup
ldc.i4.3
ldc.i4.s 101
box int32
stelem.ref
dup
ldc.i4.5
ldc.i4.s 102
box int32
stelem.ref
dup
ldc.i4.7
ldc.i4.s 103
box int32
stelem.ref
dup
ldc.i4.s 9
ldc.i4.s 104
box int32
stelem.ref
dup
ldc.i4.s 11
ldc.i4.s 105
box int32
stelem.ref
call string string::Format(string, object[])


-2

如果声明了不同的变量,请使用以下简单方法:

Int salary=2000;

String abc="I Love Pakistan";

Double pi=3.14;

Console.Writeline=salary+"/n"+abc+"/n"+pi;
Console.readkey();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.