C#中的命名字符串格式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/159017/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 16:09:52  来源:igfitidea点击:

Named string formatting in C#

提问by Jason Baker

Is there any way to format a string by name rather than position in C#?

有没有办法按名称而不是 C# 中的位置来格式化字符串?

In python, I can do something like this example (shamelessly stolen from here):

在 python 中,我可以做类似这个例子的事情(无耻地从这里来的):

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

Is there any way to do this in C#? Say for instance:

有没有办法在 C# 中做到这一点?比如说:

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

Being able to do this using a variable name would be nice, but a dictionary is acceptable too.

能够使用变量名来做到这一点会很好,但字典也是可以接受的。

采纳答案by John Sheehan

There is no built-in method for handling this.

没有处理这个的内置方法。

Here's one method

这是一种方法

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

Here's another

这是另一个

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

A third improved method partially based on the two above, from Phil Haack

部分基于上述两种方法的第三种改进方法,来自 Phil Haack

回答by Joel Coehoorn

I think the closest you'll get is an indexed format:

我认为最接近的是索引格式:

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

There's also String.Replace(), if you're willing to do it in multiple steps and take it on faith that you won't find your 'variables' anywhere else in the string:

还有 String.Replace(),如果您愿意分多个步骤进行操作并相信您不会在字符串中的其他任何地方找到“变量”:

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

Expanding this to use a List:

扩展它以使用列表:

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

You could do that with a Dictionary<string, string> too by iterating it's .Keys collections, but by using a List<KeyValuePair<string, string>> we can take advantage of the List's .ForEach() method and condense it back to a one-liner:

您也可以通过迭代它的 .Keys 集合来使用 Dictionary<string, string> 来做到这一点,但是通过使用 List<KeyValuePair<string, string>> 我们可以利用 List 的 .ForEach() 方法并将其压缩回单线:

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

A lambda would be even simpler, but I'm still on .Net 2.0. Also note that the .Replace() performance isn't stellar when used iteratively, since strings in .Net are immutable. Also, this requires the MyStringvariable be defined in such a way that it's accessible to the delegate, so it's not perfect yet.

lambda 会更简单,但我仍在使用 .Net 2.0。还要注意 .Replace() 迭代使用时的性能并不出色,因为 .Net 中的字符串是不可变的。此外,这需要MyString以委托可以访问的方式定义变量,因此它还不完美。

回答by leppie

I doubt this will be possible. The first thing that comes to mind is how are you going to get access to local variable names?

我怀疑这是可能的。首先想到的是您将如何访问局部变量名称?

There might be some clever way using LINQ and Lambda expressions to do this however.

然而,使用 LINQ 和 Lambda 表达式可能有一些聪明的方法来做到这一点。

回答by Kevin

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

Edit: What I should have said was, "No, I don't believe what you want to do is supported by C#. This is as close as you are going to get."

编辑:我应该说的是,“不,我不相信 C# 支持您想做的事情。这与您将要得到的一样接近。”

回答by spoulson

There doesn't appear to be a way to do this out of the box. Though, it looks feasible to implement your own IFormatProviderthat links to an IDictionaryfor values.

似乎没有一种开箱即用的方法。不过,实现自己的IFormatProvider链接到IDictionaryfor 值看起来是可行的。

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

Outputs:

输出:

Python has 2 quote types

The caveat is that you can't mix FormatProviders, so the fancy text formatting can't be used at the same time.

需要注意的是,您不能混合使用FormatProviders,因此不能同时使用花哨的文本格式。

回答by Lucas

The framework itself does not provide a way to do this, but you can take a look at this postby Scott Hanselman. Example usage:

框架本身并没有提供实现此目的的方法,但您可以查看Scott Hanselman 的这篇文章。用法示例:

Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual(".43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo); 

This codeby James Newton-King is similar and works with sub-properties and indexes,

James Newton-King 的这段代码与子属性和索引类似,

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

James's code relies on System.Web.UI.DataBinderto parse the string and requires referencing System.Web, which some people don't like to do in non-web applications.

James 的代码依赖System.Web.UI.DataBinder来解析字符串,并且需要引用 System.Web,有些人不喜欢在非 Web 应用程序中这样做。

EDIT: Oh and they work nicely with anonymous types, if you don't have an object with properties ready for it:

编辑:哦,如果您没有准备好属性的对象,它们可以很好地处理匿名类型:

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

回答by Haacked

I have an implementation I just posted to my blog here: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

我有一个刚刚发布到我的博客的实现:http: //haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

It addresses some issues that these other implementations have with brace escaping. The post has details. It does the DataBinder.Eval thing too, but is still very fast.

它解决了这些其他实现在大括号转义方面的一些问题。该帖子有详细信息。它也做 DataBinder.Eval 的事情,但仍然非常快。

回答by Mark Cidade

See https://stackoverflow.com/questions/271398?page=2#358259

https://stackoverflow.com/questions/271398?page=2#358259

With the linked-to extension you can write this:

使用链接到扩展,你可以这样写:

var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());

and you'll get "foo 2 System.Object".

你会得到"foo 2 System.Object“。

回答by Doggett

You can also use anonymous types like this:

您还可以使用匿名类型,如下所示:

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

Of course it would require more code if you also want to parse formatting, but you can format a string using this function like:

当然,如果您还想解析格式,则需要更多代码,但您可以使用此函数来格式化字符串,例如:

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

回答by Steve Potter

Here's one I made a while back. It extends String with a Format method taking a single argument. The nice thing is that it'll use the standard string.Format if you provide a simple argument like an int, but if you use something like anonymous type it'll work too.

这是我前一段时间做的。它使用带单个参数的 Format 方法扩展 String 。好消息是,如果您提供像 int 这样的简单参数,它将使用标准 string.Format,但如果您使用匿名类型之类的东西,它也会起作用。

Example usage:

用法示例:

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

Would result in "The Smith family has 4 children."

将导致“史密斯家族有 4 个孩子”。

It doesn't do crazy binding stuff like arrays and indexers. But it is super simple and high performance.

它不会像数组和索引器那样疯狂地绑定东西。但它超级简单和高性能。

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


    }

}