The .NET Framework gives us the Format method:
string s = string.Format("This {0} very {1}.", "is", "funny");
// s is now: "This is very funny."
I would like an "Unformat" function, something like:
object[] params = string.Unformat("This {0} very {1}.", "This is very funny.");
// params is now: ["is", "funny"]
I know something similar exists in the ANSI-C library (printf vs scanf).
The question: is there something similiar in C#?
Update: Capturing groups with regular expressions are not the solution I need. They are also one way. I'm looking for a system that can work both ways in a single format. It's OK to give up some functionality (like types and formatting info).
There's no such method, probably because of problems resolving ambiguities:
string.Unformat("This {0} very {1}.", "This is very very funny.")
// are the parameters equal to "is" and "very funny", or "is very" and "funny"?
Regular expression capturing groups are made for this problem; you may want to look into them.
Regex with grouping?
/This (.*?) very (.*?)./
If anyone's interested, I've just posted a scanf() replacement for .NET. If regular expressions don't quite cut it for you, my code follows the scanf() format string quite closely.
You can see and download the code I wrote at http://www.blackbeltcoder.com/Articles/strings/a-sscanf-replacement-for-net.
You could do string[] parts = string.Split(' '), and then extract by the index position parts[1] and parts [3] in your example.
Yep. These are called "regular expressions". The one that will do the thing is
This (?<M0>.+) very (?<M1>.+)\.
#mquander: Actualy, PHP solves it even different:
$s = "This is very very funny.";
$fmt = "This %s very %s.";
sscanf($s, $fmt, $one, $two);
echo "<div>one: [$one], two: [$two]</div>\n";
//echo's: "one: [is], two: [very]"
But maybe your regular expression remark can help me. I just need to rewrite "This {0} very {1}." to something like: new Regex(#"^This (.*) very (.*)\.$"). This should be done programmatical, so I can use one format string on the public class interface.
BTW: I've already have a parser to find the parameters: see the Named Format Redux blog entry by Phil Haack (and yes, I also want named paramters to work both ways).
I came across the same problem, i belive that there is a elegante solution using REGEX... but a came up with function in C# to "UnFormat" that works quite well. Sorry about the lack of comments.
/// <summary>
/// Unformats a string using the original formating string.
///
/// Tested Situations:
/// UnFormat("<nobr alt=\"1\">1<nobr>", "<nobr alt=\"{0}\">{0}<nobr>") : "1"
/// UnFormat("<b>2</b>", "<b>{0}</b>") : "2"
/// UnFormat("3<br/>", "{0}<br/>") : "3"
/// UnFormat("<br/>4", "<br/>{0}") : "4"
/// UnFormat("5", "") : "5"
/// UnFormat("<nobr>6<nobr>", "<nobr>{0}<nobr>") : "6"
/// UnFormat("<nobr>2009-10-02<nobr>", "<nobr>{0:yyyy-MM-dd}<nobr>") : "2009-10-02"
/// UnFormat("<nobr><nobr>", "<nobr>{0}<nobr>") : ""
/// UnFormat("bla", "<nobr>{0}<nobr>") : "bla"
/// </summary>
/// <param name="original"></param>
/// <param name="formatString"></param>
/// <returns>If an "unformat" is not possible the original string is returned.</returns>
private Dictionary<int,string> UnFormat(string original, string formatString)
{
Dictionary<int, string> returnList = new Dictionary<int, string>();
try{
int index = -1;
// Decomposes Format String
List<string> formatDecomposed = new List<string> (formatString.Split('{'));
for(int i = formatDecomposed.Count - 1; i >= 0; i--)
{
index = formatDecomposed[i].IndexOf('}') + 1;
if (index > 0 && (formatDecomposed[i].Length - index) > 0)
{
formatDecomposed.Insert(i + 1, formatDecomposed[i].Substring(index, formatDecomposed[i].Length - index));
formatDecomposed[i] = formatDecomposed[i].Substring(0, index);
}
else
//Finished
break;
}
// Finds and indexes format parameters
index = 0;
for (int i = 0; i < formatDecomposed.Count; i++)
{
if (formatDecomposed[i].IndexOf('}') < 0)
{
index += formatDecomposed[i].Length;
}
else
{
// Parameter Index
int parameterIndex;
if (formatDecomposed[i].IndexOf(':')< 0)
parameterIndex = Convert.ToInt16(formatDecomposed[i].Substring(0, formatDecomposed[i].IndexOf('}')));
else
parameterIndex = Convert.ToInt16(formatDecomposed[i].Substring(0, formatDecomposed[i].IndexOf(':')));
// Parameter Value
if (returnList.ContainsKey(parameterIndex) == false)
{
string parameterValue;
if (formatDecomposed.Count > i + 1)
if (original.Length > index)
parameterValue = original.Substring(index, original.IndexOf(formatDecomposed[i + 1], index) - index);
else
// Original String not valid
break;
else
parameterValue = original.Substring(index, original.Length - index);
returnList.Add(parameterIndex, parameterValue);
index += parameterValue.Length;
}
else
index += returnList[parameterIndex].Length;
}
}
// Fail Safe #1
if (returnList.Count == 0) returnList.Add(0, original);
}
catch
{
// Fail Safe #2
returnList = new Dictionary<int, string>();
returnList.Add(0, original);
}
return returnList;
}
I reference earlier reply, wrote a sample see following
string sampleinput = "FirstWord.22222";
Match match = Regex.Match(sampleinput, #"(\w+)\.(\d+)$", RegexOptions.IgnoreCase);
if(match.Success){
string totalmatchstring = match.Groups[0]; // FirstWord.22222
string firstpart = match.Groups[1]; // FirstWord`
string secondpart = match.Groups[2]; // 22222
}
my input is abc#xyz.com
I want to replace xyz.com with mnop.com
So the final result will be abc#mnop.com
The present way
var input = "abc#xyz.com";
var output = input.Split('#')[0] + "#mnop.com";
What is the appropriate way?Any regular expression?
Replace function sample (with correct exception handle)
/// <summary>
/// replace email domain
/// </summary>
/// <param name="email"> email </param>
/// <param name="newDomain"> new domain </param>
/// <returns></returns>
private string ReplaceMailDomain(string email, string newDomain)
{
if (email == null) throw new ArgumentNullException("email");
int pos = email.IndexOf('#');
if (pos < 0)
{
throw new ArgumentException("Invalid email", "email");
}
else
{
return email.Substring(0, pos + 1) + newDomain;
}
}
Usage:
string email = ReplaceMailDomain("abc#xyz.com", "mnop.com");
If you know before hand the string you are looking for, you can simply use the normal Replace(string toFind, string replacement). You can use indexof and substring to get the domain name.
Given the below:
string input = "abc#xyz.com";
string domain = input.Substring(input.IndexOf('#') + 1);
Console.WriteLine(input.Replace(domain, "mnop.com"));
It yields
abc#mnop.com
If you just want to "take something before # sign and append some domain name", then one of possible approaches is
var input = "abc#xyz.com";
var output = input.Substring(0, input.IndexOf('#')) + "#mnop.com";
It is slightly "lightweight" than using Split because it doesn't creates array (and in fact you doesn't need array since you're using only its first element).
Assuming the address will be valid, meaning there is only one # character, you can use the simple Replace() function
var domain = "xyz.com";
var newdomain = "xyz.com";
var input = "abc#xyz.com";
var output = input.Replace("#" + domain, "#" + newdomain);
Regexps would be overkill for this.
If you want to remove any domain name and replace it, then use Substring() and IndexOf() for that
var output = input.Substring(0, input.IndexOf('#') + 1) + newdomain;
Note that this will throw an exception if the string doesn't contain the # character.
This is my sample filename Text_2.23.txt
I already separated the extension but I'm not able to get the version out, so I could use it later in code for comparison. I need to get 2.23 out,
edit: Filenames can be in various forms, but the version is allways at the end of the filename.
string s = f1.Name.ToString(); //for the Filename
int k = s.LastIndexOf('.');
string lhs = k < 0 ? s : s.Substring(0, k),
rhs = k < 0 ? "" : s.Substring(k + 1);
char[] array = lhs.ToCharArray();
Array.Reverse(array); // reverse search from right to left
for (int j = 0; j < array.Length; j++)
{
char letter = array[j]; //the letter I get out in the sample case is "3"
}
Use the Path and Version classes:
string fileName = "Text_2.23.txt";
string fn = Path.GetFileNameWithoutExtension(fileName);
string[] parts = fn.Split('_');
Version v;
if (parts.Length > 1 && Version.TryParse(parts.Last(), out v))
{
Console.Write("Major:{0}, Minor:{1}", v.Major, v.Minor);
}
Demo
Edit: since you have commented that your file-names could be almost arbitrary, but the version is always at the end and you just want that string.
With your commented samples:
string[] fileNames = new[] { "teext_023.ext", "txtxt.x9.08.ext", "text0911.ext" };
string[] versions = fileNames
.Select(fileName =>
Path.GetFileNameWithoutExtension(fileName).Split('_')
.Last()
.Reverse()
.TakeWhile(c => c == '.' || Char.IsDigit(c))
.Reverse()
).Select(chars => new string(chars.ToArray())).ToArray();
Demo
This is what I was looking for, a simple way for solving my problem:
Array.Reverse(array);
Boolean dot = false;
for (int j = 0; j < array.Length; j++)
{
char letter = array[j];
if (char.IsNumber(letter))
{
version += letter.ToString();
}
else if (letter == '.')
{
if (dot)
{
break;
}
dot = true;
}
}
version = this.Reverse(version);
if (version.Equals(""))
{
version = "0";
}
Here are a few generic solutions using regular expressions (Regex) for extracting a version number from a string.
Explanation of the regular expression (\d+\.\d+\.\d+\.\d+):
( begins the 1st capture group
\d matches a digit (equivalent to [0-9])
+ matches the previous token between one and unlimited times, as many times as possible, giving back as needed (greedy)
\. matches the character . literally (case sensitive)
) closes the 1st capture group
So in the case of version numbers, which typically use the format X.X.X.X, it is just a matter of repeating the Regex pattern as many times as you need for the number of version number components you are interested in capturing.
The simplest yet least flexible solution is to look for the exact pattern you need. This method looks for a standard 4-component version number. To only look for a 2-part version number you would change the Regex to (\d+\.\d+)
/// <summary>
/// Extracts a version from the given input.
/// </summary>
/// <param name="input">
/// String that may contain a valid 4-component version in the format X.X.X.X.
/// <para>
/// e.g. 1.0.0.0 or 0.0.17.95
/// </para>
/// </param>
/// <returns>Version or null.</returns>
public string ExtractVersionNumber(string input)
{
var regex = new Regex(#"(\d+\.\d+\.\d+\.\d+)");
var match = regex.Match(input);
return match.Success ? match.Value : null;
}
A more flexible solution is to specify whether you need the build and revision number components of a version number.
/// <summary>
/// Extracts a version number from the given input.
/// </summary>
/// <param name="input">
/// String that may contain a valid version.
/// <para>
/// e.g. 1.0.0.0 or 0.0.17.95
/// </para>
/// </param>
/// <param name="requireBuild">
/// Indicates whether the build component of the version is required.
/// <para>
/// e.g. 1.0.0 or 0.0.17
/// </para>
/// </param>
/// <param name="requireRevision">
/// Indicates whether the revision component of the version is required.
/// <para>
/// e.g. 1.0.0.0 or 0.0.17.95
/// </para>
/// </param>
/// <returns>Version or null.</returns>
public string ExtractVersionNumber(string input, bool requireBuild = true, bool requireRevision = true)
{
var pattern = #"\d+\.\d+";
if (requireBuild)
{
pattern += #"\.\d+";
}
if (requireRevision)
{
pattern += #"\.\d+";
}
var regex = new Regex($"({pattern})");
var match = regex.Match(input);
return match.Success ? match.Value : null;
}
An alternative flexible approach is to specify the number of version number components required.
/// <summary>
/// Extracts a version from the given input.
/// </summary>
/// <param name="input">
/// String that may contain a valid version number.
/// </param>
/// <param name="componentsRequired">
/// Number of version components required. Defaults to 4.
/// <para>Valid values: 4, 3, 2.</para>
/// <para>4 components example: 1.0.0.0 or 0.1.17.95</para>
/// <para>3 components example: 1.0.0 or 0.1.17</para>
/// <para>2 components example: 1.0 or 0.1</para>
/// </param>
/// <returns>Version or null.</returns>
public string ExtractVersionNumber(string input, int componentsRequired = 4)
{
string pattern;
switch (componentsRequired)
{
case 4:
pattern = #"(\d+\.\d+\.\d+\.\d+)";
break;
case 3:
pattern = #"(\d+\.\d+\.\d+)";
break;
case 2:
pattern = #"(\d+\.\d+)";
break;
default:
throw new ArgumentOutOfRangeException(nameof(componentsRequired), "Valid values: 4, 3, 2");
}
var regex = new Regex(pattern);
var match = regex.Match(input);
return match.Success ? match.Value : null;
}
Is the following behaviour some feature or a bug in C# .NET?
Test application:
using System;
using System.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Arguments:");
foreach (string arg in args)
{
Console.WriteLine(arg);
}
Console.WriteLine();
Console.WriteLine("Command Line:");
var clArgs = Environment.CommandLine.Split(' ');
foreach (string arg in clArgs.Skip(clArgs.Length - args.Length))
{
Console.WriteLine(arg);
}
Console.ReadKey();
}
}
}
Run it with command line arguments:
a "b" "\\x\\" "\x\"
In the result I receive:
Arguments:
a
b
\\x\
\x"
Command Line:
a
"b"
"\\x\\"
"\x\"
There are missing backslashes and non-removed quote in args passed to method Main(). What is the correct workaround except manually parsing Environment.CommandLine?
According to this article by Jon Galloway, there can be weird behaviour experienced when using backslashes in command line arguments.
Most notably it mentions that "Most applications (including .NET applications) use CommandLineToArgvW to decode their command lines. It uses crazy escaping rules which explain the behaviour you're seeing."
It explains that the first set of backslashes do not require escaping, but backslashes coming after alpha (maybe numeric too?) characters require escaping and that quotes always need to be escaped.
Based off of these rules, I believe to get the arguments you want you would have to pass them as:
a "b" "\\x\\\\" "\x\\"
"Whacky" indeed.
The full story of the crazy escaping rules was told in 2011 by an MS blog entry: "Everyone quotes command line arguments the wrong way"
Raymond also had something to say on the matter (already back in 2010): "What's up with the strange treatment of quotation marks and backslashes by CommandLineToArgvW"
The situation persists into 2020 and the escaping rules described in Everyone quotes command line arguments the wrong way are still correct as of 2020 and Windows 10.
I came across this same issue the other day and had a tough time getting through it. In my googling, I came across this article regarding VB.NET (the language of my application) that solved the problem without having to change any of my other code based on the arguments.
In that article, he refers to the original article which was written for C#. Here's the actual code, you pass it Environment.CommandLine():
C#
class CommandLineTools
{
/// <summary>
/// C-like argument parser
/// </summary>
/// <param name="commandLine">Command line string with arguments. Use Environment.CommandLine</param>
/// <returns>The args[] array (argv)</returns>
public static string[] CreateArgs(string commandLine)
{
StringBuilder argsBuilder = new StringBuilder(commandLine);
bool inQuote = false;
// Convert the spaces to a newline sign so we can split at newline later on
// Only convert spaces which are outside the boundries of quoted text
for (int i = 0; i < argsBuilder.Length; i++)
{
if (argsBuilder[i].Equals('"'))
{
inQuote = !inQuote;
}
if (argsBuilder[i].Equals(' ') && !inQuote)
{
argsBuilder[i] = '\n';
}
}
// Split to args array
string[] args = argsBuilder.ToString().Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
// Clean the '"' signs from the args as needed.
for (int i = 0; i < args.Length; i++)
{
args[i] = ClearQuotes(args[i]);
}
return args;
}
/// <summary>
/// Cleans quotes from the arguments.<br/>
/// All signle quotes (") will be removed.<br/>
/// Every pair of quotes ("") will transform to a single quote.<br/>
/// </summary>
/// <param name="stringWithQuotes">A string with quotes.</param>
/// <returns>The same string if its without quotes, or a clean string if its with quotes.</returns>
private static string ClearQuotes(string stringWithQuotes)
{
int quoteIndex;
if ((quoteIndex = stringWithQuotes.IndexOf('"')) == -1)
{
// String is without quotes..
return stringWithQuotes;
}
// Linear sb scan is faster than string assignemnt if quote count is 2 or more (=always)
StringBuilder sb = new StringBuilder(stringWithQuotes);
for (int i = quoteIndex; i < sb.Length; i++)
{
if (sb[i].Equals('"'))
{
// If we are not at the last index and the next one is '"', we need to jump one to preserve one
if (i != sb.Length - 1 && sb[i + 1].Equals('"'))
{
i++;
}
// We remove and then set index one backwards.
// This is because the remove itself is going to shift everything left by 1.
sb.Remove(i--, 1);
}
}
return sb.ToString();
}
}
VB.NET:
Imports System.Text
' Original version by Jonathan Levison (C#)'
' http://sleepingbits.com/2010/01/command-line-arguments-with-double-quotes-in-net/
' converted using http://www.developerfusion.com/tools/convert/csharp-to-vb/
' and then some manual effort to fix language discrepancies
Friend Class CommandLineHelper
''' <summary>
''' C-like argument parser
''' </summary>
''' <param name="commandLine">Command line string with arguments. Use Environment.CommandLine</param>
''' <returns>The args[] array (argv)</returns>
Public Shared Function CreateArgs(commandLine As String) As String()
Dim argsBuilder As New StringBuilder(commandLine)
Dim inQuote As Boolean = False
' Convert the spaces to a newline sign so we can split at newline later on
' Only convert spaces which are outside the boundries of quoted text
For i As Integer = 0 To argsBuilder.Length - 1
If argsBuilder(i).Equals(""""c) Then
inQuote = Not inQuote
End If
If argsBuilder(i).Equals(" "c) AndAlso Not inQuote Then
argsBuilder(i) = ControlChars.Lf
End If
Next
' Split to args array
Dim args As String() = argsBuilder.ToString().Split(New Char() {ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
' Clean the '"' signs from the args as needed.
For i As Integer = 0 To args.Length - 1
args(i) = ClearQuotes(args(i))
Next
Return args
End Function
''' <summary>
''' Cleans quotes from the arguments.<br/>
''' All signle quotes (") will be removed.<br/>
''' Every pair of quotes ("") will transform to a single quote.<br/>
''' </summary>
''' <param name="stringWithQuotes">A string with quotes.</param>
''' <returns>The same string if its without quotes, or a clean string if its with quotes.</returns>
Private Shared Function ClearQuotes(stringWithQuotes As String) As String
Dim quoteIndex As Integer = stringWithQuotes.IndexOf(""""c)
If quoteIndex = -1 Then Return stringWithQuotes
' Linear sb scan is faster than string assignemnt if quote count is 2 or more (=always)
Dim sb As New StringBuilder(stringWithQuotes)
Dim i As Integer = quoteIndex
Do While i < sb.Length
If sb(i).Equals(""""c) Then
' If we are not at the last index and the next one is '"', we need to jump one to preserve one
If i <> sb.Length - 1 AndAlso sb(i + 1).Equals(""""c) Then
i += 1
End If
' We remove and then set index one backwards.
' This is because the remove itself is going to shift everything left by 1.
sb.Remove(System.Math.Max(System.Threading.Interlocked.Decrement(i), i + 1), 1)
End If
i += 1
Loop
Return sb.ToString()
End Function
End Class
I have escaped the problem the other way...
Instead of getting arguments already parsed I am getting the arguments string as it is and then I am using my own parser:
static void Main(string[] args)
{
var param = ParseString(Environment.CommandLine);
...
}
// The following template implements the following notation:
// -key1 = some value -key2 = "some value even with '-' character " ...
private const string ParameterQuery = "\\-(?<key>\\w+)\\s*=\\s*(\"(?<value>[^\"]*)\"|(?<value>[^\\-]*))\\s*";
private static Dictionary<string, string> ParseString(string value)
{
var regex = new Regex(ParameterQuery);
return regex.Matches(value).Cast<Match>().ToDictionary(m => m.Groups["key"].Value, m => m.Groups["value"].Value);
}
This concept lets you type quotes without the escape prefix.
After much experimentation this worked for me. I'm trying to create a command to send to the Windows command line. A folder name comes after the -graphical option in the command, and since it may have spaces in it, it has to be wrapped in double quotes. When I used back slashes to create the quotes they came out as literals in the command. So this. . . .
string q = #"" + (char) 34;
string strCmdText = string.Format(#"/C cleartool update -graphical {1}{0}{1}", this.txtViewFolder.Text, q);
System.Diagnostics.Process.Start("CMD.exe", strCmdText);
q is a string holding just a double quote character. It's preceded with # to make it a verbatim string literal.
The command template is also a verbatim string literal, and the string.Format method is used to compile everything into strCmdText.
This works for me, and it works correctly with the example in the question.
/// <summary>
/// https://www.pinvoke.net/default.aspx/shell32/CommandLineToArgvW.html
/// </summary>
/// <param name="unsplitArgumentLine"></param>
/// <returns></returns>
static string[] SplitArgs(string unsplitArgumentLine)
{
int numberOfArgs;
IntPtr ptrToSplitArgs;
string[] splitArgs;
ptrToSplitArgs = CommandLineToArgvW(unsplitArgumentLine, out numberOfArgs);
// CommandLineToArgvW returns NULL upon failure.
if (ptrToSplitArgs == IntPtr.Zero)
throw new ArgumentException("Unable to split argument.", new Win32Exception());
// Make sure the memory ptrToSplitArgs to is freed, even upon failure.
try
{
splitArgs = new string[numberOfArgs];
// ptrToSplitArgs is an array of pointers to null terminated Unicode strings.
// Copy each of these strings into our split argument array.
for (int i = 0; i < numberOfArgs; i++)
splitArgs[i] = Marshal.PtrToStringUni(
Marshal.ReadIntPtr(ptrToSplitArgs, i * IntPtr.Size));
return splitArgs;
}
finally
{
// Free memory obtained by CommandLineToArgW.
LocalFree(ptrToSplitArgs);
}
}
[DllImport("shell32.dll", SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine,
out int pNumArgs);
[DllImport("kernel32.dll")]
static extern IntPtr LocalFree(IntPtr hMem);
static string Reverse(string s)
{
char[] charArray = s.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
static string GetEscapedCommandLine()
{
StringBuilder sb = new StringBuilder();
bool gotQuote = false;
foreach (var c in Environment.CommandLine.Reverse())
{
if (c == '"')
gotQuote = true;
else if (gotQuote && c == '\\')
{
// double it
sb.Append('\\');
}
else
gotQuote = false;
sb.Append(c);
}
return Reverse(sb.ToString());
}
static void Main(string[] args)
{
// Crazy hack
args = SplitArgs(GetEscapedCommandLine()).Skip(1).ToArray();
}
Is there any way to format a string by name rather than position in C#?
In python, I can do something like this example (shamelessly stolen from here):
>>> print '%(language)s has %(#)03d quote types.' % \
{'language': "Python", "#": 2}
Python has 002 quote types.
Is there any way to do this in C#? Say for instance:
String.Format("{some_variable}: {some_other_variable}", ...);
Being able to do this using a variable name would be nice, but a dictionary is acceptable too.
There is no built-in method for handling this.
Here's one method
string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);
Here's another
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);
A third improved method partially based on the two above, from Phil Haack
Update: This is now built-in as of C# 6 (released in 2015).
String Interpolation
$"{some_variable}: {some_other_variable}"
I have an implementation I just posted to my blog here: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx
It addresses some issues that these other implementations have with brace escaping. The post has details. It does the DataBinder.Eval thing too, but is still very fast.
Interpolated strings were added into C# 6.0 and Visual Basic 14
Both were introduced through new Roslyn compiler in Visual Studio 2015.
C# 6.0:
return "\{someVariable} and also \{someOtherVariable}" OR
return $"{someVariable} and also {someOtherVariable}"
source: what's new in C#6.0
VB 14:
return $"{someVariable} and also {someOtherVariable}"
source: what's new in VB 14
Noteworthy features (in Visual Studio 2015 IDE):
syntax coloring is supported - variables contained in strings are highlighted
refactoring is supported - when renaming, variables contained in strings get renamed, too
actually not only variable names, but expressions are supported - e.g. not only {index} works, but also {(index + 1).ToString().Trim()}
Enjoy! (& click "Send a Smile" in the VS)
You can also use anonymous types like this:
public string Format(string input, object p)
{
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(p))
input = input.Replace("{" + prop.Name + "}", (prop.GetValue(p) ?? "(null)").ToString());
return input;
}
Of course it would require more code if you also want to parse formatting, but you can format a string using this function like:
Format("test {first} and {another}", new { first = "something", another = "something else" })
There doesn't appear to be a way to do this out of the box. Though, it looks feasible to implement your own IFormatProvider that links to an IDictionary for values.
var Stuff = new Dictionary<string, object> {
{ "language", "Python" },
{ "#", 2 }
};
var Formatter = new DictionaryFormatProvider();
// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);
Outputs:
Python has 2 quote types
The caveat is that you can't mix FormatProviders, so the fancy text formatting can't be used at the same time.
The framework itself does not provide a way to do this, but you can take a look at this post by Scott Hanselman. Example usage:
Person p = new Person();
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);
This code by James Newton-King is similar and works with sub-properties and indexes,
string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));
James's code relies on System.Web.UI.DataBinder to parse the string and requires referencing System.Web, which some people don't like to do in non-web applications.
EDIT: Oh and they work nicely with anonymous types, if you don't have an object with properties ready for it:
string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
See https://stackoverflow.com/questions/271398?page=2#358259
With the linked-to extension you can write this:
var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());
and you'll get "foo 2 System.Object".
I think the closest you'll get is an indexed format:
String.Format("{0} has {1} quote types.", "C#", "1");
There's also String.Replace(), if you're willing to do it in multiple steps and take it on faith that you won't find your 'variables' anywhere else in the string:
string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");
Expanding this to use a List:
List<KeyValuePair<string, string>> replacements = GetFormatDictionary();
foreach (KeyValuePair<string, string> item in replacements)
{
MyString = MyString.Replace(item.Key, item.Value);
}
You could do that with a Dictionary<string, string> too by iterating it's .Keys collections, but by using a List<KeyValuePair<string, string>> we can take advantage of the List's .ForEach() method and condense it back to a one-liner:
replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});
A lambda would be even simpler, but I'm still on .Net 2.0. Also note that the .Replace() performance isn't stellar when used iteratively, since strings in .Net are immutable. Also, this requires the MyString variable be defined in such a way that it's accessible to the delegate, so it's not perfect yet.
My open source library, Regextra, supports named formatting (amongst other things). It currently targets .NET 4.0+ and is available on NuGet. I also have an introductory blog post about it: Regextra: helping you reduce your (problems){2}.
The named formatting bit supports:
Basic formatting
Nested properties formatting
Dictionary formatting
Escaping of delimiters
Standard/Custom/IFormatProvider string formatting
Example:
var order = new
{
Description = "Widget",
OrderDate = DateTime.Now,
Details = new
{
UnitPrice = 1500
}
};
string template = "We just shipped your order of '{Description}', placed on {OrderDate:d}. Your {{credit}} card will be billed {Details.UnitPrice:C}.";
string result = Template.Format(template, order);
// or use the extension: template.FormatTemplate(order);
Result:
We just shipped your order of 'Widget', placed on 2/28/2014. Your {credit} card will be billed $1,500.00.
Check out the project's GitHub link (above) and wiki for other examples.
private static Regex s_NamedFormatRegex = new Regex(#"\{(?!\{)(?<key>[\w]+)(:(?<fmt>(\{\{|\}\}|[^\{\}])*)?)?\}", RegexOptions.Compiled);
public static StringBuilder AppendNamedFormat(this StringBuilder builder,IFormatProvider provider, string format, IDictionary<string, object> args)
{
if (builder == null) throw new ArgumentNullException("builder");
var str = s_NamedFormatRegex.Replace(format, (mt) => {
string key = mt.Groups["key"].Value;
string fmt = mt.Groups["fmt"].Value;
object value = null;
if (args.TryGetValue(key,out value)) {
return string.Format(provider, "{0:" + fmt + "}", value);
} else {
return mt.Value;
}
});
builder.Append(str);
return builder;
}
public static StringBuilder AppendNamedFormat(this StringBuilder builder, string format, IDictionary<string, object> args)
{
if (builder == null) throw new ArgumentNullException("builder");
return builder.AppendNamedFormat(null, format, args);
}
Example:
var builder = new StringBuilder();
builder.AppendNamedFormat(
#"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() {
{ "Name", "wayjet" },
{ "LoginTimes",18 },
{ "Score", 100.4 },
{ "Date",DateTime.Now }
});
Output:
你好,wayjet,今天是2011-05-04, 这是你第18次登录,积分{ 100.40 }
Check this one:
public static string StringFormat(string format, object source)
{
var matches = Regex.Matches(format, #"\{(.+?)\}");
List<string> keys = (from Match matche in matches select matche.Groups[1].Value).ToList();
return keys.Aggregate(
format,
(current, key) =>
{
int colonIndex = key.IndexOf(':');
return current.Replace(
"{" + key + "}",
colonIndex > 0
? DataBinder.Eval(source, key.Substring(0, colonIndex), "{0:" + key.Substring(colonIndex + 1) + "}")
: DataBinder.Eval(source, key).ToString());
});
}
Sample:
string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var o = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.Now };
Console.WriteLine(StringFormat(format, o));
Performance is pretty ok compared to other solutions.
I doubt this will be possible. The first thing that comes to mind is how are you going to get access to local variable names?
There might be some clever way using LINQ and Lambda expressions to do this however.
Here's one I made a while back. It extends String with a Format method taking a single argument. The nice thing is that it'll use the standard string.Format if you provide a simple argument like an int, but if you use something like anonymous type it'll work too.
Example usage:
"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
Would result in "The Smith family has 4 children."
It doesn't do crazy binding stuff like arrays and indexers. But it is super simple and high performance.
public static class AdvancedFormatString
{
/// <summary>
/// An advanced version of string.Format. If you pass a primitive object (string, int, etc), it acts like the regular string.Format. If you pass an anonmymous type, you can name the paramters by property name.
/// </summary>
/// <param name="formatString"></param>
/// <param name="arg"></param>
/// <returns></returns>
/// <example>
/// "The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })
///
/// results in
/// "This Smith family has 4 children
/// </example>
public static string Format(this string formatString, object arg, IFormatProvider format = null)
{
if (arg == null)
return formatString;
var type = arg.GetType();
if (Type.GetTypeCode(type) != TypeCode.Object || type.IsPrimitive)
return string.Format(format, formatString, arg);
var properties = TypeDescriptor.GetProperties(arg);
return formatString.Format((property) =>
{
var value = properties[property].GetValue(arg);
return Convert.ToString(value, format);
});
}
public static string Format(this string formatString, Func<string, string> formatFragmentHandler)
{
if (string.IsNullOrEmpty(formatString))
return formatString;
Fragment[] fragments = GetParsedFragments(formatString);
if (fragments == null || fragments.Length == 0)
return formatString;
return string.Join(string.Empty, fragments.Select(fragment =>
{
if (fragment.Type == FragmentType.Literal)
return fragment.Value;
else
return formatFragmentHandler(fragment.Value);
}).ToArray());
}
private static Fragment[] GetParsedFragments(string formatString)
{
Fragment[] fragments;
if ( parsedStrings.TryGetValue(formatString, out fragments) )
{
return fragments;
}
lock (parsedStringsLock)
{
if ( !parsedStrings.TryGetValue(formatString, out fragments) )
{
fragments = Parse(formatString);
parsedStrings.Add(formatString, fragments);
}
}
return fragments;
}
private static Object parsedStringsLock = new Object();
private static Dictionary<string,Fragment[]> parsedStrings = new Dictionary<string,Fragment[]>(StringComparer.Ordinal);
const char OpeningDelimiter = '{';
const char ClosingDelimiter = '}';
/// <summary>
/// Parses the given format string into a list of fragments.
/// </summary>
/// <param name="format"></param>
/// <returns></returns>
static Fragment[] Parse(string format)
{
int lastCharIndex = format.Length - 1;
int currFragEndIndex;
Fragment currFrag = ParseFragment(format, 0, out currFragEndIndex);
if (currFragEndIndex == lastCharIndex)
{
return new Fragment[] { currFrag };
}
List<Fragment> fragments = new List<Fragment>();
while (true)
{
fragments.Add(currFrag);
if (currFragEndIndex == lastCharIndex)
{
break;
}
currFrag = ParseFragment(format, currFragEndIndex + 1, out currFragEndIndex);
}
return fragments.ToArray();
}
/// <summary>
/// Finds the next delimiter from the starting index.
/// </summary>
static Fragment ParseFragment(string format, int startIndex, out int fragmentEndIndex)
{
bool foundEscapedDelimiter = false;
FragmentType type = FragmentType.Literal;
int numChars = format.Length;
for (int i = startIndex; i < numChars; i++)
{
char currChar = format[i];
bool isOpenBrace = currChar == OpeningDelimiter;
bool isCloseBrace = isOpenBrace ? false : currChar == ClosingDelimiter;
if (!isOpenBrace && !isCloseBrace)
{
continue;
}
else if (i < (numChars - 1) && format[i + 1] == currChar)
{//{{ or }}
i++;
foundEscapedDelimiter = true;
}
else if (isOpenBrace)
{
if (i == startIndex)
{
type = FragmentType.FormatItem;
}
else
{
if (type == FragmentType.FormatItem)
throw new FormatException("Two consequtive unescaped { format item openers were found. Either close the first or escape any literals with another {.");
//curr character is the opening of a new format item. so we close this literal out
string literal = format.Substring(startIndex, i - startIndex);
if (foundEscapedDelimiter)
literal = ReplaceEscapes(literal);
fragmentEndIndex = i - 1;
return new Fragment(FragmentType.Literal, literal);
}
}
else
{//close bracket
if (i == startIndex || type == FragmentType.Literal)
throw new FormatException("A } closing brace existed without an opening { brace.");
string formatItem = format.Substring(startIndex + 1, i - startIndex - 1);
if (foundEscapedDelimiter)
formatItem = ReplaceEscapes(formatItem);//a format item with a { or } in its name is crazy but it could be done
fragmentEndIndex = i;
return new Fragment(FragmentType.FormatItem, formatItem);
}
}
if (type == FragmentType.FormatItem)
throw new FormatException("A format item was opened with { but was never closed.");
fragmentEndIndex = numChars - 1;
string literalValue = format.Substring(startIndex);
if (foundEscapedDelimiter)
literalValue = ReplaceEscapes(literalValue);
return new Fragment(FragmentType.Literal, literalValue);
}
/// <summary>
/// Replaces escaped brackets, turning '{{' and '}}' into '{' and '}', respectively.
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
static string ReplaceEscapes(string value)
{
return value.Replace("{{", "{").Replace("}}", "}");
}
private enum FragmentType
{
Literal,
FormatItem
}
private class Fragment
{
public Fragment(FragmentType type, string value)
{
Type = type;
Value = value;
}
public FragmentType Type
{
get;
private set;
}
/// <summary>
/// The literal value, or the name of the fragment, depending on fragment type.
/// </summary>
public string Value
{
get;
private set;
}
}
}
here is a simple method for any object:
using System.Text.RegularExpressions;
using System.ComponentModel;
public static string StringWithFormat(string format, object args)
{
Regex r = new Regex(#"\{([A-Za-z0-9_]+)\}");
MatchCollection m = r.Matches(format);
var properties = TypeDescriptor.GetProperties(args);
foreach (Match item in m)
{
try
{
string propertyName = item.Groups[1].Value;
format = format.Replace(item.Value, properties[propertyName].GetValue(args).ToString());
}
catch
{
throw new FormatException("The format string is not valid");
}
}
return format;
}
And here how to use it:
DateTime date = DateTime.Now;
string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);
output : 2/27/2012
I implemented this is a simple class that duplicates the functionality of String.Format (except for when using classes). You can either use a dictionary or a type to define fields.
https://github.com/SergueiFedorov/NamedFormatString
C# 6.0 is adding this functionality right into the language spec, so NamedFormatString is for backwards compatibility.
I solved this in a slightly different way to the existing solutions.
It does the core of the named item replacement (not the reflection bit that some have done). It is extremely fast and simple...
This is my solution:
/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
public class StringTemplateFormatter
{
private readonly IFormatProvider _formatProvider;
/// <summary>
/// Constructs the formatter with the specified <see cref="IFormatProvider"/>.
/// This is defaulted to <see cref="CultureInfo.CurrentCulture">CultureInfo.CurrentCulture</see> if none is provided.
/// </summary>
/// <param name="formatProvider"></param>
public StringTemplateFormatter(IFormatProvider formatProvider = null)
{
_formatProvider = formatProvider ?? CultureInfo.CurrentCulture;
}
/// <summary>
/// Formats a string with named format items given a template dictionary of the items values to use.
/// </summary>
/// <param name="text">The text template</param>
/// <param name="templateValues">The named values to use as replacements in the formatted string.</param>
/// <returns>The resultant text string with the template values replaced.</returns>
public string FormatTemplate(string text, Dictionary<string, object> templateValues)
{
var formattableString = text;
var values = new List<object>();
foreach (KeyValuePair<string, object> value in templateValues)
{
var index = values.Count;
formattableString = ReplaceFormattableItem(formattableString, value.Key, index);
values.Add(value.Value);
}
return String.Format(_formatProvider, formattableString, values.ToArray());
}
/// <summary>
/// Convert named string template item to numbered string template item that can be accepted by <see cref="string.Format(string,object[])">String.Format</see>
/// </summary>
/// <param name="formattableString">The string containing the named format item</param>
/// <param name="itemName">The name of the format item</param>
/// <param name="index">The index to use for the item value</param>
/// <returns>The formattable string with the named item substituted with the numbered format item.</returns>
private static string ReplaceFormattableItem(string formattableString, string itemName, int index)
{
return formattableString
.Replace("{" + itemName + "}", "{" + index + "}")
.Replace("{" + itemName + ",", "{" + index + ",")
.Replace("{" + itemName + ":", "{" + index + ":");
}
}
It is used in the following way:
[Test]
public void FormatTemplate_GivenANamedGuid_FormattedWithB_ShouldFormatCorrectly()
{
// Arrange
var template = "My guid {MyGuid:B} is awesome!";
var templateValues = new Dictionary<string, object> { { "MyGuid", new Guid("{A4D2A7F1-421C-4A1D-9CB2-9C2E70B05E19}") } };
var sut = new StringTemplateFormatter();
// Act
var result = sut.FormatTemplate(template, templateValues);
//Assert
Assert.That(result, Is.EqualTo("My guid {a4d2a7f1-421c-4a1d-9cb2-9c2e70b05e19} is awesome!"));
}
Hope someone finds this useful!
Even though the accepted answer gives some good examples, the .Inject as well as some of the Haack examples do not handle escaping. Many also rely heavily on Regex (slower), or DataBinder.Eval which is not available on .NET Core, and in some other environments.
With that in mind, I've written a simple state machine based parser that streams through characters, writing to a StringBuilder output, character by character. It is implemented as String extension method(s) and can take both a Dictionary<string, object> or object with parameters as input (using reflection).
It handles unlimited levels of {{{escaping}}} and throws FormatException when input contains unbalanced braces and/or other errors.
public static class StringExtension {
/// <summary>
/// Extension method that replaces keys in a string with the values of matching object properties.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="injectionObject">The object whose properties should be injected in the string</param>
/// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, object injectionObject) {
return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
char openBraceChar = '{';
char closeBraceChar = '}';
return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
}
/// <summary>
/// Extension method that replaces keys in a string with the values of matching dictionary entries.
/// </summary>
/// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
/// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
/// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
string result = formatString;
if (dictionary == null || formatString == null)
return result;
// start the state machine!
// ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
StringBuilder outputString = new StringBuilder(formatString.Length * 2);
StringBuilder currentKey = new StringBuilder();
bool insideBraces = false;
int index = 0;
while (index < formatString.Length) {
if (!insideBraces) {
// currently not inside a pair of braces in the format string
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// add a brace to the output string
outputString.Append(openBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// not an escaped brace, set state to inside brace
insideBraces = true;
index++;
continue;
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered outside braces
if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
// this is an escaped closing brace, this is okay
// add a closing brace to the output string
outputString.Append(closeBraceChar);
// skip over braces
index += 2;
continue;
}
else {
// this is an unescaped closing brace outside of braces.
// throw a format exception
throw new FormatException($"Unmatched closing brace at position {index}");
}
}
else {
// the character has no special meaning, add it to the output string
outputString.Append(formatString[index]);
// move onto next character
index++;
continue;
}
}
else {
// currently inside a pair of braces in the format string
// found an opening brace
if (formatString[index] == openBraceChar) {
// check if the brace is escaped
if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
// there are escaped braces within the key
// this is illegal, throw a format exception
throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
}
else {
// not an escaped brace, we have an unexpected opening brace within a pair of braces
throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
}
}
else if (formatString[index] == closeBraceChar) {
// handle case where closing brace is encountered inside braces
// don't attempt to check for escaped braces here - always assume the first brace closes the braces
// since we cannot have escaped braces within parameters.
// set the state to be outside of any braces
insideBraces = false;
// jump over brace
index++;
// at this stage, a key is stored in current key that represents the text between the two braces
// do a lookup on this key
string key = currentKey.ToString();
// clear the stringbuilder for the key
currentKey.Clear();
object outObject;
if (!dictionary.TryGetValue(key, out outObject)) {
// the key was not found as a possible replacement, throw exception
throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
}
// we now have the replacement value, add the value to the output string
outputString.Append(outObject);
// jump to next state
continue;
} // if }
else {
// character has no special meaning, add it to the current key
currentKey.Append(formatString[index]);
// move onto next character
index++;
continue;
} // else
} // if inside brace
} // while
// after the loop, if all braces were balanced, we should be outside all braces
// if we're not, the input string was misformatted.
if (insideBraces) {
throw new FormatException("The format string ended before the parameter was closed.");
}
return outputString.ToString();
}
/// <summary>
/// Creates a Dictionary from an objects properties, with the Key being the property's
/// name and the Value being the properties value (of type object)
/// </summary>
/// <param name="properties">An object who's properties will be used</param>
/// <returns>A <see cref="Dictionary"/> of property values </returns>
private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
Dictionary<string, object> values = null;
if (properties != null) {
values = new Dictionary<string, object>();
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
foreach (PropertyDescriptor prop in props) {
values.Add(prop.Name, prop.GetValue(properties));
}
}
return values;
}
}
Ultimately, all the logic boils down into 10 main states - For when the state machine is outside a bracket and likewise inside a bracket, the next character is either an open brace, an escaped open brace, a closed brace, an escaped closed brace, or an ordinary character. Each of these conditions is handled individually as the loop progresses, adding characters to either an output StringBuffer or a key StringBuffer. When a parameter is closed, the value of the key StringBuffer is used to look up the parameter's value in the dictionary, which then gets pushed into the output StringBuffer. At the end, the value of the output StringBuffer is returned.
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";
Edit:
What I should have said was, "No, I don't believe what you want to do is supported by C#. This is as close as you are going to get."