Định dạng chuỗi được đặt tên trong C #


156

Có cách nào để định dạng một chuỗi theo tên thay vì vị trí trong C # không?

Trong python, tôi có thể làm một cái gì đó như ví dụ này (bị đánh cắp một cách đáng xấu hổ từ đây ):

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

Có cách nào để làm điều này trong C # không? Nói ví dụ:

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

Có thể làm điều này bằng cách sử dụng một tên biến sẽ tốt, nhưng từ điển cũng được chấp nhận.


Tôi cũng đang thiếu điều này từ Ruby.
JesperE

Tôi nghĩ rằng ví dụ của bạn quá đơn giản và đang dẫn dắt mọi người đưa ra cho bạn những câu trả lời không có ích. Có lẽ việc sử dụng một biến nhiều hơn một lần trong chuỗi sẽ mang tính biểu thị nhiều hơn.
Nêm

Trên thực tế, sự nhầm lẫn CỤ THỂ là việc sử dụng String.Format. Điều đó dựa vào các câu trả lời như của tôi, không hữu ích vì chúng không được định hướng thay đổi, nhưng lại chính xác khi có liên quan như String.Format.
John Rudy

1
Cuộc gọi đến String.Format rõ ràng là một ví dụ giả định. Tất nhiên trừ khi bạn không biết rằng việc gọi String.Format bằng dấu chấm lửng là không thể. Vấn đề là tôi đã không đặt rằng tôi muốn định dạng xảy ra bởi các tham số được đặt tên thay vì vị trí, đã bị lỗi.
Jason Baker

FYI: Được gửi tới Giọng nói người dùng của MS Connect để yêu cầu điều này trở thành một tính năng tiêu chuẩn của khung. Đối với bất cứ ai quan tâm, xin vui lòng upvote: visualstudio.uservoice.com/forums/121579-visual-studio/...
JohnLBevan

Câu trả lời:


130

Không có phương pháp tích hợp để xử lý việc này.

Đây là một phương pháp

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

Đây là một

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

Phương pháp cải tiến thứ ba một phần dựa trên hai phương pháp trên , từ Phil Haack


11
Tôi đã rất vui khi sử dụng FormatWith (), nhưng muốn chỉ ra một vấn đề mà tôi mới gặp. Việc triển khai dựa trên DataBinder từ System.Web.UI, không được hỗ trợ trong SQL CLR. Tiêm (o) không phụ thuộc vào chất kết dính dữ liệu, điều này làm cho nó hữu ích cho việc thay thế nhiều mã thông báo trong đối tượng SQL CLR của tôi.
EBarr

1
Có lẽ bạn có thể cập nhật câu đầu tiên của câu trả lời của bạn. Nội suy chuỗi có mặt trong C # và VB trong vài tháng (cuối cùng ...). Câu trả lời của bạn ở trên cùng vì vậy nó có thể hữu ích cho người đọc nếu bạn có thể liên kết chúng với một số tài nguyên .NET được cập nhật.
miroxlav 24/2/2015

1
@miroxlav nó không thực sự giống nhau. Bạn không thể vượt qua các chuỗi nội suy xung quanh: stackoverflow.com/q/31987232/213725
DixonD

@DixonD - bạn chắc chắn đúng nhưng đó không phải là mục đích của họ. Trong Q & A bạn đã liên kết, OP cố gắng tham chiếu tên biến trước khi nó tồn tại. Ý tưởng không hay lắm, nhưng nếu ai đó khăng khăng về điều đó, anh ta có thể xây dựng trình phân tích cú pháp chuyên dụng. Nhưng tôi sẽ không làm hỏng điều này với khái niệm nội suy chuỗi chung.
miroxlav

44

Tôi có một triển khai tôi vừa đăng lên blog của mình ở đây: http://haacked.com/archive/2009/01/04/fun-with-named-formats-opes-parsing-and-edge-case.aspx

Nó giải quyết một số vấn đề mà các triển khai khác có với niềng răng thoát. Bài viết có chi tiết. Nó cũng làm điều DataBinder.Eval, nhưng vẫn rất nhanh.


3
Mã có sẵn để tải xuống trong bài viết 404 đó. Tôi cũng rất muốn xem nó.
quentin-starin

2
@qes: Một liên kết cập nhật đã được đăng trong các bình luận: code.haacked.com/util/NamedStringFormatSolution.zip
Der Hochstapler

3
@OliverSalzburg: Bây giờ tôi đã sử dụng SmartFormat cho tất cả các nhu cầu định dạng của mình, yêu thích nó. github.com/scottrippey/SmartFormat
quentin-starin

@qes: Bạn có phiền có thể viết và trả lời về nó và cho thấy nó hoạt động như thế nào không? Trông thật thú vị
Der Hochstapler

@qes: Bạn chắc chắn nên thêm SmartFormat làm câu trả lời vì nó rất hay và được hỗ trợ tích cực (2015).
Răzvan Flavius ​​Panda

42

Chuỗi nội suy đã được thêm vào C # 6.0 và Visual Basic 14

Cả hai đều được giới thiệu thông qua trình biên dịch Roslyn mới trong Visual Studio 2015 .

  • C # 6.0:

    return "\{someVariable} and also \{someOtherVariable}" HOẶC LÀ
    return $"{someVariable} and also {someOtherVariable}"

  • VB 14:

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

Các tính năng đáng chú ý (trong Visual Studio 2015 IDE):

  • tô màu cú pháp được hỗ trợ - các biến có trong chuỗi được tô sáng
  • tái cấu trúc được hỗ trợ - khi đổi tên, các biến có trong chuỗi cũng được đổi tên
  • thực tế không chỉ tên biến, mà các biểu thức được hỗ trợ - ví dụ: không chỉ {index}hoạt động mà còn{(index + 1).ToString().Trim()}

Thưởng thức! (& nhấp vào "Gửi nụ cười" trong VS)


2
Câu hỏi được gắn thẻ .net 3.5 do đó thông tin của bạn hợp lệ nhưng không phải là câu trả lời
Douglas Gandini

1
@miroxlav - Bạn nói đúng về phiên bản khung. Nội suy chuỗi chỉ phụ thuộc vào trình biên dịch Roslyn mới được sử dụng trong VS 2015.
Douglas Gandini

2
Điều này cũng sẽ không hoạt động trừ khi chuỗi định dạng của bạn được đặt vào chính mã. tức là nó sẽ không hoạt động nếu chuỗi định dạng của bạn đến từ nguồn bên ngoài, như tệp cấu hình hoặc cơ sở dữ liệu.
Craig Brett

40

Bạn cũng có thể sử dụng các loại ẩn danh như thế này:

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

Tất nhiên nó sẽ yêu cầu nhiều mã hơn nếu bạn cũng muốn phân tích định dạng, nhưng bạn có thể định dạng một chuỗi bằng hàm này như:

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

1
Hoàn hảo cho những người trong chúng ta vẫn còn trên 2.0. Vâng, tôi biết .... Giải pháp này rất đơn giản và dễ hiểu. VÀ NÓ HOẠT ĐỘNG!!!
Brad Bruce

14

Dường như không có cách nào để làm điều này ra khỏi hộp. Mặc dù, có vẻ khả thi để thực hiện của riêng bạn IFormatProviderliên kết đến một IDictionarygiá trị.

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

Đầu ra:

Python có 2 loại trích dẫn

Thông báo trước là bạn không thể trộn FormatProviders, do đó, định dạng văn bản ưa thích không thể được sử dụng cùng một lúc.


1
+1 cho phác thảo, IMHO, phương pháp khái niệm tốt nhất, có triển khai tốt tại mo.notono.us/2008/07/c-opesinject-format-strings-by-key.html - các bài đăng khác bao gồm điều này nhưng chúng cũng bao gồm đề xuất các phương pháp dựa trên sự phản ánh, IMHO, khá xấu xa
Adam Ralph

9

Khung chính nó không cung cấp một cách để làm điều này, nhưng bạn có thể xem bài đăng này của Scott Hanselman. Ví dụ sử dụng:

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

Mã này của James Newton-King tương tự và hoạt động với các thuộc tính và chỉ mục phụ,

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

Mã của James dựa vào System.Web.UI.DataBinder để phân tích chuỗi và yêu cầu tham chiếu System.Web, điều mà một số người không muốn làm trong các ứng dụng không phải web.

EDIT: Ồ và chúng hoạt động độc đáo với các loại ẩn danh, nếu bạn không có đối tượng với các thuộc tính sẵn sàng cho nó:

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


4

Tôi nghĩ rằng gần nhất bạn sẽ nhận được là một định dạng được lập chỉ mục:

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

Ngoài ra còn có String.Replace (), nếu bạn sẵn sàng thực hiện theo nhiều bước và tin chắc rằng bạn sẽ không tìm thấy 'biến' của mình ở bất kỳ nơi nào khác trong chuỗi:

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

Mở rộng này để sử dụng Danh sách:

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

Bạn cũng có thể làm điều đó với Từ điển <chuỗi, chuỗi> bằng cách lặp lại các bộ sưu tập .Keys của nó, nhưng bằng cách sử dụng Danh sách <KeyValuePair <chuỗi, chuỗi >> chúng ta có thể tận dụng phương thức Listororach () của List một lớp lót:

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

Một lambda thậm chí còn đơn giản hơn, nhưng tôi vẫn đang sử dụng .Net 2.0. Cũng lưu ý rằng hiệu suất .Replace () không xuất sắc khi được sử dụng lặp đi lặp lại, vì các chuỗi trong .Net là bất biến. Ngoài ra, điều này đòi hỏi MyStringbiến phải được xác định theo cách mà đại biểu có thể truy cập được, vì vậy nó chưa hoàn hảo.


Chà, đó không phải là giải pháp đẹp nhất, nhưng bây giờ tôi đang làm gì. Điều duy nhất tôi đã làm khác là sử dụng StringBuilder thay vì chuỗi để tôi không tiếp tục tạo chuỗi mới.
Jason Baker

3

Thư viện mã nguồn mở của tôi, Regextra , hỗ trợ định dạng được đặt tên (trong số những thứ khác). Nó hiện nhắm mục tiêu .NET 4.0+ và có sẵn trên NuGet . Tôi cũng có một bài đăng blog giới thiệu về nó: Regextra: giúp bạn giảm (vấn đề) {2} .

Các bit định dạng được đặt tên hỗ trợ:

  • Định dạng cơ bản
  • Định dạng thuộc tính lồng nhau
  • Định dạng từ điển
  • Thoát khỏi dấu phân cách
  • Định dạng chuỗi tiêu chuẩn / tùy chỉnh / IFormatProvider

Thí dụ:

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

Kết quả:

Chúng tôi vừa giao đơn đặt hàng 'Widget', được đặt vào ngày 28/2/2014. Thẻ {credit} của bạn sẽ được lập hóa đơn $ 1.500.

Kiểm tra liên kết GitHub của dự án (ở trên) và wiki để biết các ví dụ khác.


Wow, điều này có vẻ tuyệt vời, đặc biệt là khi xử lý một số ví dụ định dạng khó hơn mà người ta bắt gặp.
Nicholas Petersen

2

Kiểm tra cái này:

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

Mẫu vật:

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

Hiệu suất là khá ok so với các giải pháp khác.


1

Tôi nghi ngờ điều này sẽ có thể. Điều đầu tiên bạn nghĩ đến là làm thế nào bạn có thể truy cập vào các tên biến cục bộ?

Tuy nhiên, có thể có một số cách thông minh bằng cách sử dụng biểu thức LINQ và Lambda để làm điều này.


@leppie: +1 nếu bạn có thể cho tôi một số LINQ + Lambda để làm điều đó; D (ok +1 để có câu trả lời phù hợp)
user7116

Tôi cũng thích nhìn thấy nó! Có lẽ tôi sẽ chấp nhận thử thách đó!
leppie

Tôi đoán rằng sẽ không thể thực hiện được với các tên biến, nhưng đặt nó trong đó trong trường hợp tôi sai. :) Không có cách nào để làm điều này với một từ điển?
Jason Baker

Tôi đã thử, và có một chút ở đâu đó, nhưng tôi cho rằng nó quá xấu và khó sử dụng. Nó sẽ trông giống như: chuỗi s = format (f => f ("{hello} {world}", xin chào, thế giới));
leppie

1

Đây là một trong những tôi đã trở lại một thời gian. Nó mở rộng String với một phương thức Format lấy một đối số. Điều tuyệt vời là nó sẽ sử dụng chuỗi tiêu chuẩn. Định dạng nếu bạn cung cấp một đối số đơn giản như int, nhưng nếu bạn sử dụng một cái gì đó như kiểu ẩn danh thì nó cũng sẽ hoạt động.

Ví dụ sử dụng:

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

Sẽ dẫn đến "Gia đình Smith có 4 đứa con."

Nó không làm những thứ ràng buộc điên rồ như mảng và bộ chỉ mục. Nhưng nó là siêu đơn giản và hiệu suất cao.

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

Thí dụ:

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

Đầu ra:, ​​wayjet, 今天 是 2011-05-04, 这 是 你 18 次 登录 {100.40}


1

đây là một phương pháp đơn giản cho mọi đối tượng:

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

Và đây là cách sử dụng nó:

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

đầu ra: 27/2/2012


0

Tôi đã triển khai đây là một lớp đơn giản sao chép chức năng của String.Format (ngoại trừ khi sử dụng các lớp). Bạn có thể sử dụng từ điển hoặc loại để xác định các trường.

https://github.com/SergueiFedorov/ NamedFormatString

C # 6.0 đang thêm chức năng này ngay vào thông số ngôn ngữ, do đó, để NamedFormatStringtương thích ngược.


0

Tôi đã giải quyết điều này theo một cách hơi khác với các giải pháp hiện có. Nó thực hiện cốt lõi của sự thay thế vật phẩm được đặt tên (không phải là bit phản chiếu mà một số người đã làm). Nó cực kỳ nhanh chóng và đơn giản ... Đây là giải pháp của tôi:

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

Nó được sử dụng theo cách sau:

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

Hy vọng ai đó thấy điều này hữu ích!


0

Mặc dù câu trả lời được chấp nhận đưa ra một số ví dụ hay, nhưng .Inject cũng như một số ví dụ về Haack không xử lý thoát. Nhiều người cũng phụ thuộc rất nhiều vào Regex (chậm hơn) hoặc DataBinder.Eval không có trên .NET Core và trong một số môi trường khác.

Với ý nghĩ đó, tôi đã viết một trình phân tích cú pháp dựa trên máy trạng thái đơn giản để truyền qua các ký tự, viết thành StringBuilderđầu ra, ký tự theo ký tự. Nó được triển khai như là Stringphương thức mở rộng và có thể lấy cả một Dictionary<string, object>hoặcobject với các tham số làm đầu vào (sử dụng sự phản chiếu).

Nó xử lý các mức {{{escaping}}}và ném không giới hạn FormatExceptionkhi đầu vào chứa các dấu ngoặc không cân bằng và / hoặc các lỗi khác.

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

Cuối cùng, tất cả logic đều biến thành 10 trạng thái chính - Vì khi máy trạng thái nằm ngoài một giá đỡ và tương tự bên trong một giá đỡ, ký tự tiếp theo là một nẹp mở, một nẹp mở thoát, một nẹp đóng, một nẹp đóng thoát, hoặc một nhân vật bình thường. Mỗi điều kiện này được xử lý riêng lẻ khi vòng lặp tiến triển, thêm ký tự vào đầu ra StringBufferhoặc khóa StringBuffer. Khi một tham số được đóng, giá trị của khóa StringBufferđược sử dụng để tra cứu giá trị của tham số trong từ điển, sau đó được đẩy vào đầu ra StringBuffer. Cuối cùng, giá trị của đầu ra StringBufferđược trả về.


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

Chỉnh sửa: Điều tôi nên nói là "Không, tôi không tin những gì bạn muốn làm được hỗ trợ bởi C #. Điều này gần giống như bạn sẽ nhận được."


1
Tôi tò mò về số phiếu giảm. Có ai muốn nói với tôi tại sao không?
Kevin

1
Vì vậy, chuỗi.format sẽ thực hiện thao tác này 4 / Mười giây một giây nhanh hơn Nếu chức năng này sẽ được gọi là tấn bạn có thể nhận thấy sự khác biệt đó. Nhưng ít nhất nó trả lời câu hỏi của anh ta thay vì chỉ bảo anh ta làm theo cách mà anh ta đã nói rằng anh ta không muốn làm điều đó.
Kevin

4
Tôi đã không bỏ phiếu cho bạn, nhưng tôi sẽ không thực hiện điều này chủ yếu bởi vì tốt, tôi thấy làm nhiều việc nối chuỗi xấu xí. Nhưng đó là quan điểm cá nhân của tôi.
Jason Baker

Thật kỳ lạ khi điều này đã bỏ phiếu rất nhiều. Cân nhắc mở rộng câu trả lời của bạn, rằng khi kết nối không được gọi thường xuyên, bạn có thể xem xét "someString" + someVariable + "someOtherString"dễ đọc hơn. Bài viết này đồng ý với bạn.
Steven Jeuris
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.