如何为字符串插值构建表达式树?

时间:2017-05-18 01:25:56

标签: c# lambda expression

我有一个存储为字符串的模板:“[u1:firstname] [u1:lastname]”

我需要将模板转换为输出字符串的Expression。 我已经编写了一个解析器来提取令牌,但我不太确定如何为它构建Expression。代码已在下面简化。

public class Account {
    private Func<Account, string> template;

    public User User1 { get; set; }
    public User User2 { get; set; }
    public string AccountName => this.template(this);

    public void SetTemplate(Expression<Func<Account, string>> template) {
        this.template = template.Compile();
    }
}

public class User {
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

帐户名称由模板定义,如果与模板关联的属性发生更改,帐户名称也会更改。

如果我手动设置表达式,它可以工作:
var account = new Account().SetTemplate(a => $"{a.User1.FirstName} {a.User2.LastName}");

但是如何更动态地构建Expression

现在我正在做这样的事情:

using TemplateExpression = Expression<Func<Account, string>>;

string templateString = "[u1:firstname] [u1:lastname]";
var expressions = new List<Expression>();
for (int i = 0; i < templateString.Length; ++i) {
    var token = getToken(templateString, i);
    switch (token) {
        case "u1:firstname":
           TemplateExpression u1FirstNameExpr = a => a.User1.FirstName;
           expressions.Add(u1FirstNameExpr);
           break;
        case "u1:lastname":
           TemplateExpression u1LastNameExpr = a => a.User1.LastName;
           expressions.Add(u1LastNameExpr);
           break;
        // other possible tokens.
        default: // constant
           var constant = Expression.Constant(token);
           expressions.Add(constant);
           break;
    }
}

但是我不知道如何将这些表达式组合成一个类似于上面的字符串插值的表达式。我想也许可以使用string.Concat组合表达式,但我也无法使用它。也许string.Format会更好吗?但我仍然不确定如何构建该表达式。

1 个答案:

答案 0 :(得分:1)

请注意,我必须重写getToken才能测试该方法。我使用Expression.Invoke是因为这是从另一个Expression调用Expression的最简单方法。最后,几乎所有代码都是准备string,其中包含"Hello {0} world {1}"之类的格式以及string.Format中传递的对象数组。

public static string getToken(string templateString, ref int i, out bool isToken)
{
    int j = i;

    if (templateString[j] == '{')
    {
        isToken = true;

        j++;

        int k = templateString.IndexOf('}', j);

        if (k == -1)
        {
            throw new Exception();
        }

        i = k + 1;

        return templateString.Substring(j, k - j);
    }
    else
    {
        isToken = false;
        i++;
        return templateString[j].ToString();
    }
}

public static Expression<Func<Account, string>> CreateTemplate(string templateString)
{
    var formatObjs = new List<Expression>();
    var formatString = new StringBuilder();
    int parameterNumber = 0;

    var accountParameter = Expression.Parameter(typeof(Account), "a");

    for (int i = 0; i < templateString.Length;)
    {
        bool isToken;
        var token = getToken(templateString, ref i, out isToken);

        if (isToken)
        {
            Expression<Func<Account, string>> member;

            switch (token)
            {
                case "u1:firstname":
                    member = a => a.User1.FirstName;
                    break;
                case "u1:lastname":
                    member = a => a.User1.LastName;
                    break;
                // other possible tokens.
                default: // constant
                    throw new Exception();
            }

            formatObjs.Add(Expression.Invoke(member, accountParameter));

            formatString.Append('{');
            formatString.Append(parameterNumber);
            formatString.Append('}');
            parameterNumber++;
        }
        else
        {
            formatString.Append(token);
        }
    }

    var formatMethod = typeof(string).GetMethod("Format", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(object[]) }, null);
    var formatConstantExpression = Expression.Constant(formatString.ToString());
    var formatObjsExpression = Expression.NewArrayInit(typeof(object), formatObjs);
    var lambdaExpression = Expression.Lambda<Func<Account, string>>(Expression.Call(formatMethod, formatConstantExpression, formatObjsExpression), accountParameter);
    return lambdaExpression;
}

使用它像:

var acc = new Account
{
    User1 = new User { FirstName = "Foo", LastName = "Bar" }
};

acc.SetTemplate(Account.CreateTemplate("Hello {u1:firstname} World {u1:lastname}!!!"));
string name = acc.AccountName;