Как мне построить и скомпилировать это выражение C # с универсальными типами & лямбда-выражениями - PullRequest
0 голосов
/ 13 января 2019

Я пытаюсь создать собственный сериализатор. Это должно быть быстрым.

Идея состоит в том, чтобы создать и кэшировать некоторые Func<…> для каждого типа

В этом упрощенном примере я успешно создаю Func для типов STRING, но я застрял на том, как создать его для типов ARRAY.

Может быть полезно представить, что теперь я могу сериализовать класс Meow, но я не смогу сериализовать класс Ruff.

class Program
{
    class Meow
    {
        public string Rawr { get; set; } = "X";
    }
    class Ruff
    {
        public Meow[] Grr { get; set; } = new[] { new Meow(), new Meow() };
    }
    static class Serializer<T>
    {
        static int DoSomething(string value, Stream stream) => value.Length;
        static int DoSomethingElse(T[] values, Stream stream) => values.Length;

        public static Func<T, Stream, int> GetSerializer()
        {
            var firstProperty = typeof(T).GetProperties()[0].GetGetMethod();

            var typeParam = Expression.Parameter(typeof(T));
            var compiledGetter = Expression
                .Lambda(
                    Expression.Call(typeParam, firstProperty),
                    typeParam
                )
                .Compile();

            var returnType = firstProperty.ReturnType;

            if (returnType == typeof(string))
            {
                var getString = (Func<T, string>)compiledGetter;
                return (T item, Stream stream) => DoSomething(getString(item), stream);
            }
            if (returnType.IsArray)
            {
                // var getArray = (Func<T, returnType>)compiledGetter;

                var elementType = returnType.GetElementType();

                // return (T item, Stream stream) => 
                //    Serializer<elementType>.DoSomethingElse(getArray(item), stream))
            }
            return (T item, Stream stream) => 0;
        }
    }
    static void Main(string[] args)
    {
        MemoryStream s = new MemoryStream();
        Console.WriteLine(Serializer<Meow>.GetSerializer()(new Meow(), s));
        Console.WriteLine(Serializer<Ruff>.GetSerializer()(new Ruff(), s));
        Console.ReadKey();
        // Should print "1", "2"
        // Currently prints "1", "0"
    }
}

Сериализация Meow - это просто. Функция возьмет T и Stream, извлечет string из T и передаст их в DoSomething(string, Stream) для возврата bool.

Но при сериализации Ruff встречается свойство с типом возврата Meow[]. Чтобы сериализовать его, нужно взять T и Stream, извлечь массив элемента неизвестного типа из T и передать их в Serializer<Meow>.DoSomethingElse(Meow[], Stream)

Закомментированные строки показывают суть того, что, я думаю, должно произойти. Но как я могу создать скомпилированный Expression для всего этого и, наконец, вернуть Func<T, Stream, bool>?

РЕДАКТИРОВАТЬ: тестовый код теперь включен. При реализации сериализатор Ruff должен выплевывать 2, длину массива.

РЕДАКТИРОВАТЬ # 2: РЕШЕНИЕ! благодаря Джефф Меркадо

Ниже приведен рабочий код (только метод GetSerializer)

        public static Func<T, Stream, int> GetSerializer()
        {
            var itemTypeExpression = Expression.Parameter(typeof(T));
            var streamTypeExpression = Expression.Parameter(typeof(Stream));

            var firstProperty = typeof(T).GetProperties().First();
            var propType = firstProperty.PropertyType;

            var getterExpression = Expression.Lambda(
                Expression.Property(itemTypeExpression, firstProperty),
                itemTypeExpression
                );

            Expression body = null;

            if (propType == typeof(string))
            {
                body = Expression.Call(
                    typeof(Serializer<T>),
                    nameof(DoSomething),
                    Type.EmptyTypes,
                    Expression.Invoke(
                        getterExpression,
                        itemTypeExpression
                        ),
                    streamTypeExpression
                    );
            }
            else if (propType.IsArray)
            {
                var elementType = propType.GetElementType();
                var elementTypeExpression = Expression.Parameter(elementType);

                var serializerType = typeof(Serializer<>).MakeGenericType(elementType);
                var serializerTypeExpression = Expression.Parameter(serializerType);

                body = Expression.Call(
                    serializerType,
                    nameof(DoSomethingElse),
                    Type.EmptyTypes,
                    Expression.Invoke(
                        getterExpression,
                        itemTypeExpression
                        ),
                    streamTypeExpression
                    );
            }
            if (body != null)
                return Expression.Lambda<Func<T, Stream, int>>(body, itemTypeExpression, streamTypeExpression).Compile();
            return (T item, Stream stream) => 0;
        }

1 Ответ

0 голосов
/ 13 января 2019

У вас есть пара проблем: при построении выражения ваш тип Ruff не будет удовлетворять этому выражению.

Вы эффективно строите это выражение:

(Rawr arg0, Stream arg1) =>
    Serializer<Ruff>.DoSomethingElse(arg0.Grr, arg1);

Обратите внимание, что тип arg0.Grr равен Meow[], но ожидаемый тип Ruff[]. Вместо этого метод DoSomethingElse() должен быть универсальным, чтобы быть совместимым.

static int DoSomethingElse<TValue>(TValue[] values, Stream stream) => values.Length;

С другой стороны, кажется, что на самом деле не имеет значения, что является базовым типом, вам просто нужна длина массива. Так что вместо этого вы можете просто сделать его Array, и он все равно будет работать.

static int DoSomethingElse(Array values, Stream stream) => values.Length;

В целом, я бы не стал смешивать различные типы выражений таким образом (объекты выражений и лямбда-выражения), ни строить их все, используя лямбда-выражения, либо все, используя выражения. Я бы написал этот метод так:

public static Func<T, Stream, int> GetSerializer()
{
    var firstProperty = typeof(T).GetProperties().First();
    var item = Expression.Parameter(typeof(T));
    var stream = Expression.Parameter(typeof(Stream));
    var propType = firstProperty.PropertyType;
    if (typeof(string).IsAssignableFrom(propType))
    {
        var body = Expression.Call(
            typeof(Serializer<T>),
            "DoSomething",
            Type.EmptyTypes,
            Expression.Invoke(
                MakeGetter(firstProperty),
                item
            ),
            stream
        );
        return Expression.Lambda<Func<T, Stream, int>>(body, item, stream).Compile();
    }
    if (typeof(Array).IsAssignableFrom(propType))
    {
        var body = Expression.Call(
            typeof(Serializer<T>),
            "DoSomethingElse",
            Type.EmptyTypes,
            Expression.Invoke(
                MakeGetter(firstProperty),
                item
            ),
            stream
        );
        return Expression.Lambda<Func<T, Stream, int>>(body, item, stream).Compile();
    }
    return (T arg0, Stream arg1) => 0;

    Expression MakeGetter(PropertyInfo prop)
    {
        var arg0 = Expression.Parameter(typeof(T));
        return Expression.Lambda(
            Expression.Property(arg0, prop),
            arg0
        );
    }
}

Исходя из ваших комментариев, для меня имеет смысл сделать методы обобщенными, а не сериализатором. Вам просто нужно будет сделать соответствующие выражения для общих вызовов.

static class Serializer
{
    static int DoSomething(string value, Stream stream) => value.Length;
    static int DoSomethingElse<T>(T[] values, Stream stream) => values.Length;

    public static Func<T, Stream, int> GetSerializer<T>()
    {
        var firstProperty = typeof(T).GetProperties().First();
        var item = Expression.Parameter(typeof(T));
        var stream = Expression.Parameter(typeof(Stream));
        var propType = firstProperty.PropertyType;
        if (typeof(string).IsAssignableFrom(propType))
        {
            var body = Expression.Call(
                typeof(Serializer),
                "DoSomething",
                Type.EmptyTypes,
                Expression.Invoke(
                    MakeGetter(firstProperty),
                    item
                ),
                stream
            );
            return Expression.Lambda<Func<T, Stream, int>>(body, item, stream).Compile();
        }
        if (typeof(Array).IsAssignableFrom(propType))
        {
            var body = Expression.Call(
                typeof(Serializer),
                "DoSomethingElse",
                new[] { propType.GetElementType() },
                Expression.Invoke(
                    MakeGetter(firstProperty),
                    item
                ),
                stream
            );
            return Expression.Lambda<Func<T, Stream, int>>(body, item, stream).Compile();
        }
        return (T arg0, Stream arg1) => 0;

        Expression MakeGetter(PropertyInfo prop)
        {
            var arg0 = Expression.Parameter(typeof(T));
            return Expression.Lambda(
                Expression.Property(arg0, prop),
                arg0
            );
        }
    }
}
...