строковое выражение для делегата функции c # - PullRequest
4 голосов
/ 17 мая 2019

Я хочу преобразовать следующую строку в делегат функции.

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

Я думаю, что скомпилированный лямбда или синтаксический анализатор выражений будет подходить сюда, но не уверен насчет лучшего способа. Любые входы?

Ответы [ 3 ]

3 голосов
/ 17 мая 2019

Это возможно как: создать выражение 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 голосов
/ 17 мая 2019

Почти идентичный ответ был опубликован , пока я тестировал это, но, поскольку приведенный ниже код имеет преимущество вызова каждого свойства, упомянутого в строке форматирования, не более одного раза, я все равно публикую его:

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

Вдохновение приходит от Создание лямбда-выражения по выражению с использованием string.format в C #? .

1 голос
/ 18 мая 2019

Простая пошаговая версия для создания дерева выражений на основе простого варианта использования может помочь в создании любого вида дерева выражений

Что мы хотим достичь: (кодирование в 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();

Ниже приводится сгенерированное выражение:

enter image description here

Пошаговый процесс создания дерева выражений

При просмотре дерева выраженийможно понять следующие моменты:

  1. Мы создаем делегат Func типа typeof(Func<Foo,String>)
  2. Тип внешнего выражения для выражения: Lambda Type
  3. Просто нуженПараметр Выражение typeof(Foo)
  4. В аргументах ему нужно, MethodInfo из string.Format
  5. В аргументах метода Format ему нужны следующие выражения
  6. a.) КонстантаВыражение - {0}-{1}
  7. b.) MemberExpression для Id поле
  8. c.) MemberExpression для Description поле
  9. Violа и все готово

Используя вышеприведенные шаги, приведем простой код для создания выражения:

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