将字节数组烘焙到动态IL

时间:2019-05-20 19:31:07

标签: c# reflection cil reflection.emit

我正在通过发出IL编写面向性能的数据反序列化器。序列化的数据为UTF8,并且字段表示为字符串。

[FieldA]: 22
[FieldB]: 16

我已经编写了一个自定义阅读器,该阅读器可以正确地对序列化的数据进行标记,并在逐步浏览序列化的数据时提供ReadOnlySpan<byte>

我希望有一个静态的,内联的反序列化器,它能够打包字段的字节签名,以便我可以轻松地创建一个跳转表并设置适当的字段。

非动态代码的外观:

// byteSpan is a ReadOnlySpan<byte> containing the signature

var signatureA = Encoding.UTF8.GetBytes( "FieldA" );
var signatureB = Encoding.UTF8.GetBytes( "FieldB" );
if( byteSpan.SequenceEqual( signatureA ) )
  DoSomething();
else if ( byteSpan.SequenceEqual( signatureB ) )
  DoSomething();
...

如何生成跳转表:

var fieldSignatures = GetTypeSignatures<T>(); // Returns a Tuple<byte[], FieldInfo>
var setFieldLabels = new List<Tuple<FieldInfo, Label>>();

foreach( (byte[] signature, FieldInfo field) in fieldSignatures )
{
  var setFieldLabel = il.DefineLabel();
  setFieldLabels.Add( Tuple.Create( field, setFieldLabel ) );

  il.Emit( OpCodes.Ldloc_1 ); // Load the current ReadOnlySpan<byte>
  // Inline load byte[] signature here
  il.Emit( OpCodes.Call, METHOD_SEQUENCEEQUAL );
  il.Emit( OpCodes.Brtrue, setFieldLabel );
}

EmitFieldSetters( setFieldLabels, ref il );

是否可以将签名字节数组直接烘焙到要发出的IL中,使它们成为委托的一部分?

这些签名是在运行时根据类型信息生成的,因此在静态类中手动定义它们是不可行的。一种解决方法是定义一个新的动态AssemblyType并将字节存储在其中,但我想避免这样做,如果可能的话。

1 个答案:

答案 0 :(得分:2)

您可能想做的是将签名字节数组(byte[][])的数组作为动态方法的隐藏第一个参数传递。

您可以通过以下方式加载适当的字节数组:

// Load the first byte[][] signatures array argument
il.Emit( OpCodes.LdArg_0 );
// Load the index into the signatures array
il.Emit( OpCodes.Ldc_I4, signatureIndex );
// Fetch the signature byte[] element from the array
il.Emit( OpCodes.Ldelem_Ref );

然后,当您通过动态方法创建委托时,可以使用带有目标对象的重载,该对象成为(隐藏的)第一个参数:

var deserializerDelegate = dynamicMethod.CreateDelegate(typeof(YourDelegateType), signatures);

总而言之,请看我对上述问题的评论,该问题涉及使用替代签名查找算法,即使使用动态生成的IL,该算法也可能比线性搜索最佳。