webentwicklung-frage-antwort-db.com.de

Benannte String-Formatierung in C #

Gibt es eine Möglichkeit, einen String nach Name und nicht nach Position in C # zu formatieren?

In Python kann ich so etwas wie dieses Beispiel machen (schamlos aus hier gestohlen):

>>> print '%(language)s has %(#)03d quote types.' % \
      {'language': "Python", "#": 2}
Python has 002 quote types.

Gibt es eine Möglichkeit, dies in C # zu tun? Sagen Sie zum Beispiel:

String.Format("{some_variable}: {some_other_variable}", ...);

Dies mit einem Variablennamen tun zu können, wäre Nizza, aber auch ein Wörterbuch ist akzeptabel.

153
Jason Baker

Es gibt keine integrierte Methode, um dies zu handhaben.

Hier ist eine Methode

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

Hier ist ein anderes

Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);

Eine dritte verbesserte Methode, die teilweise auf den beiden oben genannten von Phil Haack basiert

127
John Sheehan

Ich habe eine Implementierung, die ich gerade in meinem Blog gepostet habe: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-Edge-cases.aspx

Es wird auf einige Probleme dieser anderen Implementierungen eingegangen, bei denen eine Flucht der Klammern auftritt. Der Beitrag enthält Details. Es macht auch das DataBinder.Eval-Ding, ist aber immer noch sehr schnell.

45
Haacked

Interpolierte Zeichenfolgen wurden in C # 6.0 und Visual Basic 14 hinzugefügt

Beide wurden durch den neuen Roslyn - Compiler in Visual Studio 2015 eingeführt.

  • C # 6.0: 

    return "\{someVariable} and also \{someOtherVariable}" ODER
    return $"{someVariable} and also {someOtherVariable}"

  • VB 14: 

    return $"{someVariable} and also {someOtherVariable}"

Bemerkenswerte Funktionen (in Visual Studio 2015 IDE):

  • Syntax Coloring wird unterstützt - in Strings enthaltene Variablen werden hervorgehoben
  • refactoring wird unterstützt - beim Umbenennen werden in Strings enthaltene Variablen ebenfalls umbenannt
  • tatsächlich werden nicht nur Variablennamen, sondern -Ausdrücke unterstützt - z. nicht nur {index} funktioniert, sondern auch {(index + 1).ToString().Trim()}

Genießen! (& klicke im VS auf "Lächeln")

40
miroxlav

Sie können auch anonyme Typen wie diese verwenden:

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

Natürlich würde es mehr Code erfordern, wenn Sie auch die Formatierung analysieren möchten. Sie können jedoch eine Zeichenfolge mit dieser Funktion wie

Format("test {first} and {another}", new { first = "something", another = "something else" })
39
Doggett

Es scheint keine Möglichkeit zu geben, dies aus der Box heraus zu tun. Es scheint jedoch machbar, Ihre eigene IFormatProvider zu implementieren, die mit einer IDictionary für Werte verknüpft ist.

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);

Ausgänge:

FormatProviders nicht mischen können, sodass die Textformatierung nicht gleichzeitig verwendet werden kann.

13
spoulson

Das Framework selbst bietet keine Möglichkeit, dies zu tun, aber Sie können einen Blick auf diesen Beitrag von Scott Hanselman werfen. Verwendungsbeispiel:

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); 

Dieser Code von James Newton-King ist ähnlich und funktioniert mit Untereigenschaften und Indizes. 

string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));

Der Code von James stützt sich auf System.Web.UI.DataBinder, um die Zeichenfolge zu analysieren, und erfordert den Verweis auf System.Web, was einige Leute in Nicht-Webanwendungen nicht gerne tun.

EDIT: Oh, und sie funktionieren gut mit anonymen Typen, wenn Sie kein Objekt mit Eigenschaften dafür haben:

string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
9
Lucas

Siehe https://stackoverflow.com/questions/271398?page=2#358259

Mit der verlinkten Erweiterung können Sie folgendes schreiben:

var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());

und du bekommst "foo 2 System.Object ".

6
Mark Cidade

Ich glaube, am nächsten kommt ein indiziertes Format:

String.Format("{0} has {1} quote types.", "C#", "1");

Es gibt auch String.Replace (), wenn Sie bereit sind, dies in mehreren Schritten durchzuführen und davon auszugehen, dass Sie Ihre Variablen an keiner anderen Stelle im String finden:

string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");

Erweitern, um eine Liste zu verwenden:

List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

Sie können dies auch mit einem Dictionary <string, string> tun, indem Sie dessen .Keys-Sammlungen durchlaufen. Durch die Verwendung einer List <KeyValuePair <string, string >> können Sie jedoch die .ForEach () - Methode von List nutzen und diese wieder zusammenführen ein Einliner:

replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

Ein Lambda wäre noch einfacher, aber ich bin immer noch auf .Net 2.0. Beachten Sie außerdem, dass die Leistung von .Replace () bei iterativer Verwendung nicht herausragend ist, da Strings in .Net unveränderlich sind. Dies erfordert auch, dass die Variable MyString so definiert ist, dass sie für den Delegierten zugänglich ist, also noch nicht perfekt ist.

4
Joel Coehoorn

Meine Open Source-Bibliothek Regextra unterstützt unter anderem die benannte Formatierung. Es zielt derzeit auf .NET 4.0 und ist unter NuGet verfügbar. Ich habe auch einen einführenden Blogbeitrag dazu: Regextra: hilft Ihnen, Ihre (Probleme) zu reduzieren {2} .

Das benannte Formatierungsbit unterstützt:

  • Grundlegende Formatierung
  • Formatierung von verschachtelten Eigenschaften
  • Wörterbuchformatierung
  • Flucht von Trennzeichen
  • Standard-/benutzerdefinierte/IFormatProvider-Stringformatierung

Beispiel:

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);

Ergebnis:

Wir haben gerade Ihre Bestellung von 'Widget' am 28.02.2014 versendet. Ihre {credit} -Karte wird mit $ 1.500,00 belastet.

Weitere Beispiele finden Sie im GitHub-Link des Projekts (oben) und im Wiki.

3
Ahmad Mageed

Überprüfen Sie diese hier:

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());
        });
}

Probe:

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));

Die Leistung ist im Vergleich zu anderen Lösungen ziemlich gut.

2
Pavlo Neyman

hier ist eine einfache Methode für jedes Objekt:

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

Und hier, wie man es benutzt:

 DateTime date = DateTime.Now;
 string dateString = StringWithFormat("{Month}/{Day}/{Year}", date);

ausgabe: 27.02.2012

1
Ashkan Ghodrat

Ich bezweifle, dass dies möglich sein wird. Das Erste, was Ihnen einfällt, ist, wie Sie auf lokale Variablennamen zugreifen können.

Es gibt jedoch einige Möglichkeiten, LINQ- und Lambda-Ausdrücke dafür einzusetzen.

1
leppie
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);
}

Beispiel:

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 }
});

Ausgabe: Way, Wayjet, -05 -04 2011-05-04, 第 是 你 第 18 次 登录 , {100.40}

1
wayjet

Hier ist eine, die ich vor einiger Zeit gemacht habe. Es erweitert String um eine Format-Methode, die ein einzelnes Argument verwendet. Das Schöne daran ist, dass es die Standardzeichenfolge verwendet. Wenn Sie ein einfaches Argument wie ein int angeben, können Sie ein Format verwenden.

Verwendungsbeispiel:

"The {Name} family has {Children} children".Format(new { Children = 4, Name = "Smith" })

Ergebnis wäre "Die Familie Smith hat 4 Kinder."

Es macht keine verrückten Bindungen wie Arrays und Indexer. Aber es ist super einfach und leistungsstark.

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


    }

}
1
Steve Potter

Ich habe dieses Problem ein wenig anders gelöst als die bestehenden Lösungen ... Es erledigt den Kern des genannten Elements als Ersatz (nicht das Reflexionsbit, das einige bereits gemacht haben). Es ist extrem schnell und einfach ... Dies ist meine Lösung:

/// <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 + ":");
    }
}

Es wird auf folgende Weise verwendet:

    [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!"));
    }

Hoffe, jemand findet das nützlich!

0
Mark Whitfeld

Dies ist eine einfache Klasse, die die Funktionalität von String.Format dupliziert (außer bei Verwendung von Klassen). Sie können entweder ein Wörterbuch oder einen Typ verwenden, um Felder zu definieren.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 fügt diese Funktionalität direkt in die Sprachspezifikation ein, sodass NamedFormatString für Rückwärtskompatibilität sorgt.

0
Serguei Fedorov

Obwohl die akzeptierte Antwort einige gute Beispiele liefert, können das .Inject sowie einige der Haack-Beispiele nicht mit Flucht umgehen. Viele setzen auch stark auf Regex (langsamer) oder DataBinder.Eval, das in .NET Core und in einigen anderen Umgebungen nicht verfügbar ist.

Aus diesem Grund habe ich einen einfachen Zustandsmaschinen-basierten Parser geschrieben, der Zeichen durchströmt und Zeichen für Zeichen in eine StringBuilder-Ausgabe schreibt. Es ist als String-Erweiterungsmethode (s) implementiert und kann sowohl Dictionary<string, object> als auch object mit Parametern als Eingabe (unter Verwendung von Reflektion) verwenden.

Es behandelt unbegrenzte Ebenen von {{{escaping}}} und wirft FormatException, wenn die Eingabe unsymmetrische geschweifte Klammern und/oder andere Fehler enthält.

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

Letztendlich läuft die gesamte Logik auf 10 Hauptzustände zurück. Wenn sich die Zustandsmaschine außerhalb einer Klammer befindet und in einer Klammer ist das nächste Zeichen entweder eine offene Klammer, eine geöffnete Klammer, eine geschlossene Klammer, eine geschlossene Klammer, oder ein gewöhnlicher Charakter. Jede dieser Bedingungen wird während des Fortschreitens der Schleife einzeln behandelt, indem Zeichen entweder zu einer Ausgabe StringBuffer oder einer Taste StringBuffer hinzugefügt werden. Wenn ein Parameter geschlossen wird, wird der Wert der Taste StringBuffer verwendet, um den Wert des Parameters im Wörterbuch nachzuschlagen, der dann in die Ausgabe StringBuffer verschoben wird. Am Ende wird der Wert der Ausgabe StringBuffer zurückgegeben.

0
Ryan