C # olarak adlandırılmış dize biçimlendirme


156

Bir dizeyi C # yerine adıyla biçimlendirmenin herhangi bir yolu var mı?

Python, bu örnek (utanmadan buradan çalındı ) gibi bir şey yapabilirim :

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

C # bunu yapmak için herhangi bir yolu var mı? Diyelim ki:

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

Bunu değişken bir ad kullanarak yapabilmek hoş olurdu, ancak sözlük de kabul edilebilir.


Bunu Ruby'den de özlüyorum.
JesperE

Bence örneğiniz çok basit ve insanları size yardımcı olmayan cevaplar vermeye yönlendiriyor. Belki bir değişkeni dizede bir kereden fazla kullanmak daha açıklayıcı olacaktır.
Kama

Aslında, SPECIFIC karışıklığı String.Format kullanımıdır. Bu, değişken gibi olmadıkları, ancak String.Format söz konusu olduğunda doğru olan yararlı olmayan benimki gibi cevaplara katkıda bulunur.
John Rudy

1
String.Format'a yapılan çağrı açıkça anlaşılır bir örnektir. Tabii ki Elips ile String.Format çağırmanın mümkün olmadığını bilmiyorsan. Sorun, biçimlendirmenin fiexed olan konum yerine adlandırılmış parametrelerle gerçekleşmesini istediğimi belirtmememdi.
Jason Baker

Bilginize: MS Connect Kullanıcı Sesi'ne gönderilerek bunun standart bir özellik haline getirilmesi istenmiştir. İlgilenenler için lütfen oy verin: visualstudio.uservoice.com/forums/121579-visual-studio/…
JohnLBevan

Yanıtlar:


130

Bunu ele almak için yerleşik bir yöntem yoktur.

İşte bir yöntem

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

İşte başka

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

Phil Haack'ten kısmen ikisine dayanan üçüncü geliştirilmiş yöntem


11
FormatWith () kullanarak çok mutlu oldum, ancak son zamanlarda karşılaştığım bir sorunu işaret etmek istedim. Uygulama DataBinder SQL CLR desteklenmeyen System.Web.UI dayanır. Inject (o) veri bağlayıcısına güvenmez, bu da SQL CLR nesnemdeki çoklu token değiştirme için yararlı kılar.
EBarr

1
Belki cevabınızın ilk cümlesini güncelleyebilirsiniz. Dize enterpolasyonu birkaç ay boyunca C # ve VB'de bulunur (sonunda ...). Cevabınız en üstte yer alıyor, bu nedenle okuyucuları güncelleştirilmiş bazı .NET kaynaklarına bağlayabilirseniz yararlı olabilir.
miroxlav

1
@miroxlav gerçekten aynı değil. Enterpolasyonlu dizeleri
buralara aktaramazsınız

@DixonD - kesinlikle haklısın ama amacı değildi. Bağladığınız Soru-Cevap bölümünde OP, değişken adına henüz var olmadan önce başvurmaya çalışır. Çok iyi bir fikir değil, ama birisi bunun üzerinde ısrar ederse, uzman ayrıştırıcı oluşturabilir. Ama bunu genel string enterpolasyon konseptiyle karıştırmam.
miroxlav

44

Blogumda az önce buraya gönderdiğim bir uygulama var: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

Bu diğer uygulamaların küme ayracı kaçışıyla ilgili bazı sorunlarına değinmektedir. Gönderinin ayrıntıları var. DataBinder.Eval şeyi de yapar, ancak yine de çok hızlı.


3
Bu makale 404'lerde indirilebilecek kod. Ben de görmek istiyorum.
quentin-starin

2
@qes: Yorumlarda güncellenmiş bir bağlantı yayınlanmıştır: code.haacked.com/util/NamedStringFormatSolution.zip
Der

3
@OliverSalzburg: SmartFormat'ı bir süredir tüm biçimlendirme ihtiyaçlarım için kullanıyorum, sevdim. github.com/scottrippey/SmartFormat
quentin-

@qes: Bu konuda yazıp cevaplayıp nasıl çalıştığını gösterebilir misiniz? İlginç görünüyor
Der Hochstapler

@qes: SmartFormat'ı kesinlikle çok güzel ve aktif olarak desteklendiği için kesinlikle cevap olarak eklemelisiniz (2015).
Răzvan Flavius ​​Panda

42

Enterpolasyonlu dizeler C # 6.0 ve Visual Basic 14'e eklendi

Her ikisi de Visual Studio 2015'te yeni Roslyn derleyicisi aracılığıyla tanıtıldı .

  • C # 6.0:

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

  • VB 14:

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

Dikkate değer özellikler (Visual Studio 2015 IDE'de):

  • sözdizimi renklendirme desteklenir - dizelerde yer alan değişkenler vurgulanır
  • yeniden düzenleme desteklenir - yeniden adlandırma sırasında, dizelerde bulunan değişkenler de yeniden adlandırılır
  • aslında sadece değişken isimler değil, ifadeler de desteklenir - örneğin sadece {index}çalışır değil , aynı zamanda{(index + 1).ToString().Trim()}

Zevk almak! (& VS'de "Gülümseme Gönder" i tıklayın)


2
Soru .net 3.5 ile etiketlenmiştir, bu nedenle bilgileriniz geçerlidir, ancak bu bir alternatif değildir
Douglas Gandini

1
@miroxlav - Çerçeve sürümü konusunda haklısınız. Dize enterpolasyonu, VS 2015'te kullanılan yeni Roslyn derleyicisine bağlıdır.
Douglas Gandini

2
Biçim dizeniz kodun kendisine yerleştirilmediği sürece bu da çalışmaz. yani biçim dizeniz bir yapılandırma dosyası veya veritabanı gibi harici bir kaynaktan geliyorsa çalışmaz.
Craig Brett

40

Bunun gibi anonim türleri de kullanabilirsiniz:

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

Elbette biçimlendirmeyi ayrıştırmak istiyorsanız daha fazla kod gerektirir, ancak bu işlevi kullanarak bir dizeyi biçimlendirebilirsiniz:

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

1
Hala 2.0 için olanlar için mükemmel. Evet, biliyorum ... Bu çözüm basit ve anlaşılması kolaydır. VE ÇALIŞIYOR!!!
Brad Bruce

14

Bunu kutudan çıkarmanın bir yolu yok gibi görünüyor. Yine de, IFormatProviderbir IDictionarydeğerlere bağlantı veren kendi uygulamanızı uygulamak mümkün görünmektedir .

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

Çıktılar:

Python, 2 teklif tipine sahiptir

Uyarı, karıştıramamanızdır FormatProviders, bu nedenle süslü metin biçimlendirmesi aynı anda kullanılamaz.


1
Anahat için +1, IMHO, mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html adresinde güzel bir uygulamaya sahip olan en iyi kavramsal yöntemdir - diğer gönderiler bunu içerir, ancak bunlar IMHO'nun oldukça kötü olduğu yansıma temelli yöntemleri önermek
Adam Ralph

9

Çerçevenin kendisi bunu yapmanın bir yolunu sağlamaz, ancak Scott Hanselman'ın bu yazısına bir göz atabilirsiniz . Örnek kullanım:

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

James Newton-King'in bu kodu benzerdir ve alt özellikler ve dizinlerle çalışır,

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

James'in kodu , dizeyi ayrıştırmak için System.Web.UI.DataBinder'a dayanır ve bazı kişilerin web dışı uygulamalarda yapmak istemediği System.Web'e başvurmayı gerektirir.

EDIT: Ah ve onlar için hazır özelliklere sahip bir nesne yoksa, anonim türleri ile güzel çalışır:

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


4

Bence en yakın dizine alınmış bir biçim olacaktır:

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

Ayrıca, birden fazla adımda yapmaya ve dizede başka bir yerde 'değişkenlerinizi bulamayacağınıza inanıyorsanız, String.Replace () de vardır:

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

Liste kullanmak için bunu genişletme:

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

Bunu Sözlük <string, string> ile de yapabilirsiniz. bir astar:

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

Bir lambda daha da basit olurdu, ama hala .Net 2.0'dayım. Ayrıca .Net'teki dizeler değişmez olduğu için .Replace () performansının yinelemeli olarak kullanıldığında yıldız olmadığını unutmayın. Ayrıca, bu, MyStringdeğişkenin temsilci tarafından erişilebilir olacak şekilde tanımlanmasını gerektirir, bu yüzden henüz mükemmel değildir.


Bu en güzel çözüm değil, ama şimdilik bununla devam ediyorum. Farklı yaptığım tek şey, bir dize yerine bir StringBuilder kullanmak oldu, böylece yeni dizeleri yapmaya devam etmeyin.
Jason Baker

3

Açık kaynak kütüphanem Regextra , adlandırılmış biçimlendirmeyi (diğer şeylerin yanı sıra) destekler. Şu anda .NET 4.0 ve sonraki sürümlerini hedeflemektedir ve NuGet'te kullanılabilir . Ayrıca bununla ilgili tanıtıcı bir blog yayınım var: Regextra: {2} sorununuzu azaltmanıza yardımcı oluyor .

Adlandırılmış biçimlendirme biti şunları destekler:

  • Temel biçimlendirme
  • Yuvalanmış özellikler biçimlendirme
  • Sözlük biçimlendirme
  • Sınırlayıcılardan kaçma
  • Standart / Özel / IFormatProvider dize biçimlendirmesi

Misal:

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

Sonuç:

28.02.2014 tarihinde verilen 'Widget' siparişinizi gönderdik. {Credit} kartınıza 1,500,00 ABD doları fatura edilecektir.

Diğer örnekler için projenin GitHub bağlantısına (yukarıda) ve wiki'ye göz atın.


Vay be, bu, özellikle bir kişinin karşılaştığı daha zor format örnekleriyle uğraşırken şaşırtıcı görünüyor.
Nicholas Petersen

2

Bunu kontrol et:

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

Örneklem:

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

Performans diğer çözümlere kıyasla oldukça iyi.


1

Bunun mümkün olacağından şüpheliyim. Akla ilk gelen şey, yerel değişken adlarına nasıl erişeceğinizdir?

Ancak bunu yapmak için LINQ ve Lambda ifadelerini kullanmanın akıllıca bir yolu olabilir.


@ leppie: Bunu yapmak için bana biraz LINQ + Lambda verebilirseniz +1; D (ilgili bir cevaba sahip olmak için +1)
user7116

Ben de görmek isterim! Belki de bu mücadeleyi üstleneceğim!
leppie

Değişken isimleriyle yapmanın imkansız olacağını düşündüm, ancak yanlış olduğumda bunu koydum. :) Bunu sözlükle yapmanın da bir yolu yok mu?
Jason Baker

Denedim ve biraz bir yerim var, ama çok çirkin ve kullanımı zor gördüm. Şuna benzer olurdu: string s = format (f => f ("{merhaba} {dünya}", merhaba, dünya));
leppie

1

İşte bir süre önce yaptığım. String'i tek bir argüman alarak bir Format yöntemiyle genişletir. Güzel bir şey, int gibi basit bir argüman sağlarsanız standart string.Format kullanmasıdır, ancak anonim tip gibi bir şey kullanırsanız da çalışır.

Örnek kullanım:

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

"Smith ailesinin 4 çocuğu var."

Diziler ve dizinleyiciler gibi çılgın bağlayıcı şeyler yapmaz. Ama süper basit ve yüksek performans.

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

Misal:

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

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


1

İşte herhangi bir nesne için basit bir yöntem:

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

Ve burada nasıl kullanılır:

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

çıktı: 2/27/2012


0

Bu String.Format işlevselliğini çoğaltır basit bir sınıftır (sınıfları kullanırken hariç). Alanları tanımlamak için bir sözlük veya bir tür kullanabilirsiniz.

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 bu işlevselliği doğrudan dil spesifikasyonuna ekliyor, bu yüzden NamedFormatStringgeriye dönük uyumluluk için.


0

Bunu mevcut çözümlerden biraz farklı bir şekilde çözdüm. Adlandırılmış öğe değişiminin çekirdeğini yapar (bazılarının yaptığı yansıma bitini değil). Son derece hızlı ve basit ... Bu benim çözümüm:

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

Aşağıdaki şekilde kullanılır:

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

Umarım birisi bunu faydalı bulur!


0

Kabul edilen cevap bazı iyi örnekler verse de, .Inject ve bazı Haack örnekleri kaçmayı ele almaz. Birçoğu ayrıca, .NET Core'da ve diğer bazı ortamlarda bulunmayan Regex (yavaş) veya DataBinder.Eval'e büyük ölçüde güvenir.

Bunu göz önünde bulundurarak, karakterler arasında akış, StringBuilderçıktıya yazma , karakter karakter ile basit bir durum makine tabanlı ayrıştırıcı yazdım . Bu şekilde uygulanan Stringuzatma yöntemi (s) ve her ikisi de sunar Dictionary<string, object>veobject (yansıma kullanarak) girdi olarak parametrelerle.

Giriş, dengesiz diş telleri ve / veya diğer hatalar içerdiğinde sınırsız düzeyde işler {{{escaping}}}ve atar FormatException.

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

Nihayetinde, tüm mantık 10 ana duruma kaynar - Durum makinesi bir braketin dışında ve aynı şekilde bir braketin içinde olduğunda, bir sonraki karakter ya açık bir küme ayracı, kaçan bir açık küme ayracı, kapalı bir küme ayracı, kaçan bir kapalı küme ayracı, ya da sıradan bir karakter. Döngü ilerledikçe bu koşulların her biri ayrı ayrı işlenir ve bir çıktıya StringBufferveya bir tuşa karakterler eklenir StringBuffer. Bir parametre kapatıldığında, anahtarın StringBufferdeğeri parametrenin sözlükteki değerini aramak için kullanılır ve daha sonra çıktıya aktarılır StringBuffer. Sonunda, çıktının StringBufferdeğeri döndürülür.


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

Düzenleme: Söylemeliydim, "Hayır, ne yapmak istediğine inanmıyorum C # tarafından destekleniyor. Bu alacak kadar yakın."


1
Aşağı oyları merak ediyorum. Bana nedenini söylemek isteyen var mı?
Kevin

1
Yani string.format bu işlemi bir saniyenin 4 / Onbininde biri daha hızlı gerçekleştirecektir. Bu işlev ton olarak adlandırılacaksa, bu farkı fark edebilirsiniz. Ama en azından sorusuna, sadece bunu yapmak istemediğini söylediği gibi yapmasını söylemek yerine cevaplıyor.
Kevin

4
Sana oy vermedim, ama bunu esas olarak uygulamazdım çünkü çirkin çok sayıda dize birleştirmesi yapıyorum. Ama bu benim kişisel görüşüm.
Jason Baker

Bu çok düşmüş olması çok garipti. Yanıtınızı genişletmeyi düşünün, birleştirme sık sık çağrılmadığında "someString" + someVariable + "someOtherString"daha okunabilir olarak değerlendirebilirsiniz. Bu makale sizinle aynı fikirde.
Steven Jeuris
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.