Как собрать выражение для конкатенации свойств произвольного класса?

131
24 августа 2021, 04:50

Хочу с помощью деревьев выражений LINQ сгенерировать делегат, который будет делать конкатенацию всех строковых свойств произвольного типа:

static Func<T,string> TestConcat<T>(T obj)
{
    var type=obj.GetType();
    LabelTarget label = Expression.Label(typeof(string), "ResultLabel");
    var param = Expression.Parameter(type);
    var resultExp=Expression.Variable(typeof(string), "result");
    Expression assign = Expression.Assign(resultExp, Expression.Constant(""));
    foreach (var prop in type.GetProperties())
    {
        var member = Expression.Property(param, prop);
        assign = Expression.Add(assign, member, typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) }));
    }
    Console.WriteLine(assign.ToString());
    var block = Expression.Block(new[] { resultExp, param }, new[] { Expression.Label(label, assign), assign  });
    return Expression.Lambda<Func<T,string>>(block, param).Compile();
}

Ловлю NullReferenceException, но не понимаю в чем проблема...

Подскажите, как правильно сделать?

Answer 1

Мне кажется, вы напихали лишнего в свой метод. У меня работает такой код:

static class ConcatEx
{
    public static string ConcatProps<T>(this T obj) => ConcatPropsImpl<T>.ConcatProps(obj);
    private class ConcatPropsImpl<T>
    {
        static ConcatPropsImpl()
        {
            var parameter = Expression.Parameter(typeof(T), "x");
            Expression body = Expression.Constant(string.Empty, typeof(string));
            var method = typeof(string).GetMethod("Concat", new[] { typeof(string), typeof(string) });
            foreach (var prop in typeof(T).GetProperties())
                body = Expression.Add(body, Expression.Property(parameter, prop), method);
            var lambda = Expression.Lambda<Func<T, string>>(body, parameter);
            ConcatProps = lambda.Compile();
        }
        public static readonly Func<T, string> ConcatProps;
    }
}

Использовать так:

var test = new Test { A = "A", B = "B", C = "C" };
Console.WriteLine(test.ConcatProps());

Ну или, если вам нужна именно функция, а не результат ее выполнения, то берите ConcatEx.ConcatProps<Test>

Но, мне кажется, на выходе не самый эффективный метод получается (string.Concat(string.Concat(string.Concat("", x.A), x.B), x.C))

Наверное, лучше вызывать перегрузку string.Concat, принимающую массив:

static class ConcatEx
{
    public static string ConcatProps<T>(this T obj) => ConcatPropsImpl<T>.ConcatProps(obj);
    private class ConcatPropsImpl<T>
    {
        static ConcatPropsImpl()
        {
            var parameter = Expression.Parameter(typeof(T), "x");
            var method = typeof(string).GetMethod("Concat", new[] { typeof(object[]) });
            var props = typeof(T).GetProperties().Select(prop => Expression.Property(parameter, prop));
            var array = Expression.NewArrayInit(typeof(object), props);
            var body = Expression.Call(method, array);
            var lambda = Expression.Lambda<Func<T, string>>(body, parameter);
            ConcatProps = lambda.Compile();
        }
        public static readonly Func<T, string> ConcatProps;
    }
}
READ ALSO
Как привести к generic типу?

Как привести к generic типу?

нельзя просто взять и привестиTType может быть int, string, etc

98
При уничтожении обьекта подвисает игра на Unity

При уничтожении обьекта подвисает игра на Unity

При уничтожении игрового объекта Destroy(gameobject); подвисает на секунду играПричина этому проклятая библиотека A* Pathfinding Project Выключая главный...

89
Как обратиться к данным в Entity Framework

Как обратиться к данным в Entity Framework

В моемNet Core приложении я хранил данные в классе DataService

265