Multiline C# interpolated string literal - c#

C# 6 brings compiler support for interpolated string literals with syntax:
var person = new { Name = "Bob" };
string s = $"Hello, {person.Name}.";
This is great for short strings, but if you want to produce a longer string must it be specified on a single line?
With other kinds of strings you can:
var multi1 = string.Format(#"Height: {0}
Width: {1}
Background: {2}",
height,
width,
background);
Or:
var multi2 = string.Format(
"Height: {1}{0}" +
"Width: {2}{0}" +
"Background: {3}",
Environment.NewLine,
height,
width,
background);
I can't find a way to achieve this with string interpolation without having it all one one line:
var multi3 = $"Height: {height}{Environment.NewLine}Width: {width}{Environment.NewLine}Background: {background}";
I realise that in this case you could use \r\n in place of Environment.NewLine (less portable), or pull it out to a local, but there will be cases where you can't reduce it below one line without losing semantic strength.
Is it simply the case that string interpolation should not be used for long strings?
Should we just string using StringBuilder for longer strings?
var multi4 = new StringBuilder()
.AppendFormat("Width: {0}", width).AppendLine()
.AppendFormat("Height: {0}", height).AppendLine()
.AppendFormat("Background: {0}", background).AppendLine()
.ToString();
Or is there something more elegant?

You can combine $ and # together to get a multiline interpolated string literal:
string s =
$#"Height: {height}
Width: {width}
Background: {background}";
Source: Long string interpolation lines in C#6 (Thanks to #Ric for finding the thread!)

I'd probably use a combination
var builder = new StringBuilder()
.AppendLine($"Width: {width}")
.AppendLine($"Height: {height}")
.AppendLine($"Background: {background}");

Personally, I just add another interpolated string using string concatenation
For example
var multi = $"Height : {height}{Environment.NewLine}" +
$"Width : {width}{Environment.NewLine}" +
$"Background : {background}";
I find that is easier to format and read.
This will have additional overhead compared to using $#" " but only in the most performance critical applications will this be noticeable. In memory string operations are extremely cheap compared to data I/O. Reading a single variable from the db will take hundreds of times longer in most cases.

Since C# 11 you can do it like this (file multiline.cs):
using System;
public class Program
{
public static void Main()
{
var multiLineStr =
$$"""
{
"Color" : "Blue",
"Thickness" : {{1 + 1}}
}
""";
Console.WriteLine(multiLineStr);
}
}
Now the string variable multiLineStr contains:
{
"Color" : "Blue",
"Thickness" : 2
}
Explanation:
The string is now delimited by """, and interpolation is delimited by {{ and }} because there were two consecutive $ specified (you can add more $ or " if needed, but for the quotes you must use the same number for opening and closing it).
The indentation of the lines where you define the starting and ending quotes matters! If you indented it with different amount of tabs and/or spaces, the compiler might complain.
If required, you can have more than 3 quotes, e.g. """"". Please note that the number of opening and closing quotes must match (in this case, 5 opening and 5 closing double quotes are required to enclose a string).
With this new syntax, the # prefix is not needed because you don't need to escape double quotes inside of the string any more
This simplifies declaring multi-line strings a lot.
You can find a full documentation here # Microsoft.
Note: You can try it out in LinqPad 7 (in Preferences -> Query tab, make sure that "Enable C#/F# preview features" is enabled), in Visual Studio or on the command line. DotNetFiddle does not support the new syntax (yet).
To try it out on the command line, use the batch file CompileCS you can find in the link and invoke it like: compilecs /run multiline.cs (provided you have installed a recent version of Roslyn).

Related

Replace text in file regardless of the end of the line

I've been working on a tool to modify a text file to change graphics settings for a game. A few examples of the settings are as follows:
sg.ShadowQuality=0
ResolutionSizeX=1440
ResolutionSizeY=1080
bUseVSync=False
I want to be able to find sg.ShadowQuality=(rest of line, regardless of what is after this text), and replace it. This is so that a user can set this to say, 10 then 1 without having to check for 10 and 1 etc.
Basically, I'm try to find out what I need to use to find/replace a string in a text file without knowing the end of the string.
My current code looks like:
FileInfo GameUserSettings = new FileInfo(#SD + GUSDirectory);
GameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(SD + GUSDirectory);
text = text.Replace("sg.ShadowQuality=0", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
text = text.Replace("sg.ShadowQuality=1", "sg.ShadowQuality=" + Shadows.Value.ToString());
File.WriteAllText(SD + GUSDirectory, text);
SD + GUSDirectory is the location of the text file.
The file must have readonly Off to be edited, otherwise the game can revert the settings back, hence the need for this.(It is turned back to readonly On after any change, its just not included in this code provided)
You can do it like you do, if you use a regular expression to match all the line
FileInfo gameUserSettings = new FileInfo(Path.Combine(#SD, GUSDirectory)); //name local varaible in camelCase, use Path.Combine to combine paths
gameUserSettings.IsReadOnly = false;
string text = File.ReadAllText(gameUserSettings.FullName); //use the fileinfo you just made rather than make the path again
text = Regex.Replace(text, "^sg[.]ShadowQuality=.*$", $"sg.ShadowQuality={Shadows.Value}", RegexOptions.Multiline); //note switch to interpolated strings
File.WriteAllText(gameUserSettings.FullName, text);
That regex is a Multiline one (so ^ and $ have altered meanings):
^sg[.]ShadowQuality=.*$
start of line ^ (not start of input)
followed by sg
followed by period . (in a character class it loses its "any character" meaning)
followed by ShadowQuality=
followed by any number of any character(.*)
followed by end of line $ (not end of input)
The vital bit is "any number of any character" that can cope with the vlaue in the file being 1, 2, 7, hello and so on..
The replacement is:
$"sg.ShadowQuality={Shadows.Value}"
This is an interpolated string; a neater way of representing strings that mix constant content (hardcoded chars) and variable content. When a $tring contains a { that "breaks out" of the string and back into normal c# code so you can write code that resolves to values that will be included in the string -> if Shadows.Value is for example a decimal? of 1.23 it will become 1.23
You can format data too; calling for $"to one dp is {Shadows.Value:F1}" would produce "to one dp is 1.2" - the 1.23 is formatted to 1 decimal place by the F1, just like calling Shadows.Value.ToString("F1") would

How to write Regex pattern to extract output from SOX info in C#?

I have the following string (output from sox --info command):
Input File : 'C:\Users\source\repos\dotnetcore\audio\1000.wav'
Channels : 1
Sample Rate : 44100
Precision : 16-bit
Duration : 00:05:11.64 = 13743363 samples = 23373.1 CDDA sectors
File Size : 27.5M
Bit Rate : 706k
Sample Encoding: 16-bit Signed Integer PCM
I need to extract the file path (without the single quote), channels, sample rate etc.
I have a method where I pass in the whole string (the output) and the property I want to extract. Like this:
private static string Extract(string inputStr, string property)
{
string pattern = string.Format(#"\s+{0}\s+: '?(.*)\r\n", property);
Match result = Regex.Match(inputStr, pattern);
if (result.Success)
{
return result.Groups[1].Value;
}
return string.Empty;
}
This almost returns what I need, except for the last single quote in the Input File. How do I not include that in the pattern
Extract(output, "Input File") //returns C:\Users\source\repos\dotnetcore\audio\1000.wav' --> How to remove the last single quote
Extract(output, "Channels") //returns 1 --> Good
Extract(output, "Sample Rate") // returns 44100 --> Good
I have tried these patterns also
\s+Input File\s+: '?(.*)'? //Still returns with the last single quote
\s+Input File\s+: '?(.*)'+ //This works for Input File but doesn't work for other properties
Edit: Based on the original author’s comment and my not inspecting all the lines closely enough
\s+{0}\s*: '?([^\r\n']*)'?
Original:
\s+{0}\s+: '?([^\r\n']*)'?
This is because * is greedy--it will keep pulling as many characters as it can. Because the ? allows 0 or 1 characters, it doesn't stop the * from continuing to pull in characters.
Try the following expression: \s*(?<name>[^:]+?)\s*:\s*(?<value>('[^']+')|.+)
See demo: https://regex101.com/r/w7b2oO/1
A couple of differences:
(?<name>...) gives the capture group a name so that you can reference it by name instead of index
('[^']+')|.+) makes captures values enclosed in a string ('[^']+') or (|) without (.+)

String concatenation using String interpolation

I've something like below.
var amount = "$1,000.99";
var formattedamount = string.Format("{0}{1}{0}", "\"", amount);
How can I achieve same using String interpolation?
I tried like below
var formattedamount1 = $"\"{amount}\"";
Is there any better way of doing this using string interpolation?
Update
Is there any better way of doing this using string interpolation
No, this is just string interpolation, you cant make the following any shorter and more readable really
var formattedamount1 = $"\"{amount}\"";
Original answer
$ - string interpolation (C# Reference)
To include a brace, "{" or "}", in the text produced by an
interpolated string, use two braces, "{{" or "}}". For more
information, see Escaping Braces.
Quotes are just escaped as normal
Example
string name = "Horace";
int age = 34;
Console.WriteLine($"He asked, \"Is your name {name}?\", but didn't wait for a reply :-{{");
Console.WriteLine($"{name} is {age} year{(age == 1 ? "" : "s")} old.");
Output
He asked, "Is your name Horace?", but didn't wait for a reply :-{
Horace is 34 years old.
Same thing you can achieve by doing:
var formattedamount1 = $"\"{amount}\"";
OR
var formattedamount1 = $#"""{amount}""";
It's basically allowing you to write string.Format(), but instead of using one string with "placeholders"({0}, {1}, .. {N}), you are directly writing/using your variable inside string.
Please read more about String Interpolation (DotNetPerls), $ - string interpolation to fully understand whats going on.
Just to give one more option, if you want to make sure you use the same quote at both the start and the end, you could use a separate variable for that:
string quote = "\"";
string amount = "$1,000.99";
string formattedAmount = $"{quote}{amount}{quote}";
I'm not sure I'd bother with that personally, but it's another option to consider.

C# variables inside of quotes

With string interpolation, how do you handle variables piped into a command that contain spaces in them? For example, if you have a variable that has spaces in it (like a UNC path), how do you handle that?
This code works when no spaces are present in the "filePath" variable (i.e.; \ServerName\testfile.txt):
Ex: System.Diagnostics.Process.Start("net.exe", $"use X: \\{filePath} {pwd /USER:{usr}").WaitForExit();
As soon as you encounter a path that has spaces in it, however, the command above no longer works, because it's unable to find the path. Normally, I would apply quotes around a path containing spaces, to counter this (in other languages like PowerShell). How do you do something similar with C# interpolation.
C# 6.0+:
System.Diagnostics.Process.Start("net.exe", #$"use X: \\Servername\share {pwd} /USER:{usr}").WaitForExit();
C# < 6.0:
System.Diagnostics.Process.Start("net.exe", #"use X: \\Servername\share " + pwd + " /USER: " + usr).WaitForExit();
use the $
void Main()
{
string pwd = "test";
var myVar = $"This is a {pwd}";
var folder = "MyFolder";
var myVarWithPaths = $"C:\\{folder}";
Console.WriteLine(myVar);
Console.WriteLine(myVarWithPaths);
}
Output
This is a test
C:\MyFolder
C# 6.0 introduced string interpolation, which is used by prefixing a quoted string of text with the $ character.
e.g.
var i = 0;
var s = $"i = {i}";
// output: i = 0
You can also embed multiple interpolated strings, as well as conditions.
var i = 0;
var s = $"i is: {(i == 1 ? $"i is {1}" : "i is not 1")}";
This can be combined with string literals that are prefixed with #.
var i = 1;
var s = #$"c:\{i}\test";
Basically, you can write almost any normal expression statement in a interpolated string, such as calling methods:
var s = $"i is {GetValueOfI() - 100}";
For types that are not a System.String, the implementation of that types ToString() method will be used for the resulting value.
See: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated

How to remove the exe part of the command line

This is my code. The input command line is var1 val1 var2 val2:
var rawCmd = Environment.CommandLine;
// Environment.CommandLine adds the .exe info that I don't want in my command line:
// rawCmd = "path\to\ProjectName.vshost.exe" var1 val1 var2 val2
// A. This correction makes it work, although it is pretty ugly:
var cleanCmd = rawCmd.Split(new string[] { ".exe\" " }, StringSplitOptions.None)[1];
// B. This alternative should be cleaner, but I can't make it work:
var exePath = System.Reflection.Assembly.GetCallingAssembly().Location;
cleanCmd = rawCmd.Replace(string.Format($"\"{exePath}\" "), "");
So to make B work, I should be able to find the .vhost.exe info (which I am not able to find).
But also I would like to know if there is a cleaner way to do all this.
As for the reason why I want to achieve this, here is the explanation (tl;dr: parsing a json from the command line): https://stackoverflow.com/a/36203572/831138
Instead of using
var rawCmd = Environment.CommandLine;
You can use:
var rawCmd = Environment.CommandLine;
var argsOnly = rawCmd.Replace("\"" + Environment.GetCommandLineArgs()[0] + "\"", "");
This will return "var1 val1 var2 val2" in your example. And it should work with the JSON example in the other post.
This only strips the command invocation part, no matter you write it as program, program.exe, .\program, c:program, "c:\Program Files\program", "\Program Files\program.exe", etc. or your path separator.
var exe = Environment.GetCommandLineArgs()[0]; // Command invocation part
var rawCmd = Environment.CommandLine; // Complete command
var argsOnly = rawCmd.Remove(rawCmd.IndexOf(exe),exe.Length).TrimStart('"').Substring(1);
It will leave double quotes, carets, and spaces between arguments untouched, even spaces at the beginning i.e., just after program name. Note there's an extra space at the beginning, don't ask me why. If that bothers you, remove first character, it should be easy. Hence the final .Substring(1). I define the two variables to make it more readable.
Edit:
Takes account of quoted invocation and the case where the program name string happens to appear as part of an argument (e.g., if your program is me.exe and you run me "Call me Ishmael", .Replace would trim the second me too). Now I also take out that extra space.
This is a very old question, but as of Windows 10 20H2 and .NET Framework 4.8, all of the above solutions appear to be broken in one way or another (eg. double-quote delimited exe paths).
I needed to remove the exe from Environment.CommandLine in a more generally robust way, so I decided to try a regex based approach. (Then I had 2 problems, lol.) Hope this helps somebody!
internal static string GetRawCommandLineArgs( )
{
// Separate the args from the exe path.. incl handling of dquote-delimited full/relative paths.
Regex fullCommandLinePattern = new Regex(#"
^ #anchor match to start of string
(?<exe> #capture the executable name; can be dquote-delimited or not
(\x22[^\x22]+\x22) #case: dquote-delimited
| #or
([^\s]+) #case: no dquotes
)
\s* #chomp zero or more whitespace chars, after <exe>
(?<args>.*) #capture the remainder of the command line
$ #match all the way to end of string
",
RegexOptions.IgnorePatternWhitespace|
RegexOptions.ExplicitCapture|
RegexOptions.CultureInvariant
);
Match m = fullCommandLinePattern.Match(Environment.CommandLine);
if (!m.Success) throw new ApplicationException("Failed to extract command line.");
// Note: will return empty-string if no args after exe name.
string commandLineArgs = m.Groups["args"].Value;
return commandLineArgs;
}
Testing done:
exe paths with/without doublequote
args containing doublequotes and also referencing the exe name
invoking exe with no args returns empty string
[Edit] Testing NOT done:
.NET 5 or any other runtime or OS
I have found a way to get the vhost path:
var exePath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, System.AppDomain.CurrentDomain.FriendlyName);
...although I am afraid it could lead to inconsistencies depending on where the code is executed (Debug / production ...). I hope not, I'll have to keep testing.
If the number of spaces between elements is not important, you can just Join the argument array, which is split on unquoted space characters, and doesn't contain the executable:
void Main(string[] args)
{
var cleanCmd = string.Join(" ", args);
// etc
}
Actually, the command invocation part is always enclosed in double quotes, even if the path doesn't contain any spaces. And it is always followed by a space character, even if no command line params are specified. So this should be enough in any case:
string args = Environment.CommandLine
.Substring(Environment.GetCommandLineArgs()[0].Length + 3);
But if you're like me and want to make it 100% waterproof, you can use:
string args = Environment.CommandLine
.TrimStart('"')
.Substring(Environment.GetCommandLineArgs()[0].Length)
.TrimStart('"')
.TrimStart(' ');

Categories

Resources