【问题标题】:string expression to c# function delegate字符串表达式到 c# 函数委托
【发布时间】:2019-05-17 10:19:32
【问题描述】:

我想把下面的字符串转换成函数委托。

[Id]-[Description]

C#类:

public class Foo
{
    public string Id {get;set;}
    public string Description {get;set;}
}

结果函数委托:

Func<Foo, string> GetExpression = delegate()
{
    return x => string.Format("{0}-{1}", x.Id, x.Description);
};

我认为编译的 lambda 或表达式解析器在这里是一种方法,但不确定最好的方法。有输入吗?

【问题讨论】:

  • 为什么不覆盖ToString()方法?
  • @asd 因为 OP 需要一个可以通过相关输入从其他地方执行的委托
  • 您是否有理由要使用委托,而不仅仅是创建一个为您解析字符串的通用函数?
  • @WiktorZychla 我认为 OP 希望根据第一行中给出的示例字符串动态创建 Func
  • 但是你为什么想要一个函数委托呢?编写一个简单的函数来执行此操作(例如,使用一堆 string.Replace)非​​常容易。你需要一个函数有什么原因吗?

标签: c# lambda expression-trees func


【解决方案1】:

可能是:构造 Linq 表达式然后编译它。编译后的表达式是一个普通的委托,没有性能缺陷。

如果参数类型(Foo)在编译时已知的实现示例:

class ParserCompiler
{
    private static (string format, IReadOnlyCollection<string> propertyNames) Parse(string text)
    {
        var regex = new Regex(@"(.*?)\[(.+?)\](.*)");

        var formatTemplate = new StringBuilder();
        var propertyNames = new List<string>();
        var restOfText = text;
        Match match;
        while ((match = regex.Match(restOfText)).Success)
        {
            formatTemplate.Append(match.Groups[1].Value);
            formatTemplate.Append("{");
            formatTemplate.Append(propertyNames.Count);
            formatTemplate.Append("}");

            propertyNames.Add(match.Groups[2].Value);

            restOfText = match.Groups[3].Value;
        }

        formatTemplate.Append(restOfText);

        return (formatTemplate.ToString(), propertyNames);
    }

    public static Func<T, string> GetExpression<T>(string text) //"[Id]-[Description]"
    {
        var parsed = Parse(text); //"{0}-{1}  Id, Description"

        var argumentExpression = Expression.Parameter(typeof(T));

        var properties = typeof(T)
            .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetField)
            .ToDictionary(keySelector: propInfo => propInfo.Name);

        var formatParamsArrayExpr = Expression.NewArrayInit(
            typeof(object), 
            parsed.propertyNames.Select(propName => Expression.Property(argumentExpression, properties[propName])));

        var formatStaticMethod = typeof(string).GetMethod("Format", BindingFlags.Static | BindingFlags.Public, null,new[] { typeof(string), typeof(object[]) }, null);
        var formatExpr = Expression.Call(
            formatStaticMethod,
            Expression.Constant(parsed.format, typeof(string)),
            formatParamsArrayExpr);

        var resultExpr = Expression.Lambda<Func<T, string>>(
            formatExpr,
            argumentExpression); // Expression<Func<Foo, string>> a = (Foo x) => string.Format("{0}-{1}", x.Id, x.Description);

        return resultExpr.Compile();
    }
}

及用法:

        var func = ParserCompiler.GetExpression<Foo>("[Id]-[Description]");
        var formattedString = func(new Foo {Id = "id1", Description = "desc1"});

【讨论】:

  • 啊,是的,它只是查询每个属性的次数与格式字符串中提到的一样多……我要发布的那个调用每个属性一次;)
【解决方案2】:

一个几乎相同的答案was posted,而我正在测试这个,但是,由于下面的代码有一个优点是最多调用格式化字符串中提到的每个属性,所以我还是发布它:

public static Func<Foo, string> GetExpression(string query_string)
{
    (string format_string, List<string> prop_names) = QueryStringToFormatString(query_string);

    var lambda_parameter = Expression.Parameter(typeof(Foo));

    Expression[] formatting_params = prop_names.Select(
        p => Expression.MakeMemberAccess(lambda_parameter, typeof(Foo).GetProperty(p))
     ).ToArray();

    var formatMethod = typeof(string).GetMethod("Format", new[] { typeof(string), typeof(object[]) });

    var format_call = Expression.Call(formatMethod, Expression.Constant(format_string), Expression.NewArrayInit(typeof(object), formatting_params));

    var lambda = Expression.Lambda(format_call, lambda_parameter) as Expression<Func<Foo, string>>;
    return lambda.Compile();
}

// A *very* primitive parser, improve as needed
private static (string format_string, List<string> ordered_prop_names) QueryStringToFormatString(string query_string)
{
    List<string> prop_names = new List<string>();

    string format_string = Regex.Replace(query_string, @"\[.+?\]", m => {
        string prop_name = m.Value.Substring(1, m.Value.Length - 2);

        var known_pos = prop_names.IndexOf(prop_name);

        if (known_pos < 0)
        {
            prop_names.Add(prop_name);
            known_pos = prop_names.Count - 1;
        }

        return $"{{{known_pos}}}";
    });

    return (format_string, prop_names);
}

灵感来自Generate lambda Expression By Clause using string.format in C#?

【讨论】:

    【解决方案3】:

    基于简单用例创建表达式树的简单分步版本,可以帮助创建任何类型的表达式树

    我们想要实现的目标:(在 linqpad 中编码,Dump 是一个打印调用)

    Expression<Func<Foo,string>> expression = (f) => string.Format($"{f.Id}- 
    {f.Description}"); 
    
    var foo = new Foo{Id = "1",Description="Test"};
    
    var func  = expression.Compile();
    
    func(foo).Dump(); // Result "1-Test"
    
    expression.Dump();
    

    以下是生成的表达式:

    创建表达式树的分步过程

    在查看表达式树时,可以理解以下几点:

    1. 我们创建了一个typeof(Func&lt;Foo,String&gt;) 类型的 Func 委托
    2. 表达式的外部节点类型是Lambda Type
    3. 只需要typeof(Foo)的一个参数表达式
    4. 在它需要的参数中,MethodInfo of string.Format
    5. 在 Format 方法的参数中,它需要以下表达式
    6. a.) 常量表达式 - {0}-{1}
    7. b.) Id 字段的成员表达式
    8. c.) Description 字段的成员表达式
    9. 维奥拉,我们完成了

    使用上面的步骤是创建表达式的简单代码:

    // Create a ParameterExpression
    var parameterExpression = Expression.Parameter(typeof(Foo),"f");
    
    // Create a Constant Expression
    var formatConstant  = Expression.Constant("{0}-{1}");
    
    // Id MemberExpression
    var idMemberAccess = Expression.MakeMemberAccess(parameterExpression, typeof(Foo).GetProperty("Id"));
    
    // Description MemberExpression         
    var descriptionMemberAccess = Expression.MakeMemberAccess(parameterExpression, typeof(Foo).GetProperty("Description"));
    
    // String.Format (MethodCallExpression)
    var formatMethod = Expression.Call(typeof(string),"Format",null,formatConstant,idMemberAccess,descriptionMemberAccess);
    
    // Create Lambda Expression
    var lambda = Expression.Lambda<Func<Foo,string>>(formatMethod,parameterExpression);
    
    // Create Func delegate via Compilation
    var func = lambda.Compile();
    
    // Execute Delegate 
    func(foo).Dump(); // Result "1-Test"
    

    【讨论】:

    • 谢谢。这对于了解其工作原理非常有用,并有助于轻松创建复杂的。
    • 是的,虽然您需要一个 Expression Tree Visualizer 来解决反向问题,但 Linqpad 很好,还有其他类似的Expression Tree Visualizer,但是从零开始构建的非常好的方法,快乐编程
    猜你喜欢
    • 1970-01-01
    • 2011-08-14
    • 2015-12-04
    • 1970-01-01
    • 1970-01-01
    • 2012-08-27
    • 2011-06-30
    • 1970-01-01
    相关资源
    最近更新 更多