【问题标题】:How do I construct and compile this C# Expression with Generic types & lambda如何使用通用类型和 lambda 构造和编译此 C# 表达式
【发布时间】:2019-01-13 04:17:42
【问题描述】:

我正在尝试创建自定义序列化程序。它需要高性能。

想法是为每种类型构造和缓存一些Func<…>

在这个简化的示例中,我成功为 STRING 类型构造了 Func,但我一直不知道如何为 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 很容易。该函数将获取TStream,从T 中提取string,并将它们传递给DoSomething(string, Stream) 以返回一个布尔值。

但是在序列化Ruff 时,它遇到了一个返回类型为Meow[] 的属性。序列化它需要取TStream,从T中提取一个未知元素类型的数组,并将它们传递给Serializer&lt;Meow&gt;.DoSomethingElse(Meow[], Stream)

注释掉的行显示了我认为需要发生的事情的要点。但是我怎样才能为这一切创建一个已编译的Expression 并最终返回一个Func&lt;T, Stream, bool&gt;

编辑:现在包含测试代码。实现时,Ruff 序列化程序应该输出2,即数组的长度。

编辑 #2:解决方案! 感谢 Jeff Mercado

下面是工作代码(只是 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;
        }

【问题讨论】:

  • 您能否在序列化Meow(显示输出)时显示示例输入代码以及您的代码工作?然后显示尽可能多的代码来序列化Ruff 以及预期的输出等?
  • 你为什么要使用表达式来构建它?当您在编译时不知道类型时,表达式很有用。这就是你在这里所期待的吗?这是一个通用的序列化器吗?
  • @Enigmativity 是的,一个有点通用的序列化程序。它将根据特殊规则序列化几十个模型类。我可以为每个定义一个序列化程序,但这会很痛苦并且难以维护。至于输入代码: Serializer.GetSerializer()(new Meow(), null)
  • 请使用输入代码编辑您的问题,以便我可以复制、粘贴和运行它。然后告诉我你期望的输出。请花点时间来做这件事,这样我就不必自己弄清楚了。那么这意味着我可以专注于给你答案而不是编写测试代码。

标签: c# lambda reflection expression


【解决方案1】:

这里有几个问题,你构建表达式的方式,你的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;

总的来说,我不会以这种方式混合不同类型的表达式(表达式对象和 lambda),要么全部使用 lambda 构建,要么全部使用表达式。我会这样写这个方法:

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

根据您的 cmets,使方法通用而不是序列化程序对我来说更有意义。您只需要对泛型调用做出适当的表达即可。

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

【讨论】:

  • 谢谢杰夫。我不是在构建表达式 Serializer.DoSomethingElse(...),而是在构建 Serializer.DoSomethingElse(...)。本质上,Ruff 的序列化器正在发现一个 Meow 类型的数组作为属性,并且需要调用 Meow Serializer 方法来序列化数组中的所有 Meow。我确实喜欢您的 Expression xor Lambda 方法,并且会在更多评论之前深入研究它们 - 谢谢!
  • 哦,我明白了。因此,对于每种新类型,您都会调用该类型的序列化程序版本。在这种情况下,将方法设为通用而不是类会更合适。我会针对这种情况进行更新。
  • 无需更新 - 感谢您的代码,我明白了。您为我提供了缺失的部分,让我了解表达式是如何工作的。我将更新我的问题以显示我想出的最终解决方案,感谢您。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多