动态字符串插值

38
"管理员的待办工作"
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(ReplaceMacro("{job.Name} job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
        Console.ReadLine();
    }

    static string ReplaceMacro(string value, Job job)
    {
        return value; //Output should be "Todo job for admin"
    }
}

class Job
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

9
基本上是不行的。这不是插值字符串文字的工作方式。字符串格式化(或转换为 FormattableString)会立即完成。 - Jon Skeet
虽然没有什么阻止你使用字符串替换来实现一个朴素的解决方案。根据情况,它可能会有性能问题,但你可以这样做。 - Lasse V. Karlsen
你需要自己解析字符串。 - juharr
我认为你所要求的是一个FormattableString,它是由编译器输出的插值字符串对象。搜索“Interpolated strings: advanced usages - Meziantou's blog”可以找到一种技术,该技术从FormattableString派生出一个类来实现你所要求的功能。 - Suncat2000
15个回答

48

两个建议:

DataBinder.Eval

string ReplaceMacro(string value, Job job)
{
    return Regex.Replace(value, @"{(?<exp>[^}]+)}", match => {
        return (System.Web.UI.DataBinder.Eval(new { Job = job }, match.Groups["exp"].Value) ?? "").ToString();
    });
}

Linq.Expression

请使用MSDN LINQSamples中提供的动态查询类:

string ReplaceMacro(string value, Job job)
{
    return Regex.Replace(value, @"{(?<exp>[^}]+)}", match => {
        var p = Expression.Parameter(typeof(Job), "job");
        var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
        return (e.Compile().DynamicInvoke(job) ?? "").ToString();
    });
}

在我看来,Linq.Expression更加强大,因此如果您信任输入字符串,您可以做更有趣的事情,即:
value = "{job.Name.ToUpper()} job for admin"
return = "TODO job for admin"

你能否更新"${job.Name}"的正则表达式...谢谢 - Jitendra Tiwari
太好了。愚蠢的是,我已经在处理更复杂的事情时使用动态表达式,并使用正则表达式替换琐碎的变量,但我没有将正则表达式用于查找复杂的变量名,然后使用现有的表达式解析代码进行替换... - James Thorpe
谢谢!我在这上面浪费了两天时间。 - habib
8
如果您正在寻找.NET Core版本,可以使用System.Linq.Dynamic.Core库。然后必须将ParseLambda行更改为: var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { p }, null, match.Groups["exp"].Value); - Nigel
太棒了!正是我在寻找的。将复杂字符串存储在我的数据库中,并使用运行时数据进行“插值”。Linq表达式非常强大,可以允许替换深度嵌套对象,@Nigel的Core版本也很好地融入其中!感谢大家! - Steve Danner

16

你不能以这种方式使用字符串插值。但是你仍然可以使用 C#6 之前的方法,使用 string.Format

static void Main(string[] args)
{
    Console.WriteLine(ReplaceMacro("{0} job for admin", new Job { Id = 1, Name = "Todo", Description = "Nothing" }));
    Console.ReadLine();
}

static string ReplaceMacro(string value, Job job)
{
    return string.Format(value, job.Name);
}

6
由于字符串插值只是一种语法糖,而String.Format仍然用于生成IL代码,在这里做更多的花哨操作真的很困难。 - decPL
这基本上应该是答案,因为它简明扼要地完成了所需的任务,同时简单易懂。 - Glitch

5
这是一个通用的解决方案,扩展了@Dan提供的答案。
它可以用于任何类型的对象。

安装System.Linq.Dynamic。

     Install-Package System.Linq.Dynamic -Version 1.0.7 

    string ReplaceMacro(string value, object @object)
    {
        return Regex.Replace(value, @"{(.+?)}", 
        match => {
            var p = Expression.Parameter(@object.GetType(), @object.GetType().Name);                
            var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] { p }, null, match.Groups[1].Value);
            return (e.Compile().DynamicInvoke(@object) ?? "").ToString();
        });
    }

点击此处查看一个与Customer类型相关的演示案例


3

你可以使用RazorEngine

using RazorEngine;

class Program 
{
    static void Main(string[] args)
    {
        Console.WriteLine(ReplaceMacro("@Model.Name job for admin", new Job { Id = 1, Name = "Todo", Description="Nothing" }));
        Console.ReadLine();
    }

    static string ReplaceMacro(string value, Job job)
    {
        return Engine.Razor.RunCompile(value, "key", typeof(Job), job);
    }
}

它甚至支持匿名类型和方法调用:
string template = "Hello @Model.Name. Today is @Model.Date.ToString(\"MM/dd/yyyy\")";
var model = new { Name = "Matt", Date = DateTime.Now };

string result = Engine.Razor.RunCompile(template, "key", null, model);

对于 .net core 项目,请考虑使用 RazorEngine.NetCore,而不是 RazorEngine - VivekDev

3

来参加派对有点晚了!这是我写的内容 -

using System.Reflection;
using System.Text.RegularExpressions;

public static class AmitFormat
{
    //Regex to match keywords of the format {variable}
    private static readonly Regex TextTemplateRegEx = new Regex(@"{(?<prop>\w+)}", RegexOptions.Compiled);

    /// <summary>
    /// Replaces all the items in the template string with format "{variable}" using the value from the data
    /// </summary>
    /// <param name="templateString">string template</param>
    /// <param name="model">The data to fill into the template</param>
    /// <returns></returns>
    public static string FormatTemplate(this string templateString, object model)
    {
        if (model == null)
        {
            return templateString;
        }

        PropertyInfo[] properties = model.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

        if (!properties.Any())
        {
            return templateString;
        }

        return TextTemplateRegEx.Replace(
            templateString,
            match =>
            {
                PropertyInfo property = properties.FirstOrDefault(propertyInfo =>
                    propertyInfo.Name.Equals(match.Groups["prop"].Value, StringComparison.OrdinalIgnoreCase));

                if (property == null)
                {
                    return string.Empty;
                }

                object value = property.GetValue(model, null);

                return value == null ? string.Empty : value.ToString();
            });
    }
}

例子 -

string format = "{foo} is a {bar} is a {baz} is a {qux} is a really big {fizzle}";
var data = new { foo = 123, bar = true, baz = "this is a test", qux = 123.45, fizzle = DateTime.UtcNow };

与 Phil Haack 给出的其他实现相比,以下是上述示例的结果 -
AmitFormat    took 0.03732 ms
Hanselformat  took 0.09482 ms
OskarFormat   took 0.1294 ms
JamesFormat   took 0.07936 ms
HenriFormat   took 0.05024 ms
HaackFormat   took 0.05914 ms

这是一个非常稳健的实现。我只是想知道在你的示例中,像{qux:#.#}这样的格式化字符串是如何转换为123.45的。正则表达式甚至无法匹配该表达式。不是吗? - Manish Rawat
没错,它不匹配,上面的代码执行的是模式匹配和替换。移除了 :#.# - Amit

2

Wrap the string in a function...

var f = x => $"Hi {x}";

f("Mum!");

//... Hi Mum!

2

虽然不完全一样,但通过微调,我创建了通用的插值器,支持仅包含字段/属性。

public static string Interpolate(this string template, params Expression<Func<object, string>>[] values)
        {
            string result = template;
            values.ToList().ForEach(x =>
            {
                MemberExpression member = x.Body as MemberExpression;
                string oldValue = $"{{{member.Member.Name}}}";
                string newValue = x.Compile().Invoke(null).ToString();
                result = result.Replace(oldValue, newValue);
            }

                );
            return result;
        }

测试用例

 string jobStr = "{Name} job for admin";
        var d = new { Id = 1, Name = "Todo", Description = "Nothing" };
        var result = jobStr.Interpolate(x => d.Name);

另一个。
            string sourceString = "I wanted abc as {abc} and {dateTime} and {now}";
        var abc = "abcIsABC";
        var dateTime = DateTime.Now.Ticks.ToString();
        var now = DateTime.Now.ToString();
        string result = sourceString.Interpolate(x => abc, x => dateTime, x => now);

1

1

从已接受的答案开始,我创建了一个通用的扩展方法:

public static string Replace<T>(this string template, T value)
{
    return Regex.Replace(template, @"{(?<exp>[^}]+)}", match => {
        var p = Expression.Parameter(typeof(T), typeof(T).Name);
        var e = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda(new[] { p }, null, match.Groups["exp"].Value);
        return (e.Compile().DynamicInvoke(value) ?? "").ToString();
    });
}

当我在寻找替代某些丑陋的特定字符串替换电子邮件模板时,我遇到了这个被接受的答案,并创建了一个类似的通用实现。虽然我添加了一个可选的字符串,用于值属性名称,以便模板可以引用已知的有意义的名称而不是传递的类型名称,或者允许该方法从匿名类型之类的数据中解析数据。string Interpolate<TData>(string template, TData data, string? interpolateDataAs = null) 如果没有提供名称,则尝试使用TData类型名称或"data"。 - Steve Py
它能工作吗?你能提供代码吗? - giganoide
下面添加了实现。 - Steve Py

1

想知道为什么没有人提到mustache-sharp。可以通过Nuget下载。

string templateFromSomewhere = "url: {{Url}}, Name:{{Name}}";
FormatCompiler compiler = new FormatCompiler();
Generator generator = compiler.Compile(templateFromSomewhere);
string result = generator.Render(new
{
    Url="https://google.com",
    Name = "Bob",
});//"url: https://google.com, Name:Bob"

更多的例子可以在单元测试文件这里找到。


1
刚刚尝试了Mustache#来解决一个类似的问题,我必须说它是一个很棒的库。非常简单,使用标准的C#格式约定,并且比它的readme.md所建议的功能更多。感谢你的指引。 - undefined

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