简体   繁体   English

字符串表达式到c#函数委托

[英]string expression to c# function delegate

I want to convert the following string into function delegate. 我想将以下字符串转换为函数委托。

[Id]-[Description]

C# class: C#类:

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

Result function delegate: 结果函数委托:

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

I think compiled lambda or expression parser would be a way here, but not sure about the best way much. 我认为编译的lambda或表达式解析器在这里是一种方式,但不确定最好的方法。 Any inputs? 有什么投入?

It's possible as: to construct Linq Expression then compile it. 它可能是:构造Linq Expression然后编译它。 Compiled expression is an ordinary delegate, with no performance drawbacks. 编译表达式是普通委托,没有性能缺陷。

An example of implementation if type of argument( Foo ) is known at compile time: 如果在编译时已知参数类型( 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();
    }
}

And usage: 用法:

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

An almost identical answer was posted while I was testing this, but, as the below code has an advantage of calling each property mentioned in the formatting string at most once, I'm posting it anyway: 我在测试时发布了一个几乎完全相同的答案,但是,由于下面的代码最多只调用格式化字符串中提到的每个属性一次,我仍然会发布它:

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

The inspiration comes from Generate lambda Expression By Clause using string.format in C#? 灵感来自于使用C#中的string.format生成lambda Expression By Clause? .

A simple step by step version to create an Expression tree based on simple use case, can help in creating any kind of Expression tree 基于简单用例创建表达式树的简单分步版本可以帮助创建任何类型的表达式树

What we want to Achieve: (coding in linqpad, Dump is a print call) 我们想要实现的目标:(在linqpad中编码,转储是打印调用)

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

Following is the Expression generated: 以下是生成的表达式:

在此输入图像描述

Step by Step process to Create an Expression Tree 逐步创建表达式树的过程

On Reviewing the Expression Tree, following points can be understood: 在查看表达式树时,可以理解以下几点:

  1. We create a Func delegate of type typeof(Func<Foo,String>) 我们创建一个typeof(Func<Foo,String>)类型的Func委托typeof(Func<Foo,String>)
  2. Outer Node Type for Expression is Lambda Type 表达式的外部节点类型是Lambda Type
  3. Just needs one parameter Expression of typeof(Foo) 只需要一个参数表达式typeof(Foo)
  4. In Arguments it needs, MethodInfo of string.Format 在它需要的Arguments中, string.Format MethodInfo
  5. In arguments to Format method, it needs following Expressions 在Format方法的参数中,它需要遵循表达式
  6. a.) Constant Expression - {0}-{1} a。)常量表达式 - {0}-{1}
  7. b.) MemberExpression for Id field b。) Id字段的MemberExpression
  8. c.) MemberExpression for Description field c。) Description字段的MemberExpression
  9. Viola and we are done Viola和我们完成了

Using the Steps above following is the simple code to create Expression: 使用以上步骤是创建Expression的简单代码:

// 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"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM