我正在尝试创建自定义序列化程序。它需要性能。
想法是为每种类型构造并缓存一些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
进行序列化很容易。该函数将使用T
和Stream
,从string
中提取T
,并将它们传递到DoSomething(string, Stream)
中以返回布尔值。
但是在序列化Ruff
时,它遇到一个返回类型为Meow[]
的属性。要对其进行序列化,需要使用T
和Stream
,从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;
}
答案 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
);
}
}
}