C#创建一个包含UnixTimestamp帮助程序方法的DynamicAssembly

示例

本示例通过生成使用现有成员和新创建成员以及基本异常处理的代码,展示了ILGenerator的用法。以下代码发出一个DynamicAssembly,其中包含与此c#代码等效的内容:

public static class UnixTimeHelper
{
    private readonly static DateTime EpochTime = new DateTime(1970, 1, 1);

    public static int UnixTimestamp(DateTime input)
    {
        int totalSeconds;
        try
        {
            totalSeconds = checked((int)input.Subtract(UnixTimeHelper.EpochTime).TotalSeconds);
        }
        catch (OverflowException overflowException)
        {
            throw new InvalidOperationException("It's to late for an Int32 timestamp.", overflowException);
        }
        return totalSeconds;
    }
}


//获取所需的方法
var dateTimeCtor = typeof (DateTime)
    .GetConstructor(new[] {typeof (int), typeof (int), typeof (int)});
var dateTimeSubstract = typeof (DateTime)
    .GetMethod(nameof(DateTime.Subtract), new[] {typeof (DateTime)});
var timeSpanSecondsGetter = typeof (TimeSpan)
    .GetProperty(nameof(TimeSpan.TotalSeconds)).GetGetMethod();
var invalidOperationCtor = typeof (InvalidOperationException)
    .GetConstructor(new[] {typeof (string), typeof (Exception)});

if (dateTimeCtor == null || dateTimeSubstract == null ||
    timeSpanSecondsGetter == null || invalidOperationCtor == null)
{
    throw new Exception("Could not find a required Method, can not create Assembly.");
}

//设置所需的成员
var an = new AssemblyName("UnixTimeAsm");
var dynAsm = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave);
var dynMod = dynAsm.DefineDynamicModule(an.Name,an.Name+ ".dll");

var dynType = dynMod.DefineType("UnixTimeHelper",
   TypeAttributes.Abstract|TypeAttributes.Sealed| TypeAttributes.Public);

var epochTimeField = dynType.DefineField("EpochStartTime", typeof (DateTime),
   FieldAttributes.Private|FieldAttributes.Static| FieldAttributes.InitOnly);

var cctor =
    dynType.DefineConstructor(
       MethodAttributes.Private|MethodAttributes.HideBySig|MethodAttributes.SpecialName|
       MethodAttributes.RTSpecialName| MethodAttributes.Static, CallingConventions.Standard,
        Type.EmptyTypes);

var cctorGen = cctor.GetILGenerator();
cctorGen.Emit(OpCodes.Ldc_I4, 1970); //将DateTime构造函数参数加载到堆栈上
cctorGen.Emit(OpCodes.Ldc_I4_1);
cctorGen.Emit(OpCodes.Ldc_I4_1);
cctorGen.Emit(OpCodes.Newobj, dateTimeCtor); //调用构造函数
cctorGen.Emit(OpCodes.Stsfld, epochTimeField); //将对象存储在静态字段中   
cctorGen.Emit(OpCodes.Ret);

var unixTimestampMethod = dynType.DefineMethod("UnixTimestamp",
   MethodAttributes.Public|MethodAttributes.HideBySig| MethodAttributes.Static,
    CallingConventions.Standard, typeof (int), new[] {typeof (DateTime)});

unixTimestampMethod.DefineParameter(1, ParameterAttributes.None, "input");

var methodGen = unixTimestampMethod.GetILGenerator();
methodGen.DeclareLocal(typeof (TimeSpan));
methodGen.DeclareLocal(typeof (int));
methodGen.DeclareLocal(typeof (OverflowException));

methodGen.BeginExceptionBlock(); //开始尝试块
methodGen.Emit(OpCodes.Ldarga_S, (byte) 0); //要在结构上调用方法,我们需要加载它的地址
methodGen.Emit(OpCodes.Ldsfld, epochTimeField);
    //加载我们作为以下调用的参数创建的静态字段的对象
methodGen.Emit(OpCodes.Call, dateTimeSubstract); //在输入的DateTime上调用substract方法
methodGen.Emit(OpCodes.Stloc_0); //将生成的TimeSpan存储在本地
methodGen.Emit(OpCodes.Ldloca_S, (byte) 0); //加载本地地址以在其上调用方法
methodGen.Emit(OpCodes.Call, timeSpanSecondsGetter); //在TimeSpan上调用TotalSeconds Get方法
methodGen.Emit(OpCodes.Conv_Ovf_I4); //将结果转换为Int32; 在溢出时引发异常
methodGen.Emit(OpCodes.Stloc_1); //存储结果供以后返回
//跳转到catch块后面的请假指令将自动发出
methodGen.BeginCatchBlock(typeof (OverflowException)); //开始捕获块
//当我们在这里时,抛出了OverflowException,它现在已经在堆栈中
methodGen.Emit(OpCodes.Stloc_2); //将异常存储在本地。
methodGen.Emit(OpCodes.Ldstr, "It's to late for an Int32 timestamp.");
    //将我们的错误消息加载到堆栈上
methodGen.Emit(OpCodes.Ldloc_2); //再次加载异常
methodGen.Emit(OpCodes.Newobj, invalidOperationCtor);
    //使用我们的消息和内部Exception创建一个InvalidOperationException
methodGen.Emit(OpCodes.Throw); //抛出创建的异常
methodGen.EndExceptionBlock(); //结束挡块
//当我们在这里,一切都很好
methodGen.Emit(OpCodes.Ldloc_1); //加载结果值
methodGen.Emit(OpCodes.Ret); //把它返还

dynType.CreateType();

dynAsm.Save(an.Name + ".dll");