如何使用泛型类型和lambda构造和编译此C#表达式

时间:2019-01-13 04:17:42

标签: c# lambda reflection expression

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

想法是为每种类型构造并缓存一些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,从string中提取T,并将它们传递到DoSomething(string, Stream)中以返回布尔值。

但是在序列化Ruff时,它遇到一个返回类型为Meow[]的属性。要对其进行序列化,需要使用TStream,从T中提取未知元素类型的数组,然后将它们传递到Serializer<Meow>.DoSomethingElse(Meow[], Stream)

注释行显示了我认为需要发生的事情的要点。但是,我如何才能为其全部创建一个已编译的Expression并最终返回一个Func<T, Stream, bool>

编辑:现在包括测试代码。实现后,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;
        }

1 个答案:

答案 0 :(得分: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
        );
    }
}

根据您的评论,使方法通用而不是序列化器对我来说更有意义。您只需要对通用调用进行适当的表达即可。

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