命名为String.Format,这个可能吗?

37

我想使用{title}代替{0} {1}等,然后以某种方式填充数据(下面我使用了一个Dictionary)。但是这段代码无效并引发异常。我想知道是否可以做类似于我想要的东西。使用{0 .. N}不是问题,我只是好奇。

Dictionary<string, string> d = new Dictionary<string, string>();
d["a"] = "he";
d["ba"] = "llo";
d["lol"] = "world";
string a = string.Format("{a}{ba}{lol}", d);

4
请参阅:https://dev59.com/13M_5IYBdhLWcg3wlEBH#1322103 - Marc Gravell
12个回答

18
不会,但是这个扩展方法可以实现。
static string FormatFromDictionary(this string formatString, Dictionary<string, string> valueDict) 
{
    int i = 0;
    StringBuilder newFormatString = new StringBuilder(formatString);
    Dictionary<string, int> keyToInt = new Dictionary<string,int>();
    foreach (var tuple in valueDict)
    {
        newFormatString = newFormatString.Replace("{" + tuple.Key + "}", "{" + i.ToString() + "}");
        keyToInt.Add(tuple.Key, i);
        i++;                    
    }
    return String.Format(newFormatString.ToString(), valueDict.OrderBy(x => keyToInt[x.Key]).Select(x => x.Value).ToArray());
}

5
注意,formatString 中包含 "{{thing}}" 并且 ValueDict 中有一个名为 "thing" 的键将会用数字替换字符串中的 "thing"。 - Juan
这个很好用。不过ReSharper不喜欢将字符串转换为对象,所以我将你的字典更改为了一个字符串、对象对。另外,虽然只是小问题,但我不认为KeyValuePair是元组。它并不是一份有序的事物列表。ValueDict更像是元组。 - JDPeckham

9

请看这个,它支持格式化:

    public static string StringFormat(string format, IDictionary<string, object> values)
    {
        var matches = Regex.Matches(format, @"\{(.+?)\}");
        List<string> words = (from Match matche in matches select matche.Groups[1].Value).ToList();

        return words.Aggregate(
            format,
            (current, key) =>
                {
                    int colonIndex = key.IndexOf(':');
                    return current.Replace(
                        "{" + key + "}",
                        colonIndex > 0
                            ? string.Format("{0:" + key.Substring(colonIndex + 1) + "}", values[key.Substring(0, colonIndex)])
                            : values[key] == null ? string.Empty : values[key].ToString());
                });
    }

使用方法:

string format = "{foo} is a {bar} is a {baz} is a {qux:#.#} is a really big {fizzle}";
var dictionary = new Dictionary<string, object>
    {
        { "foo", 123 },
        { "bar", true },
        { "baz", "this is a test" },
        { "qux", 123.45 },
        { "fizzle", DateTime.Now }
    };
StringFormat(format, dictionary)

        

1
不考虑转义:StringFormat("{{key}}", new Dictionary<string, object>(){["key"] = "value"}); 失败。 - Niklas Peter

4
您可以自己实现:
public static string StringFormat(string format, IDictionary<string, string> values)
{
    foreach(var p in values)
        format = format.Replace("{" + p.Key + "}", p.Value);
    return format;
}

6
不过这会失去很多String.Format的功能性。 - Craig
请注意,使用此函数时您不能多次重复键。例如,“嗨{{Name}},你的名字真的是{{Name}}吗?” - Fred
我认为@Fred是错的。String.Replace会替换每个{name}。https://learn.microsoft.com/en-us/dotnet/api/system.string.replace?view=netframework-4.7.2 - Janne Harju
如果您预计经常使用较长的字符串,那么使用 StringBuilder 可以获得更好的性能。 - datchung

3

现在是可能的

使用 C# 6.0 的 插值字符串,您可以做到这一点:

string name = "John";
string message = $"Hi {name}!";
//"Hi John!"

5
这不是被要求的内容。如果你不知道该字段被称为什么,你就无法准备该标识符的变量。 - Moberg
你可以使用 $"Hi {d["name"]}",这应该符合 OP 的要求。 - Juan
你不能使用字符串插值来参数化格式字符串。例如:var format = "现在时间是 {DateTime.Now}"; Console.WriteLine($format); - Kasun
1
@Kasun,你觉得这样怎么样:format = $"现在时间是 {DateTime.Now}"; 你忘记在字符串开头加上 $ 符号了。 - Janne Harju

3

我也喜欢Hansel格式化程序。它不依赖于System.Web,像其他格式化程序一样,这对于插入到我的实用程序库中非常好。我喜欢可以从任何对象中获取并进行格式化的功能。 - Valamas

1
static public class StringFormat
{
    static private char[] separator = new char[] { ':' };
    static private Regex findParameters = new Regex(
        "\\{(?<param>.*?)\\}",
        RegexOptions.Compiled | RegexOptions.Singleline);

    static string FormatNamed(
        this string format,
        Dictionary<string, object> args)
    {
        return findParameters.Replace(
            format,
            delegate(Match match)
            {
                string[] param = match.Groups["param"].Value.Split(separator, 2);

                object value;
                if (!args.TryGetValue(param[0], out value))
                    value = match.Value;

                if ((param.Length == 2) && (param[1].Length != 0))
                    return string.Format(
                        CultureInfo.CurrentCulture,
                        "{0:" + param[1] + "}",
                        value);
                else
                    return value.ToString();
            });
    }
}

比其他扩展方法稍微复杂一些,但这也应该允许非字符串值和用于它们的格式化模式,因此在您的原始示例中:
Dictionary<string, object> d = new Dictionary<string, object>();
d["a"] = DateTime.Now;
string a = string.FormatNamed("{a:yyyyMMdd-HHmmss}", d);

也会起作用...


0
我采用了@LPCRoy的答案并进行了重构以适用于通用字典类型,添加了参数验证,它只替换它能找到的字段,并尝试通过首先对其进行转义,然后在最后以与string.Format()相同的方式用单括号替换来解决“双花括号”问题。
public static string FormatFromDictionary<K, T>(this string formatString, IDictionary<K, T> valueDict)
{
    if (string.IsNullOrWhiteSpace(formatString)) return formatString;
    if (valueDict == null || !valueDict.Any()) return formatString;

    bool escapedDoubleCurlyBraces = false;
    if (formatString.Contains("{{") || formatString.Contains("}}"))
    {
        formatString = formatString.Replace("{{", "\\{\\{").Replace("}}", "\\}\\}");
        escapedDoubleCurlyBraces = true;
    }

    int i = 0;
    StringBuilder newFormatString = new StringBuilder(formatString);
    Dictionary<K, int> keyToInt = new Dictionary<K, int>();
    string field;

    //StringBuilder.Replace() is faster than string.Replace().
    foreach (var kvp in valueDict)
    {
        field = "{" + kvp.Key.ToString() + "}";
        if (formatString.Contains(field))
        {
            newFormatString = newFormatString.Replace(field, "{" + i.ToString() + "}");
            keyToInt.Add(kvp.Key, i);
            i++;
        }
    }

    //Any replacements to make?
    if (keyToInt.Any())
    {
        formatString = string.Format(
            newFormatString.ToString(),
            keyToInt.OrderBy(kvp => kvp.Value).Select(kvp => (object)valueDict[kvp.Key]).ToArray()
        );
    }
    if (escapedDoubleCurlyBraces)
    {
        //Converts "{{" and "}}" to single "{" and "}" for the final result just like string.format().
        formatString = formatString.Replace("\\{\\{", "{").Replace("\\}\\}", "}");
    }

    return formatString;
}

0

自从C# 6发布以来,您就可以使用字符串插值功能

解决您问题的代码:

string a = $"{d["a"]}{d["ba"]}{d["lol"]}";

0
public static string StringFormat(this string format, IDictionary<string, object> values)
{
    return Regex.Matches(format, @"\{(?!\{)(.+?)\}")
            .Select(m => m.Groups[1].Value)
            .Aggregate(format, (current, key) =>
            {
                string[] splits = key.Split(":");
                string replacement = splits.Length > 1
                    ? string.Format($"{{0:{splits[1]}}}", values[splits[0]])
                    : values[key].ToString();
                return Regex.Replace(current, "(.|^)("+ Regex.Escape($"{{{key}}}")+")(.|$)", 
                                     m => m.Groups[1].ToString() == "{" && m.Groups[3].ToString() == "}"
                                        ? m.Groups[2].ToString()
                                        : m.Groups[1] + replacement + m.Groups[3]
                                    );
            });
}

这与另一个答案类似,但它考虑了使用{{text}}进行转义。


0
为什么需要一个字典?这是不必要和过于复杂的。一个简单的由名称/值对组成的二维数组同样可以很好地工作:
public static string Format(this string formatString, string[,] nameValuePairs)
{
    if (nameValuePairs.GetLength(1) != 2)
    {
        throw new ArgumentException("Name value pairs array must be [N,2]", nameof(nameValuePairs));
    }
    StringBuilder newFormat = new StringBuilder(formatString);
    int count = nameValuePairs.GetLength(0);
    object[] values = new object[count];
    for (var index = 0; index < count; index++)
    {
        newFormat = newFormat.Replace(string.Concat("{", nameValuePairs[index,0], "}"), string.Concat("{", index.ToString(), "}"));
        values[index] = nameValuePairs[index,1];
    }
    return string.Format(newFormat.ToString(), values);
}

使用以下方式调用:

string format = "{foo} = {bar} (really, it's {bar})";
string formatted = format.Format(new[,] { { "foo", "Dictionary" }, { "bar", "unnecessary" } });

结果为:"字典 = 不必要的(真的,它是不必要的)"


有没有理由用int替换键,然后进行字符串格式化,而不是直接用值替换键? - Persi
场景:ETL转换将从数据库推送的数据转换为API。将DB数据转换为字典以序列化为API的JSON。我可以创建可配置的模板,如此添加新字段到输出数据。 templates.Add("newField", "{id}:{name_short},{position}"); for (var kvp in templates) record.Add(kvp.Key, kvp.Value.FormatFromDictionary(record)); - jwatts1980

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接