使用C# IL.Emit动态将DateTimeOffset?转换为DateTime?时UTC值未被正确使用的问题
C# IL.Emit动态将DateTimeOffset?转换为DateTime?时UTC值未被正确使用的问题
我帮你定位到了问题的核心所在,你的IL指令在处理Nullable<DateTimeOffset>的属性调用时存在错误,导致没有正确获取到UtcDateTime值,最终转换结果不符合预期。
问题根源分析
你错误地使用了OpCodes.Ldarga_S(加载参数的内存地址)来调用Nullable<T>的HasValue和Value属性的get方法,但这两个方法是实例方法,需要传入Nullable<T>的实例本身,而非指针地址。这种错误会导致栈操作异常,或者无法正确获取到DateTimeOffset的实际值,进而UtcDateTime的转换逻辑没有被正确执行。
另外,你直接硬编码从typeof(DateTimeOffset?)获取属性方法,虽然当前场景能工作,但不够通用,最好从输入类型动态获取这些方法,避免耦合特定类型。
修正后的完整代码
下面是修复后的GenerateConverter方法,同时优化了方法获取逻辑和IL指令的正确性:
using System; using System.Reflection; using System.Reflection.Emit; public class Program { public static Func<TInputValue, TOutputValue> GenerateConverter<TInputValue, TOutputValue>() { var inputType = typeof(TInputValue); var outputType = typeof(TOutputValue); // 创建动态方法 var method = new DynamicMethod( "ConvertDateTimeOffsetToUtcDateTime", outputType, new[] { inputType }); var inputIsNullable = inputType.IsGenericType && inputType.GetGenericTypeDefinition() == typeof(Nullable<>); var outputIsNullable = outputType.IsGenericType && outputType.GetGenericTypeDefinition() == typeof(Nullable<>); var il = method.GetILGenerator(); // 动态获取所需的方法和属性 MethodInfo hasValueMethod = null; MethodInfo getValueMethod = null; var utctimeMethod = typeof(DateTimeOffset).GetProperty("UtcDateTime").GetGetMethod(); if (inputIsNullable) { // 从输入的可空类型动态获取HasValue和Value的get方法 hasValueMethod = inputType.GetProperty("HasValue").GetGetMethod(); getValueMethod = inputType.GetProperty("Value").GetGetMethod(); } Label returnNullLabel = il.DefineLabel(); Label continueLabel = il.DefineLabel(); // 处理可空输入的空值判断逻辑 if (inputIsNullable) { // 加载输入参数实例(DateTimeOffset?) il.Emit(OpCodes.Ldarg_0); // 调用HasValue判断是否有值 il.Emit(OpCodes.Call, hasValueMethod); il.Emit(OpCodes.Brfalse_S, returnNullLabel); // 再次加载输入参数实例,获取实际的DateTimeOffset值 il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, getValueMethod); } else { // 加载非可空的输入实例 il.Emit(OpCodes.Ldarg_0); } // 获取UTC DateTime值 il.Emit(OpCodes.Call, utctimeMethod); // 包装为可空输出类型 if (outputIsNullable) { var ctor = outputType.GetConstructor(new[] { typeof(DateTime) }); il.Emit(OpCodes.Newobj, ctor); } il.Emit(OpCodes.Br_S, continueLabel); // 空值分支处理 il.MarkLabel(returnNullLabel); if (outputIsNullable) { il.Emit(OpCodes.Ldnull); } else { // 非可空输出时,输入为空则抛出异常 il.Emit(OpCodes.Newobj, typeof(InvalidOperationException).GetConstructor(Type.EmptyTypes)); il.Emit(OpCodes.Throw); } il.MarkLabel(continueLabel); il.Emit(OpCodes.Ret); // 创建委托并返回 return (Func<TInputValue, TOutputValue>)method.CreateDelegate(typeof(Func<TInputValue, TOutputValue>)); } public static void Main() { var converter = GenerateConverter<DateTimeOffset?, DateTime?>(); // 测试有值的情况(东8区时间转UTC) DateTimeOffset? inputWithValue = new DateTimeOffset(2024, 5, 20, 10, 0, 0, TimeSpan.FromHours(8)); DateTime? outputWithValue = converter(inputWithValue); Console.WriteLine($"Input: {inputWithValue}, Output UTC: {outputWithValue}"); // 预期输出:Input: 2024/5/20 10:00:00 +08:00, Output UTC: 2024/5/20 2:00:00 // 测试空值情况 DateTimeOffset? inputNull = null; DateTime? outputNull = converter(inputNull); Console.WriteLine($"Input is null, Output is null: {outputNull == null}"); // 预期输出:Input is null, Output is null: True } }
关键修正点总结
- 修正Nullable
属性调用方式 :使用OpCodes.Ldarg_0加载Nullable<T>实例,而非Ldarga_S加载地址,确保实例方法能正确接收参数。 - 动态获取属性方法:从输入的可空类型动态获取
HasValue和Value的get方法,避免硬编码依赖特定类型。 - 保证栈平衡:严格控制IL指令的栈操作顺序,确保每个指令执行后栈的状态符合CLR要求,避免运行时异常。
备注:内容来源于stack exchange,提问作者Max_dev
相关产品推荐
相关产品推荐

