การจัดรูปแบบสตริงที่มีชื่อใน C #


156

มีวิธีการจัดรูปแบบสตริงตามชื่อมากกว่าตำแหน่งใน C # หรือไม่?

ในงูหลามฉันสามารถทำบางอย่างเช่นตัวอย่างนี้ (ถูกขโมยลงคอจากที่นี่ ):

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

มีวิธีการทำเช่นนี้ใน C # หรือไม่? พูดเช่น:

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

ความสามารถในการทำเช่นนี้โดยใช้ชื่อตัวแปรน่าจะดี แต่พจนานุกรมก็เป็นที่ยอมรับเช่นกัน


ฉันพลาดเรื่องนี้จาก Ruby เช่นกัน
JesperE

ฉันคิดว่าตัวอย่างของคุณง่ายเกินไปและนำผู้คนมาให้คำตอบที่ไม่ช่วยเหลือคุณ บางทีการใช้ตัวแปรมากกว่าหนึ่งครั้งในสตริงจะเป็นการสาธิตที่มากกว่า
Wedge

ที่จริงแล้วความสับสนเฉพาะคือการใช้ String.Format ที่ให้ยืมตัวเองเพื่อตอบเช่นเหมืองซึ่งไม่เป็นประโยชน์เพราะพวกเขาไม่ได้มุ่งเน้นตัวแปร แต่มีความแม่นยำตราบเท่าที่ String.Format เป็นห่วง
John Rudy

1
การเรียกใช้งาน String.Format เป็นตัวอย่างที่ชัดเจน เว้นแต่คุณจะไม่ทราบว่าการโทร String.Format ด้วย ellipses เป็นไปไม่ได้ ปัญหาคือฉันไม่ได้ระบุว่าฉันต้องการให้การจัดรูปแบบเกิดขึ้นโดยพารามิเตอร์ที่มีชื่อแทนที่จะเป็นตำแหน่งซึ่งได้รับการแก้ไขแล้ว
เจสันเบเกอร์

FYI: ส่งไปยัง User Voice ของ MS Connect เพื่อขอสิ่งนี้ทำให้เป็นคุณสมบัติมาตรฐานของเฟรมเวิร์ก สำหรับผู้ที่สนใจกรุณา upvote: visualstudio.uservoice.com/forums/121579-visual-studio/
......

คำตอบ:


130

ไม่มีวิธีการในตัวสำหรับการจัดการสิ่งนี้

นี่คือวิธีหนึ่ง

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

นี่คืออีกหนึ่ง

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

วิธีการปรับปรุงที่สามบางส่วนขึ้นอยู่กับทั้งสองข้างต้นจาก Phil Haack


11
ฉันมีความสุขมากที่ใช้ FormatWith () แต่ต้องการชี้ปัญหาที่ฉันเพิ่งเจอ การใช้งานนั้นอาศัย DataBinder จาก System.Web.UI ซึ่งไม่ได้รับการสนับสนุนใน SQL CLR Inject (o) ไม่พึ่งพา data binder ซึ่งทำให้มีประโยชน์สำหรับ multi-token-replace ในวัตถุ SQL CLR ของฉัน
EBarr

1
บางทีคุณสามารถปรับปรุงประโยคแรกของคำตอบของคุณ การแก้ไขสตริงมีอยู่ใน C # และ VB เป็นเวลาสองสามเดือน (ในที่สุด ... ) คำตอบของคุณอยู่ที่ด้านบนดังนั้นจึงอาจเป็นประโยชน์สำหรับผู้อ่านหากคุณสามารถลิงก์ไปยังแหล่งข้อมูล. NET ที่ได้รับการปรับปรุงบางส่วน
miroxlav

1
@miroxlav มันไม่เหมือนกันจริงๆ คุณไม่สามารถส่งผ่านสายการแก้ไขรอบ ๆ : stackoverflow.com/q/31987232/213725
DixonD

@DixonD - คุณพูดถูก แต่มันไม่ใช่จุดประสงค์ของพวกเขา ในคำถามที่คุณเชื่อมโยง OP พยายามอ้างอิงชื่อตัวแปรก่อนที่จะมีอยู่ ไม่ใช่ความคิดที่ดีมาก แต่ถ้ามีคนยืนยันในเรื่องนั้นเขาสามารถสร้างโปรแกรมแยกวิเคราะห์พิเศษได้ แต่ฉันจะไม่ทำสิ่งนี้ด้วยแนวคิดการแก้ไขสตริงทั่วไป
miroxlav

44

ฉันมีการนำไปใช้ฉันเพิ่งโพสต์ไปยังบล็อกของฉันที่นี่: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

มันเน้นปัญหาบางอย่างที่การใช้งานอื่น ๆ เหล่านี้มีกับการหลบหนีรั้ง โพสต์มีรายละเอียด มันทำ DataBinder.Eval เหมือนกัน แต่ก็ยังเร็วมาก


3
รหัสสามารถดาวน์โหลดได้ในบทความ 404 ฉันก็อยากจะเห็นมันเช่นกัน
quentin-starin

2
@qes: ลิงก์ที่อัปเดตถูกโพสต์ในความคิดเห็น: code.haacked.com/util/NamedStringFormatSolution.zip
Der Hochstapler

3
@OliverSalzburg: ฉันเคยใช้ SmartFormat สำหรับทุกความต้องการในการจัดรูปแบบของฉันในขณะนี้รักมัน github.com/scottrippey/SmartFormat
quentin-starin

@qes: คุณคิดจะเขียนและตอบเกี่ยวกับมันและแสดงให้เห็นว่ามันทำงานอย่างไร? ดูน่าสนใจ
Der Hochstapler

@qes: คุณควรเพิ่ม SmartFormat เป็นคำตอบเพราะมันดีและสนับสนุนอย่างแข็งขัน (2015)
Răzvan Flavius ​​Panda

42

มีการเพิ่มสตริง Interpolatedลงใน C # 6.0 และ Visual Basic 14

ทั้งสองถูกแนะนำผ่านใหม่โรสลินคอมไพเลอร์ในVisual Studio 2015

คุณสมบัติที่สำคัญ (ใน Visual Studio 2015 IDE):

  • สนับสนุนการกำหนดสีไวยากรณ์ - ตัวแปรที่อยู่ในสตริงจะถูกเน้นไว้
  • refactoringได้รับการสนับสนุน - เมื่อเปลี่ยนชื่อตัวแปรที่มีอยู่ในสายได้รับการเปลี่ยนชื่อเกินไป
  • อันที่จริงไม่เพียง แต่ชื่อตัวแปรเท่านั้น แต่ยังรองรับการแสดงออกเช่นไม่เพียง แต่ใช้{index}งานได้เท่านั้น{(index + 1).ToString().Trim()}

สนุก! (& คลิก "ส่งยิ้ม" ใน VS)


2
คำถามถูกติดแท็กด้วย. net 3.5 ข้อมูลของคุณจึงถูกต้อง แต่ไม่ใช่ทางเลือก
Douglas Gandini

1
@miroxlav - คุณพูดถูกเกี่ยวกับเวอร์ชั่นของเฟรมเวิร์ก การแก้ไขสตริงนั้นขึ้นอยู่กับคอมไพเลอร์ Roslyn ใหม่ที่ใช้ใน VS 2015
ดักลาสคานนี่

2
สิ่งนี้จะไม่ทำงานเว้นแต่สตริงรูปแบบของคุณจะถูกใส่ลงในโค้ด เช่นจะไม่ทำงานหากสตริงรูปแบบของคุณมาจากแหล่งภายนอกเช่นไฟล์กำหนดค่าหรือฐานข้อมูล
Craig Brett

40

คุณสามารถใช้ประเภทที่ไม่ระบุชื่อเช่นนี้:

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

แน่นอนมันจะต้องใช้รหัสเพิ่มเติมถ้าคุณต้องการแยกการจัดรูปแบบ แต่คุณสามารถจัดรูปแบบสตริงโดยใช้ฟังก์ชั่นนี้เช่น:

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

1
เหมาะสำหรับพวกเราที่ยังคงอยู่บน 2.0 ใช่ฉันรู้ .... วิธีนี้เป็นวิธีที่ง่ายและเข้าใจง่าย และมันก็ทำงาน !!!
แบรดบรูซ

14

ดูเหมือนจะไม่มีทางทำสิ่งนี้ได้นอกกรอบ แม้ว่าจะเป็นไปได้ที่จะนำไปใช้กับIFormatProviderลิงก์ของคุณเพื่อIDictionaryหาค่า

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

ขาออก:

Python มีเครื่องหมายคำพูด 2 แบบ

ข้อแม้คือคุณไม่สามารถผสมFormatProvidersดังนั้นการจัดรูปแบบข้อความแฟนซีไม่สามารถใช้ในเวลาเดียวกัน


1
+1 สำหรับการสรุป, IMHO วิธีการแนวความคิดที่ดีที่สุดซึ่งมีการดำเนินงานที่ดีที่mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html - กระทู้อื่น ๆ รวมถึงนี้ แต่พวกเขายัง เสนอวิธีการที่ใช้การสะท้อนซึ่ง IMHO นั้นค่อนข้างชั่วร้าย
Adam Ralph

9

กรอบตัวเองไม่ได้ให้วิธีการทำเช่นนี้ แต่คุณสามารถดูโพสต์นี้โดย Scott Hanselman ตัวอย่างการใช้งาน:

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 มีความคล้ายคลึงและทำงานกับคุณสมบัติย่อยและดัชนี

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

รหัสของเจมส์อาศัยSystem.Web.UI.DataBinderเพื่อแยกสตริงและต้องอ้างอิง System.Web ซึ่งบางคนไม่ชอบที่จะทำในแอปพลิเคชันที่ไม่ใช่เว็บ

แก้ไข: โอ้และพวกเขาทำงานอย่างดีกับประเภทที่ไม่ระบุชื่อถ้าคุณไม่มีวัตถุที่มีคุณสมบัติพร้อมสำหรับมัน:

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


4

ฉันคิดว่าสิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับคือรูปแบบดัชนี:

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

นอกจากนี้ยังมี String.Replace () ถ้าคุณยินดีที่จะทำในหลายขั้นตอนและเชื่อมั่นว่าคุณจะไม่พบ 'ตัวแปร' ของคุณที่อื่นในสตริง:

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

การขยายเพื่อใช้รายการ:

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

คุณสามารถทำได้ด้วย Dictionary <string, string> ด้วยการวนซ้ำมันเป็น. คอลเลกชันคีย์ แต่โดยการใช้ List <KeyValuePair <string, string >> เราสามารถใช้ประโยชน์จากวิธีการ List .Each () ของรายการและย่อกลับเป็น ซับหนึ่ง:

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

แลมบ์ดาจะเรียบง่ายขึ้น แต่ฉันยังอยู่ใน. Net 2.0 นอกจากนี้โปรดทราบว่าประสิทธิภาพการทำงานของแทนที่ () ไม่ได้เป็นตัวเอกเมื่อใช้อย่างซ้ำ ๆ เนื่องจากสตริงใน. Net นั้นไม่เปลี่ยนรูป นอกจากนี้ต้องมีการMyStringกำหนดตัวแปรในแบบที่ผู้เข้าประชุมสามารถเข้าถึงได้ดังนั้นจึงยังไม่สมบูรณ์แบบ


นั่นไม่ใช่ทางออกที่ดีที่สุด แต่ตอนนี้ฉันจะไปด้วย สิ่งเดียวที่ฉันทำแตกต่างกันคือใช้ StringBuilder แทนที่จะเป็นสตริงเพื่อที่ฉันจะไม่สร้างสตริงใหม่
Jason Baker

3

Regextraไลบรารีโอเพ่นซอร์สของฉันรองรับการจัดรูปแบบชื่อ (เหนือสิ่งอื่นใด) ขณะนี้มันเป็นเป้า .NET 4.0 ขึ้นไปและสามารถใช้ได้บนNuGet ฉันยังมีโพสต์บล็อกเกริ่นนำเกี่ยวกับมัน: Regextra: ช่วยคุณลด (ปัญหา) {2} ของคุณ

บิตการจัดรูปแบบที่กำหนดชื่อรองรับ:

  • การจัดรูปแบบพื้นฐาน
  • การจัดรูปแบบคุณสมบัติซ้อน
  • การจัดรูปแบบพจนานุกรม
  • การหลบหลีกของตัวคั่น
  • การจัดรูปแบบสตริงมาตรฐาน / กำหนดเอง / IFormatProvider

ตัวอย่าง:

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

ผลลัพธ์:

เราเพิ่งส่งคำสั่งซื้อของคุณเป็น 'Widget' วางไว้เมื่อ 2/28/2014 บัตร {เครดิต} ของคุณจะถูกเรียกเก็บเงินจำนวน $ 1,500.00

ตรวจสอบลิงค์ GitHub ของโครงการ (ด้านบน) และ wiki สำหรับตัวอย่างอื่น ๆ


ว้าวสิ่งนี้ดูน่าทึ่งโดยเฉพาะอย่างยิ่งเมื่อต้องรับมือกับตัวอย่างรูปแบบที่ยากกว่าที่พบตัวอย่าง
Nicholas Petersen

2

เลือกอันนี้:

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

ตัวอย่าง:

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

ประสิทธิภาพค่อนข้างโอเคเมื่อเทียบกับโซลูชันอื่น


1

ฉันสงสัยว่ามันจะเป็นไปได้ สิ่งแรกที่ควรคำนึงถึงคือคุณจะเข้าถึงชื่อตัวแปรท้องถิ่นได้อย่างไร

อาจมีวิธีที่ชาญฉลาดในการใช้งาน LINQ และ Lambda เพื่อทำสิ่งนี้


@leppie: +1 ถ้าคุณสามารถให้ LINQ + Lambda ทำเช่นนั้นได้ D (ok +1 สำหรับคำตอบที่เกี่ยวข้อง)
user7116

ฉันชอบที่จะเห็นเช่นกัน! บางทีฉันอาจจะใช้ความท้าทายนั้น!
leppie

ฉันคิดว่ามันเป็นไปไม่ได้ที่จะทำกับชื่อตัวแปร แต่ใส่ไว้ในนั้นในกรณีที่ฉันผิด :) ไม่มีวิธีใดในการทำเช่นนี้กับพจนานุกรม?
Jason Baker

ฉันพยายามแล้วหาของเล็ก ๆ น้อย ๆ แต่ฉันคิดว่ามันน่าเกลียดและใช้ยากเกินไป ดูเหมือนว่าจะเป็น: รูปแบบสตริง s = (f => f ("{hello} {world}", สวัสดีโลก));
leppie

1

นี่คือสิ่งที่ฉันทำในขณะที่กลับ มันจะขยาย String ด้วยวิธีการจัดรูปแบบการโต้แย้งเดียว สิ่งที่ดีคือมันจะใช้สตริงมาตรฐานจัดรูปแบบถ้าคุณระบุอาร์กิวเมนต์อย่างง่ายเช่น int แต่ถ้าคุณใช้บางอย่างเช่นประเภทที่ไม่ระบุชื่อก็จะทำงานเช่นกัน

ตัวอย่างการใช้งาน:

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

จะส่งผลให้ "ครอบครัวสมิ ธ มีลูก 4 คน"

มันไม่ได้ทำสิ่งที่มีผลผูกพันบ้าเช่นอาร์เรย์และดัชนี แต่มันเป็นเรื่องง่ายและมีประสิทธิภาพสูง

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

ตัวอย่าง:

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

เอาต์พุต: 你好, wayjet, 今天是 2011-05-04, 这是你第 18 次登录, 积分 {100.40}


1

นี่เป็นวิธีง่าย ๆ สำหรับวัตถุใด ๆ :

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

และนี่คือวิธีการใช้งาน:

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

ผลลัพธ์: 2/27/2012


0

ฉันนำมาใช้นี้เป็นคลาสที่เรียบง่ายที่ซ้ำซ้อนการทำงานของ String.Format (ยกเว้นเมื่อใช้คลาส) คุณสามารถใช้พจนานุกรมหรือประเภทเพื่อกำหนดฟิลด์

https://github.com/SergueiFedorov/NamedFormatString

C # 6.0 กำลังเพิ่มฟังก์ชั่นนี้ลงในข้อมูลจำเพาะของภาษาดังนั้นจึงNamedFormatStringใช้งานร่วมกันได้ย้อนหลัง


0

ฉันแก้ไขสิ่งนี้ในวิธีที่แตกต่างออกไปเล็กน้อยสำหรับโซลูชันที่มีอยู่ มันเป็นแกนหลักของการแทนที่รายการที่มีชื่อ (ไม่ใช่บิตการสะท้อนที่บางคนทำ) มันเร็วและง่ายมาก ... นี่คือทางออกของฉัน:

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

มันถูกใช้ในวิธีต่อไปนี้:

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

หวังว่าใครบางคนพบว่ามีประโยชน์นี้!


0

แม้ว่าคำตอบที่ได้รับการยอมรับจะให้ตัวอย่างที่ดีบางอย่าง. Inject และตัวอย่างของ Haack บางตัวไม่สามารถหลบหนีได้ หลายคนพึ่งพาอย่างมากกับ Regex (ช้ากว่า) หรือ DataBinder.Eval ซึ่งไม่พร้อมใช้งานบน. NET Core และในบางสภาพแวดล้อมอื่น ๆ

ด้วยความที่อยู่ในใจฉันได้เขียนเครื่องมือแยกวิเคราะห์อย่างง่ายที่ใช้ลำธารผ่านตัวละครเขียนไปยังStringBuilderเอาต์พุตตัวละครแต่ละตัว มันถูกนำมาใช้เป็นStringวิธีการขยาย (s) และสามารถใช้ทั้งสองDictionary<string, object>หรือobjectมีพารามิเตอร์เป็นอินพุต (ใช้การสะท้อน)

มันจัดการระดับไม่ จำกัด{{{escaping}}}และพ่น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;
    }
}

ในท้ายที่สุดตรรกะทั้งหมดจะถูกแบ่งออกเป็น 10 สถานะหลัก - สำหรับเมื่อเครื่องของรัฐอยู่นอกวงเล็บและในวงเล็บเช่นเดียวกันอักขระถัดไปคือวงเล็บปีกกาเปิดวงเล็บปีกกาเปิดหนีวงเล็บปิดปิด หรือตัวละครธรรมดา แต่ละเงื่อนไขเหล่านี้จะถูกจัดการเป็นรายบุคคลเป็นความคืบหน้าห่วงการเพิ่มตัวละครทั้งการส่งออกหรือคีย์StringBuffer StringBufferเมื่อพารามิเตอร์ปิดค่าของคีย์ที่จะใช้ในการค้นหาค่าพารามิเตอร์ในพจนานุกรมที่แล้วได้รับการผลักดันเข้าสู่การส่งออกStringBuffer StringBufferในตอนท้ายค่าของผลลัพธ์StringBufferจะถูกส่งกลับ


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

แก้ไข: สิ่งที่ฉันควรจะพูดคือ "ไม่ฉันไม่เชื่อว่าสิ่งที่คุณต้องการจะได้รับการสนับสนุนโดย C # สิ่งนี้ใกล้เคียงกับที่คุณจะได้รับ"


1
ฉันอยากรู้เกี่ยวกับคะแนนโหวตลง ใครอยากบอกฉันว่าทำไม
Kevin

1
ดังนั้น string.format จะทำการดำเนินการนี้ 4 / สิบสามวินาทีได้เร็วขึ้นหากฟังก์ชั่นนี้จะถูกเรียกว่าตันคุณอาจสังเกตเห็นความแตกต่างนั้น แต่อย่างน้อยก็ตอบคำถามของเขาแทนที่จะบอกให้เขาทำแบบเดียวกับที่เขาบอกไปแล้วว่าเขาไม่ต้องการทำ
เควิน

4
ฉันไม่ได้ลงคะแนนให้คุณ แต่ฉันจะไม่ใช้สิ่งนี้เป็นส่วนใหญ่เพราะฉันพบว่าการเรียงสตริงจำนวนมากน่าเกลียด แต่นั่นเป็นมุมมองส่วนตัวของฉัน
Jason Baker

แปลกที่เรื่องนี้ลงคะแนนมาก พิจารณาการขยายคำตอบของคุณว่าเมื่อไม่มีการโทรเข้ามาบ่อยครั้งคุณอาจพิจารณาว่า"someString" + someVariable + "someOtherString"อ่านง่ายขึ้น บทความนี้เห็นด้วยกับคุณ
Steven Jeuris
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.