Benannte Zeichenfolgenformatierung in C #


156

Gibt es eine Möglichkeit, eine Zeichenfolge nach Namen und nicht nach Position in C # zu formatieren?

In Python kann ich so etwas wie dieses Beispiel machen (schamlos von 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}", ...);

Es wäre schön, dies mit einem Variablennamen tun zu können, aber ein Wörterbuch ist auch akzeptabel.


Ich vermisse das auch bei Ruby.
JesperE

Ich denke, Ihr Beispiel ist zu simpel und führt dazu, dass die Leute Ihnen nicht hilfreiche Antworten geben. Vielleicht wäre es demonstrativer, eine Variable mehr als einmal in der Zeichenfolge zu verwenden.
Wedge

Tatsächlich ist die SPEZIFISCHE Verwirrung die Verwendung von String.Format. Das eignet sich für Antworten wie meine, die nicht hilfreich sind, weil sie nicht variablenorientiert sind, aber in Bezug auf String.Format genau sind.
John Rudy

1
Der Aufruf von String.Format ist offensichtlich ein erfundenes Beispiel. Es sei denn natürlich, Sie wussten nicht, dass das Aufrufen von String.Format mit Ellipsen nicht möglich ist. Das Problem war, dass ich nicht gesagt habe, dass die Formatierung durch benannte Parameter und nicht durch die Position erfolgen soll, die fixiert wurde.
Jason Baker

Zu Ihrer Information: Wird an die User Voice von MS Connect gesendet, um zu beantragen, dass dies zu einer Standardfunktion des Frameworks wird. Für alle Interessierten bitte upvote: visualstudio.uservoice.com/forums/121579-visual-studio/…
JohnLBevan

Antworten:


130

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 basiert , von Phil Haack


11
Ich war sehr zufrieden mit FormatWith (), wollte aber auf ein Problem hinweisen, auf das ich kürzlich gestoßen bin. Die Implementierung basiert auf dem DataBinder von System.Web.UI, der in SQL CLR nicht unterstützt wird. Inject (o) ist nicht auf den Datenordner angewiesen, was es für das Ersetzen mehrerer Token in meinem SQL CLR-Objekt nützlich machte.
EBarr

1
Vielleicht können Sie den ersten Satz Ihrer Antwort aktualisieren. Die String-Interpolation ist in C # und VB einige Monate lang vorhanden (endlich ...). Ihre Antwort befindet sich oben, daher kann es für Leser hilfreich sein, wenn Sie sie mit aktualisierten .NET-Ressourcen verknüpfen können.
Miroxlav

1
@miroxlav es ist nicht wirklich das gleiche. Sie können keine interpolierten Zeichenfolgen weitergeben: stackoverflow.com/q/31987232/213725
DixonD

@ DixonD - Sie haben definitiv Recht, aber es war nicht ihr Zweck. In den von Ihnen verknüpften Fragen und Antworten versucht OP, den Variablennamen zu referenzieren, bevor er existiert. Keine sehr gute Idee, aber wenn jemand darauf besteht, kann er einen speziellen Parser erstellen. Aber ich würde das nicht mit dem allgemeinen String-Interpolationskonzept verwechseln.
Miroxlav

44

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

Es werden einige Probleme behoben, die bei diesen anderen Implementierungen auftreten, wenn die Klammer ausgeblendet wird. Der Beitrag enthält Details. Es macht auch die DataBinder.Eval-Sache, ist aber immer noch sehr schnell.


3
Der Code, der in diesem Artikel 404 zum Download zur Verfügung steht. Ich würde es auch sehr gerne sehen.
Quentin-Starin

2
@qes: Ein aktualisierter Link wurde in den Kommentaren gepostet: code.haacked.com/util/NamedStringFormatSolution.zip
Der Hochstapler

3
@OliverSalzburg: Ich verwende SmartFormat seit einiger Zeit für alle meine Formatierungsanforderungen. Ich liebe es. github.com/scottrippey/SmartFormat
Quentin-Starin

@qes: Würde es Ihnen etwas ausmachen, darüber zu schreiben und zu antworten und zu zeigen, wie es funktioniert? Sieht interessant aus
Der Hochstapler

@qes: Sie sollten auf jeden Fall SmartFormat als Antwort hinzufügen, da es sehr nett ist und aktiv unterstützt wird (2015).
Răzvan Flavius ​​Panda

42

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

  • Syntaxfärbung wird unterstützt - in Zeichenfolgen enthaltene Variablen werden hervorgehoben
  • Refactoring wird unterstützt - beim Umbenennen werden auch in Zeichenfolgen enthaltene Variablen umbenannt
  • Eigentlich werden nicht nur Variablennamen, sondern auch Ausdrücke unterstützt - zB {index}funktioniert nicht nur , sondern auch{(index + 1).ToString().Trim()}

Genießen! (& klicken Sie im VS auf "Lächeln senden")


2
Die Frage ist mit .net 3.5 markiert, daher sind Ihre Informationen gültig, aber keine Alternative
Douglas Gandini

1
@miroxlav - Sie haben Recht mit der Framework-Version. Die String-Interpolation hängt nur vom neuen Roslyn-Compiler ab, der in VS 2015 verwendet wird.
Douglas Gandini

2
Dies funktioniert auch nur, wenn Ihre Formatzeichenfolge in den Code selbst eingefügt wird. Das heißt, es funktioniert nicht, wenn Ihre Formatzeichenfolge aus einer externen Quelle stammt, z. B. einer Konfigurationsdatei oder einer Datenbank.
Craig Brett

40

Sie können auch anonyme Typen wie diesen 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, aber Sie können eine Zeichenfolge mit dieser Funktion wie folgt formatieren:

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

1
Perfekt für diejenigen von uns, die noch auf 2.0 sind. Ja, ich weiß ... Diese Lösung ist einfach und leicht zu verstehen. UND ES FUNKTIONIERT!!!
Brad Bruce

14

Es scheint keine Möglichkeit zu geben, dies sofort zu tun. Es scheint jedoch machbar, eine eigene zu implementieren IFormatProvider, die mit einem IDictionaryfor-Wert 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:

Python hat 2 Anführungszeichen

Die Einschränkung ist, dass Sie nicht mischen können FormatProviders, sodass die ausgefallene Textformatierung nicht gleichzeitig verwendet werden kann.


1
+1 für die Gliederung, IMHO, die beste konzeptionelle Methode, die eine schöne Implementierung unter mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html hat - die anderen Beiträge enthalten dies, aber sie auch Schlagen Sie die auf Reflexion basierenden Methoden vor, die meiner Meinung nach ziemlich böse sind
Adam Ralph

9

Das Framework selbst bietet keine Möglichkeit, dies zu tun, aber Sie können sich diesen Beitrag von Scott Hanselman ansehen . Anwendungsbeispiel:

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

James 'Code basiert auf System.Web.UI.DataBinder , um die Zeichenfolge zu analysieren, und erfordert die Referenzierung auf System.Web, was manche Leute in Nicht-Webanwendungen nicht gerne tun.

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

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


4

Ich denke, das nächste, was Sie bekommen, ist 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 zu tun und davon auszugehen, dass Sie Ihre 'Variablen' nirgendwo anders in der Zeichenfolge finden:

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

Erweitern Sie dies, 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 die .Keys-Sammlungen iterieren. Wenn Sie jedoch eine List <KeyValuePair <string, string >> verwenden, können Sie die .ForEach () -Methode der Liste nutzen und sie wieder komprimieren ein Einzeiler:

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 auch, dass die Leistung von .Replace () bei iterativer Verwendung nicht herausragend ist, da Zeichenfolgen in .Net unveränderlich sind. Dazu muss die MyStringVariable auch so definiert sein, dass sie für den Delegaten zugänglich ist, sodass sie noch nicht perfekt ist.


Nun, das ist nicht die schönste Lösung, aber ich gehe jetzt damit um. Das einzige, was ich anders gemacht habe, war, einen StringBuilder anstelle eines Strings zu verwenden, damit ich nicht ständig neue Strings mache.
Jason Baker

3

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

Das benannte Formatierungsbit unterstützt:

  • Grundlegende Formatierung
  • Formatierung verschachtelter Eigenschaften
  • Wörterbuchformatierung
  • Flucht vor Begrenzern
  • Formatierung von Standard- / benutzerdefinierten / IFormatProvider-Zeichenfolgen

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' versendet, die am 28.02.2014 aufgegeben wurde. Ihre {Kreditkarte} wird mit 1.500,00 USD belastet.

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


Wow, das sieht fantastisch aus, besonders wenn es um einige der schwierigeren Formatbeispiele geht, auf die man stößt.
Nicholas Petersen

2

Überprüfen Sie dieses:

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

Stichprobe:

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 in Ordnung.


1

Ich bezweifle, dass dies möglich sein wird. Das erste, was Ihnen in den Sinn kommt, ist, wie Sie Zugriff auf lokale Variablennamen erhalten.

Es könnte jedoch eine clevere Möglichkeit geben, LINQ- und Lambda-Ausdrücke zu verwenden, um dies zu tun.


@leppie: +1, wenn Sie mir etwas LINQ + Lambda geben können, um das zu tun; D (ok +1 für eine relevante Antwort)
user7116

Ich würde es auch gerne sehen! Vielleicht nehme ich diese Herausforderung an!
Leppie

Ich dachte, es wäre unmöglich, mit Variablennamen zu tun, aber legte das dort ein, falls ich falsch lag. :) Gibt es auch keine Möglichkeit, dies mit einem Wörterbuch zu tun?
Jason Baker

Ich habe es versucht und bin irgendwohin gekommen, aber ich fand es zu hässlich und schwierig zu benutzen. Es hätte so ausgesehen: string s = format (f => f ("{hallo} {world}", hallo, world));
Leppie

1

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 ist, dass es die Standardzeichenfolge verwendet. Format, wenn Sie ein einfaches Argument wie ein int angeben, aber wenn Sie so etwas wie einen anonymen Typ verwenden, funktioniert es auch.

Anwendungsbeispiel:

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

Würde zu "Die Familie Smith hat 4 Kinder."

Es macht keine verrückten Bindungssachen 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
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: 你好, wayjet, 今天 是 2011-05-04, 这 是 你 第 18 次 登录 , 100. {100.40}


1

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


0

Ich habe implementiert, dass dies eine einfache Klasse ist, 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, NamedFormatStringum die Abwärtskompatibilität zu gewährleisten.


0

Ich habe dies etwas anders gelöst als die vorhandenen Lösungen. Es übernimmt den Kern des Ersetzens des benannten Elements (nicht das Reflexionsbit, das einige durchgeführt 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 folgendermaßen 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

Obwohl die akzeptierte Antwort einige gute Beispiele enthält, können sowohl das .Inject als auch einige der Haack-Beispiele nicht entkommen. Viele verlassen sich auch stark auf Regex (langsamer) oder DataBinder.Eval, das in .NET Core und in einigen anderen Umgebungen nicht verfügbar ist.

In diesem Sinne habe ich einen einfachen, auf einer Zustandsmaschine basierenden Parser geschrieben, der Zeichen durchströmt und zeichenweise in eine StringBuilderAusgabe schreibt . Es wird als implementiert StringErweiterungsmethode (n) und sowohl a annehmen kann Dictionary<string, object>oder objectmit Parametern als Eingang (mit Reflexion).

Es verarbeitet unbegrenzte Ebenen von {{{escaping}}}und wirft, FormatExceptionwenn die Eingabe unausgeglichene 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 hinaus. Wenn sich die Zustandsmaschine außerhalb einer Klammer und ebenfalls innerhalb einer Klammer befindet, ist das nächste Zeichen entweder eine offene Klammer, eine entkommene offene Klammer, eine geschlossene Klammer, eine entkommene geschlossene Klammer. oder ein gewöhnlicher Charakter. Jede dieser Bedingungen wird im Verlauf der Schleife einzeln behandelt, wobei entweder einer Ausgabe StringBufferoder einer Taste Zeichen hinzugefügt werden StringBuffer. Wenn ein Parameter geschlossen wird, wird der Wert des Schlüssels StringBufferverwendet, um den Wert des Parameters im Wörterbuch nachzuschlagen, der dann in die Ausgabe verschoben wird StringBuffer. Am Ende wird der Wert der Ausgabe StringBufferzurückgegeben.


-6
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";

Bearbeiten: Was ich hätte sagen sollen war: "Nein, ich glaube nicht, dass das, was Sie tun möchten, von C # unterstützt wird. Dies ist so nah wie möglich."


1
Ich bin gespannt auf die Abstimmungen. Möchte mir jemand sagen warum?
Kevin

1
Das string.format würde diese Operation also 4 / Zehntausendstel Sekunden schneller ausführen. Wenn diese Funktion als Tonne bezeichnet wird, werden Sie diesen Unterschied möglicherweise bemerken. Aber es beantwortet zumindest seine Frage, anstatt ihm nur zu sagen, dass er es genauso machen soll, wie er bereits gesagt hat, dass er es nicht tun möchte.
Kevin

4
Ich habe Sie nicht abgewählt, aber ich würde dies nicht hauptsächlich implementieren, weil ich es hässlich finde, viele String-Verkettungen zu machen. Aber das ist meine persönliche Ansicht.
Jason Baker

Seltsam, dass dies so sehr abgestimmt wurde. Erwägen Sie, Ihre Antwort zu erweitern. Wenn die Verkettung nicht häufig aufgerufen wird, können Sie eine "someString" + someVariable + "someOtherString"bessere Lesbarkeit in Betracht ziehen . Dieser Artikel stimmt mit Ihnen überein.
Steven Jeuris
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.