Memformat string bernama dalam C #


156

Apakah ada cara untuk memformat string dengan nama daripada posisi di C #?

Dengan python, saya dapat melakukan sesuatu seperti contoh ini (tanpa malu dicuri dari sini ):

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

Apakah ada cara untuk melakukan ini di C #? Katakan misalnya:

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

Mampu melakukan ini menggunakan nama variabel akan menyenangkan, tetapi kamus juga dapat diterima.


Saya kehilangan ini dari Ruby juga.
JesperE

Saya pikir teladan Anda terlalu sederhana dan mengarahkan orang untuk memberi Anda jawaban yang tidak membantu. Mungkin menggunakan variabel lebih dari sekali dalam string akan lebih demonstratif.
Wedge

Sebenarnya, kebingungan KHUSUS adalah penggunaan String.Format. Itu cocok untuk jawaban seperti saya, yang tidak membantu karena mereka tidak berorientasi variabel, tetapi akurat sejauh String.Format yang bersangkutan.
John Rudy

1
Panggilan ke String.Format jelas merupakan contoh yang dibuat-buat. Kecuali tentu saja Anda tidak menyadari bahwa memanggil String. Kesesuaian dengan elips tidak mungkin. Masalahnya adalah bahwa saya tidak menempatkan bahwa saya ingin pemformatan terjadi oleh parameter bernama daripada posisi, yang telah diperbaiki.
Jason Baker

FYI: Diserahkan ke Suara Pengguna MS Connect untuk meminta ini dijadikan fitur standar kerangka kerja. Bagi siapa pun yang tertarik, silakan upvote: visualstudio.uservoice.com/forums/121579-visual-studio/…
JohnLBevan

Jawaban:


130

Tidak ada metode bawaan untuk menangani ini.

Inilah satu metode

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

Ini satu lagi

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

Metode ketiga ditingkatkan sebagian berdasarkan pada dua di atas , dari Phil Haack


11
Saya sangat senang menggunakan FormatWith (), tetapi ingin menunjukkan masalah yang baru-baru ini saya temui. Implementasinya bergantung pada DataBinder dari System.Web.UI, yang tidak didukung dalam SQL CLR. Suntikan (o) tidak mengandalkan pengikat data, yang membuatnya berguna untuk penggantian multi-token di objek SQL CLR saya.
EBarr

1
Mungkin Anda dapat memperbarui kalimat pertama dari jawaban Anda. Interpolasi string hadir dalam C # dan VB selama beberapa bulan (akhirnya ...). Jawaban Anda ada di atas sehingga mungkin bermanfaat bagi pembaca jika Anda dapat menautkannya dengan beberapa sumber daya .NET yang diperbarui.
miroxlav

1
@miroxlav itu tidak benar-benar sama. Anda tidak dapat melewatkan string interpolasi di sekitar: stackoverflow.com/q/31987232/213725
DixonD

@DixonD - Anda pasti benar tetapi itu bukan tujuan mereka. Di T&J yang ditautkan, OP mencoba merujuk nama variabel sebelum nama itu ada. Bukan ide yang sangat bagus, tetapi jika seseorang bersikeras akan hal itu, ia dapat membuat parser khusus. Tapi saya tidak akan mengacaukan ini dengan konsep interpolasi string umum.
miroxlav

44

Saya memiliki implementasi yang baru saja saya posting ke blog saya di sini: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

Ini membahas beberapa masalah yang dimiliki oleh implementasi lain ini dengan penjepit melarikan diri. Posting memiliki detail. Itu DataBinder.Eval hal juga, tetapi masih sangat cepat.


3
Kode tersedia untuk diunduh dalam artikel 404 itu. Saya benar-benar ingin melihatnya juga.
quentin-starin

2
@ qes: Tautan yang diperbarui diposting di komentar: code.haacked.com/util/NamedStringFormatSolution.zip
Der Hochstapler

3
@OliverSalzburg: Saya telah menggunakan SmartFormat untuk semua kebutuhan pemformatan saya selama beberapa waktu sekarang, senanglah. github.com/scottrippey/SmartFormat
quentin-starin

@ qes: Bisakah Anda menulis dan menjawabnya dan menunjukkan cara kerjanya? Terlihat menarik
Der Hochstapler

@ qes: Anda harus menambahkan SmartFormat sebagai jawaban karena sangat bagus dan didukung secara aktif (2015).
Răzvan Flavius ​​Panda

42

String interpolasi ditambahkan ke C # 6.0 dan Visual Basic 14

Keduanya diperkenalkan melalui kompiler Roslyn baru di Visual Studio 2015 .

  • C # 6.0:

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

  • VB 14:

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

Fitur yang patut diperhatikan (dalam Visual Studio 2015 IDE):

  • pewarnaan sintaks didukung - variabel yang terkandung dalam string disorot
  • refactoring didukung - saat mengganti nama, variabel yang terkandung dalam string juga diganti namanya
  • sebenarnya bukan hanya nama variabel, tetapi ekspresi didukung - misalnya tidak hanya {index}berfungsi, tetapi juga{(index + 1).ToString().Trim()}

Nikmati! (& klik "Kirim Senyum" di VS)


2
Pertanyaan ini ditandai dengan .net 3.5 karena itu informasi Anda valid tetapi itu bukan alternatif
Douglas Gandini

1
@miroxlav - Anda benar tentang versi kerangka kerja. Interpolasi string hanya tergantung pada kompiler Roslyn baru yang digunakan dalam VS 2015.
Douglas Gandini

2
Ini juga tidak akan berfungsi kecuali string format Anda dimasukkan ke dalam kode itu sendiri. yaitu tidak akan berfungsi jika string format Anda berasal dari sumber eksternal, seperti file konfigurasi atau basis data.
Craig Brett

40

Anda juga dapat menggunakan jenis anonim seperti ini:

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

Tentu saja akan membutuhkan lebih banyak kode jika Anda juga ingin mem-parsing pemformatan, tetapi Anda dapat memformat string menggunakan fungsi ini seperti:

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

1
Sempurna bagi kita yang masih menggunakan 2.0. Ya, saya tahu .... Solusi ini mudah dan mudah dimengerti. DAN INI BEKERJA !!!
Brad Bruce

14

Tampaknya tidak ada cara untuk melakukan ini di luar kotak. Meskipun, tampaknya layak untuk menerapkan IFormatProvidertautan Anda sendiri ke IDictionarynilai untuk.

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

Output:

Python memiliki 2 jenis kutipan

Peringatannya adalah bahwa Anda tidak dapat mencampur FormatProviders, sehingga pemformatan teks mewah tidak dapat digunakan pada saat yang sama.


1
+1 untuk menguraikan, IMHO, metode konseptual terbaik, yang memiliki implementasi yang bagus di mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html - posting lain menyertakan ini, tetapi juga mengusulkan metode berbasis refleksi yang, IMHO, agak jahat
Adam Ralph

9

Kerangka itu sendiri tidak menyediakan cara untuk melakukan ini, tetapi Anda dapat melihat posting ini oleh Scott Hanselman. Contoh penggunaan:

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

Kode ini oleh James Newton-King serupa dan bekerja dengan sub-properti dan indeks,

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

Kode James bergantung pada System.Web.UI.DataBinder untuk mengurai string dan membutuhkan referensi System.Web, yang beberapa orang tidak suka melakukannya di aplikasi non-web.

EDIT: Oh dan mereka bekerja dengan baik dengan tipe anonim, jika Anda tidak memiliki objek dengan properti yang siap untuk itu:

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


4

Saya pikir yang paling dekat Anda akan dapatkan adalah format diindeks:

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

Ada juga String.Replace (), jika Anda bersedia melakukannya dalam beberapa langkah dan percaya bahwa Anda tidak akan menemukan 'variabel' Anda di tempat lain di string:

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

Memperluas ini untuk menggunakan Daftar:

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

Anda dapat melakukannya dengan Kamus <string, string> juga dengan mengulangi itu .Keys koleksi, tetapi dengan menggunakan Daftar <KeyValuePair <string, string >> kita dapat mengambil keuntungan dari metode .ForEach () Daftar dan mengembun kembali ke satu baris:

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

Lambda akan lebih sederhana, tapi saya masih menggunakan .Net 2.0. Perhatikan juga bahwa kinerja .Replace () tidak menjadi bintang ketika digunakan secara iteratif, karena string dalam .Net tidak dapat diubah. Juga, ini membutuhkan MyStringvariabel yang didefinisikan sedemikian rupa sehingga dapat diakses oleh delegasi, sehingga belum sempurna.


Yah, itu bukan solusi tercantik, tapi itulah yang akan saya lakukan untuk saat ini. Satu-satunya hal yang saya lakukan berbeda adalah menggunakan StringBuilder alih-alih string sehingga saya tidak terus membuat string baru.
Jason Baker

3

Pustaka sumber terbuka saya, Regextra , mendukung pemformatan bernama (antara lain). Saat ini menargetkan .NET 4.0+ dan tersedia di NuGet . Saya juga memiliki posting blog pengantar tentang hal itu: Regextra: membantu Anda mengurangi (masalah) Anda {2} .

Bit pemformatan yang dinamai mendukung:

  • Pemformatan dasar
  • Pemformatan properti bersarang
  • Pemformatan kamus
  • Melarikan diri dari pembatas
  • Pemformatan string Standar / Kustom / IFormatProvider

Contoh:

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

Hasil:

Kami baru saja mengirimkan pesanan Anda 'Widget', ditempatkan pada 28/02/2014. Kartu {kredit} Anda akan ditagih $ 1.500,00.

Lihatlah tautan GitHub proyek (di atas) dan wiki untuk contoh lainnya.


Wow, ini terlihat luar biasa, terutama ketika berhadapan dengan beberapa contoh format yang lebih sulit yang ditemui.
Nicholas Petersen

2

Periksa ini:

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

Sampel:

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

Performanya lumayan ok dibanding solusi lain.


1

Saya ragu ini akan mungkin. Hal pertama yang terlintas dalam pikiran adalah bagaimana Anda akan mendapatkan akses ke nama variabel lokal?

Mungkin ada beberapa cara pintar menggunakan ekspresi LINQ dan Lambda untuk melakukan ini.


@ leppie: +1 jika Anda dapat memberi saya beberapa LINQ + Lambda untuk melakukan itu; D (ok +1 untuk memiliki jawaban yang relevan)
user7116

Saya akan senang melihatnya juga! Mungkin saya akan menerima tantangan itu!
leppie

Saya pikir itu tidak mungkin dilakukan dengan nama variabel, tetapi taruh di sana kalau-kalau saya salah. :) Tidak ada cara untuk melakukan ini dengan kamus juga?
Jason Baker

Saya mencoba, dan mendapat sedikit tempat, tetapi saya menganggapnya terlalu jelek dan sulit untuk digunakan. Itu akan terlihat seperti: string s = format (f => f ("{hello} {world}", hello, world));
leppie

1

Ini yang saya buat beberapa waktu lalu. Itu memperluas String dengan metode Format mengambil argumen tunggal. Yang menyenangkan adalah ia akan menggunakan string standar. Sesuai jika Anda memberikan argumen sederhana seperti int, tetapi jika Anda menggunakan sesuatu seperti tipe anonim, itu juga akan berfungsi.

Contoh penggunaan:

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

Akan menghasilkan "Keluarga Smith memiliki 4 anak."

Itu tidak melakukan hal-hal yang mengikat gila seperti array dan pengindeks. Tapi itu super sederhana dan berkinerja tinggi.

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

Contoh:

var builder = new StringBuilder();
builder.AppendNamedFormat(
@"你好,{Name},今天是{Date:yyyy/MM/dd}, 这是你第{LoginTimes}次登录,积分{Score:{{ 0.00 }}}",
new Dictionary<string, object>() { 
    { "Name", "wayjet" },
    { "LoginTimes",18 },
    { "Score", 100.4 },
    { "Date",DateTime.Now }
});

Output: 你好, wayjet, 今天 是 2011-05-04, 这 是 你 第 18 次 登录 , 积分 {100.40}


1

di sini adalah metode sederhana untuk objek apa pun:

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

Dan di sini cara menggunakannya:

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

output: 2/27/2012


0

Saya menerapkan ini adalah kelas sederhana yang menduplikasi fungsi String.Format (kecuali saat menggunakan kelas). Anda bisa menggunakan kamus atau tipe untuk menentukan bidang.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 menambahkan fungsionalitas ini langsung ke spesifikasi bahasa, demikian NamedFormatStringjuga untuk kompatibilitas mundur.


0

Saya memecahkan ini dengan cara yang sedikit berbeda dengan solusi yang ada. Itu inti dari penggantian item bernama (bukan bit refleksi bahwa beberapa telah dilakukan). Ini sangat cepat dan sederhana ... Ini adalah solusi saya:

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

Ini digunakan dengan cara berikut:

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

Semoga seseorang menemukan ini berguna!


0

Meskipun jawaban yang diterima memberikan beberapa contoh yang baik, .Inject serta beberapa contoh Haack tidak menangani pelolosan. Banyak juga sangat bergantung pada Regex (lebih lambat), atau DataBinder.Eval yang tidak tersedia di .NET Core, dan di beberapa lingkungan lain.

Dengan pemikiran itu, saya telah menulis parser berbasis mesin sederhana yang mengalir melalui karakter, menulis ke StringBuilderoutput, karakter demi karakter. Ini diimplementasikan sebagai Stringmetode ekstensi dan dapat menggunakan parameter a Dictionary<string, object>atau objectdengan sebagai input (menggunakan refleksi).

Ini menangani level tak terbatas {{{escaping}}}dan melempar FormatExceptionketika input mengandung kawat gigi yang tidak seimbang dan / atau kesalahan lainnya.

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

Pada akhirnya, semua logika bermuara menjadi 10 status utama - Karena ketika mesin negara berada di luar braket dan juga di dalam braket, karakter selanjutnya adalah penjepit terbuka, penjepit terbuka keluar, penjepit tertutup, penjepit tertutup lolos, atau karakter biasa. Masing-masing kondisi ini ditangani secara individual saat loop berlanjut, menambahkan karakter ke output StringBufferatau kunci StringBuffer. Ketika parameter ditutup, nilai kunci StringBufferdigunakan untuk mencari nilai parameter dalam kamus, yang kemudian didorong ke output StringBuffer. Pada akhirnya, nilai output StringBufferdikembalikan.


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

Sunting: Yang seharusnya saya katakan adalah, "Tidak, saya tidak percaya apa yang ingin Anda lakukan didukung oleh C #. Ini sedekat yang Anda akan dapatkan."


1
Saya ingin tahu tentang suara turun. Adakah yang mau memberi tahu saya alasannya?
Kevin

1
Jadi string.format akan melakukan operasi ini 4 / Sepuluh Ribu detik lebih cepat Jika fungsi ini akan dipanggil satu ton Anda mungkin memperhatikan perbedaan itu. Tapi itu setidaknya menjawab pertanyaannya, bukan hanya menyuruhnya melakukannya dengan cara yang sama, dia sudah mengatakan dia tidak ingin melakukannya.
Kevin

4
Saya tidak memilih Anda, tetapi saya tidak akan menerapkan ini terutama karena, saya merasa melakukan banyak penggabungan string jelek. Tapi itu pandangan pribadi saya.
Jason Baker

Aneh bahwa ini turun begitu banyak. Pertimbangkan untuk memperluas jawaban Anda, bahwa ketika rangkaian tidak dipanggil sering Anda dapat mempertimbangkan "someString" + someVariable + "someOtherString"lebih mudah dibaca. Artikel ini setuju dengan Anda.
Steven Jeuris
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.