You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

动态生成MSIL中DateTime.ToString的异常行为问题

问题分析与解决方案

看起来你的Reflection Emit实现中,针对DateTime值类型属性的处理存在IL指令调用的小问题,导致获取的结果异常。我们一步步拆解问题并修复:

核心问题原因

你的代码中对所有属性的get方法都使用了Callvirt指令,但对于值类型属性(比如DateTime)的get方法来说,它是非虚方法,Callvirt虽然在多数情况下能运行,但在.NET 4.7.1中可能会导致值类型返回值的栈处理异常,从而得到错误的结果。另外,通过拼接方法名获取get方法的方式也不够可靠。

修复后的完整代码

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Globalization;

public delegate string F(A obj);
public delegate string FWithFormat(A obj, IFormatProvider formatProvider);

public abstract class A 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
}

public class B : A 
{ 
    public DateTime Timestamp { get; set; } 
}

public static class PropertyExtractor
{
    // 原需求:无参数ToString
    public static void ExtractProperty(string propertyName, A obj)
    {
        var type = obj.GetType();
        var property = type.GetProperty(propertyName);
        if (property == null)
            throw new ArgumentException($"属性 {propertyName} 在类型 {type} 中不存在");
        
        // 改用PropertyInfo获取get方法,比拼接方法名更可靠
        var getMethod = property.GetGetMethod();
        if (getMethod == null)
            throw new InvalidOperationException($"属性 {propertyName} 没有公开的get访问器");

        var dynamicMethod = new DynamicMethod("PropertyExtractor", typeof(string), 
            new[] { typeof(A) }, type, true);
        var ilGen = dynamicMethod.GetILGenerator();

        // 加载参数并转换为实际类型
        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Castclass, type);

        // 根据方法是否为虚方法选择调用指令
        if (getMethod.IsVirtual)
            ilGen.Emit(OpCodes.Callvirt, getMethod);
        else
            ilGen.Emit(OpCodes.Call, getMethod);

        // 获取正确的ToString方法:优先用当前类型的重写方法
        var toStringMethod = property.PropertyType.GetMethod("ToString", Type.EmptyTypes) 
                             ?? typeof(object).GetMethod("ToString", Type.EmptyTypes);
        if (toStringMethod == null)
            throw new InvalidOperationException("未找到ToString方法");

        // 值类型用Call,引用类型用Callvirt
        if (property.PropertyType.IsValueType)
            ilGen.Emit(OpCodes.Call, toStringMethod);
        else
            ilGen.Emit(OpCodes.Callvirt, toStringMethod);

        ilGen.Emit(OpCodes.Ret);

        var @delegate = (F)dynamicMethod.CreateDelegate(typeof(F));
        var result = @delegate(obj);
        Console.WriteLine($"提取结果:{result}");
    }

    // 扩展支持带IFormatProvider的ToString
    public static void ExtractPropertyWithFormat(string propertyName, A obj, IFormatProvider formatProvider)
    {
        var type = obj.GetType();
        var property = type.GetProperty(propertyName);
        if (property == null)
            throw new ArgumentException($"属性 {propertyName} 在类型 {type} 中不存在");
        
        var getMethod = property.GetGetMethod();
        if (getMethod == null)
            throw new InvalidOperationException($"属性 {propertyName} 没有公开的get访问器");

        var dynamicMethod = new DynamicMethod("PropertyExtractorWithFormat", typeof(string), 
            new[] { typeof(A), typeof(IFormatProvider) }, type, true);
        var ilGen = dynamicMethod.GetILGenerator();

        ilGen.Emit(OpCodes.Ldarg_0);
        ilGen.Emit(OpCodes.Castclass, type);

        if (getMethod.IsVirtual)
            ilGen.Emit(OpCodes.Callvirt, getMethod);
        else
            ilGen.Emit(OpCodes.Call, getMethod);

        // 加载格式提供者参数
        ilGen.Emit(OpCodes.Ldarg_1);

        // 获取带参数的ToString方法
        var toStringMethod = property.PropertyType.GetMethod("ToString", new[] { typeof(IFormatProvider) }) 
                             ?? typeof(object).GetMethod("ToString", new[] { typeof(IFormatProvider) });
        if (toStringMethod == null)
            throw new InvalidOperationException("未找到带IFormatProvider参数的ToString方法");

        if (property.PropertyType.IsValueType)
            ilGen.Emit(OpCodes.Call, toStringMethod);
        else
            ilGen.Emit(OpCodes.Callvirt, toStringMethod);

        ilGen.Emit(OpCodes.Ret);

        var @delegate = (FWithFormat)dynamicMethod.CreateDelegate(typeof(FWithFormat));
        var result = @delegate(obj, formatProvider);
        Console.WriteLine($"带格式提取结果:{result}");
    }
}

// 调用示例
class Program
{
    static void Main()
    {
        var obj = new B { Id = 1, Name = "SomeName", Timestamp = DateTime.Now };
        PropertyExtractor.ExtractProperty("Timestamp", obj);
        PropertyExtractor.ExtractPropertyWithFormat("Timestamp", obj, CultureInfo.InvariantCulture);
    }
}

关键优化点

  1. 可靠获取get方法:使用PropertyInfo.GetGetMethod()替代拼接get_{propertyName}的方式,避免因特殊命名规则导致的方法查找失败。
  2. 区分Call/Callvirt指令:根据方法是否为虚方法选择对应的调用指令,值类型属性的get方法用Call,避免栈处理异常。
  3. ToString方法的正确调用:根据属性类型是值类型还是引用类型选择调用指令,确保方法调用的正确性。
  4. 扩展支持格式提供者:新增带IFormatProvider参数的版本,解决你提到的传入格式提供者无效的问题。

额外建议

如果你的代码会频繁调用这个方法,建议缓存动态创建的委托,比如用Dictionary<Type, Dictionary<string, Delegate>>来存储已生成的委托,避免重复创建DynamicMethod带来的性能损耗。

内容的提问来源于stack exchange,提问作者Radoslav.Ivanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:23:26