web-dev-qa-db-fra.com

Formatage de chaîne nommée en C #

Est-il possible de formater une chaîne par nom plutôt que par position en C #?

En python, je peux faire quelque chose comme cet exemple (volé sans vergogne de ici ):

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

Est-il possible de faire cela en C #? Dites par exemple:

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

Être capable de faire cela en utilisant un nom de variable serait bien, mais un dictionnaire est également acceptable.

153
Jason Baker

Il n'y a pas de méthode intégrée pour gérer cela.

Voici une méthode

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

Voici un autre

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

Une troisième méthode améliorée partiellement basée sur les deux précédentes , de Phil Haack

127
John Sheehan

J'ai une implémentation que je viens de poster sur mon blog ici: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-Edge-cases.aspx

Il résout certains problèmes que ces autres implémentations ont avec échappement par accolade. La poste a des détails. Cela fait aussi l'affaire DataBinder.Eval, mais reste très rapide.

45
Haacked

Des chaînes interpolées ont été ajoutées dans C # 6.0 et Visual Basic 14

Les deux ont été introduits via le nouveau compilateur Roslyn dans Visual Studio 2015.

  • C # 6.0: 

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

  • VB 14: 

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

Fonctionnalités remarquables (dans Visual Studio 2015 IDE):

  • syntax coloring est supporté - les variables contenues dans les chaînes sont mises en surbrillance
  • refactoring est pris en charge - lors du changement de nom, les variables contenues dans les chaînes sont renommées
  • en fait, non seulement les noms de variables, mais expressions sont supportés - par exemple non seulement {index} fonctionne, mais aussi {(index + 1).ToString().Trim()}

Prendre plaisir! (& cliquez sur "Envoyer un sourire" dans le VS)

40
miroxlav

Vous pouvez également utiliser des types anonymes comme ceci:

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

Bien sûr, cela nécessiterait plus de code si vous souhaitez également analyser le formatage, mais vous pouvez formater une chaîne en utilisant cette fonction comme:

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

Il ne semble pas y avoir de moyen de le faire en dehors de la boîte. Cependant, il semble possible de mettre en œuvre votre propre IFormatProvider qui renvoie à une IDictionary pour les valeurs.

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

Les sorties:

FormatProviders, de sorte que le formatage de texte de fantaisie ne peut pas être utilisé en même temps.

13
spoulson

Le cadre lui-même ne fournit pas de moyen de faire cela, mais vous pouvez jeter un oeil à cet article de Scott Hanselman. Exemple d'utilisation:

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

Ce code de James Newton-King est similaire et fonctionne avec des sous-propriétés et des index, 

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

Le code de James s'appuie sur System.Web.UI.DataBinder pour analyser la chaîne et nécessite de référencer System.Web, ce que certaines personnes n'aiment pas faire dans des applications non Web.

EDIT: Oh, et ils fonctionnent bien avec les types anonymes, si vous n'avez pas d'objet avec les propriétés prêt pour cela:

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

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

Avec l'extension liée à, vous pouvez écrire ceci:

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

et vous aurez "foo 2 System.Object ".

6
Mark Cidade

Je pense que le plus proche que vous obtiendrez est un format indexé:

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

Il y a aussi String.Replace (), si vous êtes prêt à le faire en plusieurs étapes et à croire que vous ne trouverez pas vos «variables» nulle part ailleurs dans la chaîne:

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

Développer ceci pour utiliser une liste:

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

Vous pouvez le faire avec Dictionary <string, string> aussi en itérant ses collections .Keys, mais en utilisant List <KeyValuePair <string, string >>, nous pouvons tirer parti de la méthode .ForEach () de List et le condenser un one-liner:

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

Un lambda serait encore plus simple, mais je suis toujours sur .Net 2.0. Notez également que les performances .Replace () ne sont pas stellaires lorsqu’elles sont utilisées de manière itérative, car les chaînes en .Net sont immuables. De plus, cela nécessite que la variable MyString soit définie de manière à ce qu'elle soit accessible au délégué, de sorte qu'elle n'est pas encore parfaite.

4
Joel Coehoorn

Ma bibliothèque open source, Regextra , prend en charge le formatage nommé (entre autres). Il cible actuellement .NET 4.0+ et est disponible sur NuGet . J'ai également un article de blog d'introduction à ce sujet: Regextra: vous aider à réduire vos (problèmes) {2} .

Le bit de formatage nommé prend en charge:

  • Mise en forme de base
  • Mise en forme des propriétés imbriquées
  • Mise en forme du dictionnaire
  • Échappement des délimiteurs
  • Format de chaîne standard/personnalisé/IFormatProvider

Exemple:

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

Résultat:

Nous venons d'expédier votre commande de 'Widget', passée le 28/02/2014. Votre carte {crédit} sera facturée 1 500,00 $.

Consultez le lien GitHub du projet (ci-dessus) et le wiki pour d'autres exemples.

3
Ahmad Mageed

Vérifier celui-ci:

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

Échantillon:

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

Les performances sont plutôt bonnes comparées aux autres solutions.

2
Pavlo Neyman

voici une méthode simple pour tout objet:

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

Et voici comment l'utiliser:

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

sortie: 27/02/2012

1
Ashkan Ghodrat

Je doute que cela sera possible. La première chose qui me vient à l’esprit est de savoir comment vous allez avoir accès aux noms de variables locales.

Il pourrait toutefois y avoir un moyen intelligent d’utiliser les expressions LINQ et Lambda pour le faire.

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

Exemple:

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

Sortie: 你好, wayjet, le 11/05/2011, le 18 août {100.40}

1
wayjet

En voici une que j'ai faite il y a quelque temps. Il étend String avec une méthode Format prenant un seul argument. La bonne chose est qu’elle utilisera la chaîne standard.Format si vous fournissez un argument simple comme un int, mais si vous utilisez quelque chose comme un type anonyme, cela fonctionnera aussi.

Exemple d'utilisation:

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

Cela se traduirait par "La famille Smith a 4 enfants."

Il ne fait pas des choses de liaison fous comme les tableaux et les indexeurs. Mais c'est super simple et performant.

    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

J'ai résolu ce problème d'une manière légèrement différente des solutions existantes . Il effectue l'essentiel du remplacement de l'élément nommé (et non le bit de réflexion que certains ont déjà fait). C’est extrêmement simple et rapide .... Voici ma 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 + ":");
    }
}

Il est utilisé de la manière suivante:

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

J'espère que quelqu'un trouve cela utile!

0
Mark Whitfeld

J'ai implémenté il s'agit d'une classe simple qui duplique les fonctionnalités de String.Format (sauf lors de l'utilisation de classes). Vous pouvez utiliser un dictionnaire ou un type pour définir des champs.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 ajoute cette fonctionnalité directement dans la spécification de langue, donc NamedFormatString est destiné à la compatibilité ascendante.

0
Serguei Fedorov

Même si la réponse acceptée donne de bons exemples, le .Inject ainsi que certains exemples de Haack ne gèrent pas l'échappement. Nombre d'entre eux s'appuient également fortement sur Regex (plus lent) ou sur DataBinder.Eval, qui n'est pas disponible sur .NET Core et dans certains autres environnements.

Dans cet esprit, j'ai écrit un analyseur basé sur une machine à états simple qui parcourt les caractères, écrivant dans une sortie StringBuilder, caractère par caractère. Elle est implémentée en tant que méthode (s) d'extension String et peut prendre un Dictionary<string, object> ou object avec des paramètres en entrée (à l'aide de la réflexion).

Il gère un nombre illimité de {{{escaping}}} et jette FormatException lorsque l'entrée contient des accolades non équilibrées et/ou d'autres erreurs.

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

En fin de compte, toute la logique se résume en 10 états principaux - Par exemple, lorsque la machine à états est en dehors d’un crochet, le caractère suivant est un accolade ouverte, un accolade ouvert échappé, un accolade fermée ou un personnage ordinaire. Chacune de ces conditions est traitée individuellement au fur et à mesure de l'avancement de la boucle, en ajoutant des caractères à une sortie StringBuffer ou à une clé StringBuffer. Lorsqu'un paramètre est fermé, la valeur de la clé StringBuffer est utilisée pour rechercher la valeur du paramètre dans le dictionnaire, qui est ensuite poussée dans la sortie StringBuffer. A la fin, la valeur de la sortie StringBuffer est renvoyée.

0
Ryan