.net 未调用静态构造函数

bogh5gae  于 2022-12-01  发布在  .NET
关注(0)|答案(3)|浏览(179)

我尝试使用AutoMapper进行模型-视图模型Map,并希望在该类型的静态构造函数中执行一次Map配置。当用该类型调用Mapper.Map(AutoMapper)时,不会调用该类型的静态构造函数。
我的理解是Mapper.Map会尝试通过反射访问类型及其成员,并且在第一次尝试使用时会调用静态构造函数。这是一些基本的东西,但对我的理解提出了挑战。提供了代码片段。

class SampleViewModel 
{
    static SampleViewModel()
    {
        Mapper.Initialize(cfg => cfg.CreateMap<Sample, SampleViewModel>().ReverseMap());
    }

    public SampleViewModel()
    {
    }

    public int PropertyA { get; set; }
    public int PropertyB { get; set; }
}

 Sample s = new Sample { PropertyA = 10, PropertyB = 20 };
 var obj = Mapper.Map<SampleViewModel>(s); // fails

当第一次通过反射访问类型和成员时,是否调用了静态构造函数(如果提供)?

1mrurvl1

1mrurvl11#

您没有访问SampleViewModel的任何成员-仅引用类型本身是不够的。
Mapper.Map只访问它自己内部的Map“字典”--在它到达处理SampleViewModel的点之前,它失败了。静态构造函数从不运行,所以它不能将“它自己”添加到Mapper中。
现在,如果这不涉及反射,那么调用静态构造函数是正确的--仅仅是因为它会在包含访问的方法的编译过程中发生,例如:

var obj = Mapper.Map<SampleViewModel>(s);
Console.WriteLine(obj.SomeField);

在这种情况下,由于该方法引用了SampleViewModel上的一个字段,因此SampleViewModel的静态构造函数将在包含方法的JIT编译过程中被调用,这样,Mapper.Map<SampleViewModel>(s)行将正确执行,因为Map现在已经存在。不用说 * 这不是解决问题的正确方法 *。它只会使代码变得非常难以维护:)

**免责声明:*即使这样做现在就可以解决问题,但它取决于Windows上MS.NET的当前实现中的非契约行为。契约指定在对类型成员进行任何访问之前调用类型初始值设定项,但这仍然意味着CIL的有效实现可能只在Mapper.Map * 之后调用类型初始值设定项,只要它发生在obj.SomeField之前-即使这样,如果编译器可以确保这样做是安全的,obj.SomeField也可能被优化掉。 强制 * 调用类型初始值设定项的唯一真正方法是调用RuntimeHelpers.RunClassConstructor,但到那时,您还可以添加一个静态Init方法或其他方法。

真正的问题是,你不应该一开始就在静态构造函数中初始化这样的东西。Map应该在某种确定性的初始化过程中设置,比如说,显式调用InitMappings方法。否则,你就向一个巨大的海森堡错误敞开了大门,更不用说CLR中的细微变化会无缘无故地破坏你的整个应用程序。
静态构造函数并不意味着“注册”,只是类型本身的初始化--其他任何东西都是滥用,会给您(或.NET兼容性团队)带来麻烦。

7cwmlq89

7cwmlq892#

静态构造函数在类的第一个示例创建之前或访问该类型的任何静态成员之前的实现定义的时间运行。请参阅When is a static constructor called in C#?
Map器试图在执行示例化要Map到的类的工作 * 之前 * 找到Map,因此找不到Map,因为在那之前类从未示例化过。
只需将Map初始化代码移动到AutoMapperBootstrap.cs文件或其他文件中,然后在应用程序初始化中调用它。

3z6pesqy

3z6pesqy3#

我测试了您的场景,观察到在创建对象的第一个示例之前确实调用了静态构造函数。
C# Programming Guide: Static Constructors
我还通过添加一个返回IMapper的GetMapper函数修改了示例代码。这对于日常的简单Map来说似乎有点过头了,但是如果我们需要一个对象来提供它的Map器,也许我们可以从一个静态方法中获得它。
可以很容易地将创建Mapper对象的责任转移到工厂或DI容器,为了简单起见,我在这里没有包括这些。
值得注意的是,在本例中,静态字段在静态构造函数之前初始化,该构造函数在静态只读字段初始化之后立即调用。
使用AutoMapper v12.0和.Net Core 3.1。

public class SimpleObjectToMap
{
    private static readonly MapperConfiguration _simpleObjMapperConfig = new MapperConfiguration(
            config => config.CreateMap<SimpleObjectToMap, ObjectToBeMappedTo>());

    private static readonly IMapper _mapper = new Mapper(_simpleObjMapperConfig);

    private int _propertyA;
    public int PropertyA
    {
        get
        {
            Console.WriteLine("ObjectToMap.PropertyA.Get");
            return _propertyA;
        }

        set { _propertyA = value; }
    }

    static SimpleObjectToMap()
    {
        Console.WriteLine("*** ObjectToMap static ctor called ***");
    }

    public static IMapper GetMapper()
    {
        return _mapper;
    }
}

public class ObjectToBeMappedTo
{
    static ObjectToBeMappedTo()
    {
        Console.WriteLine("*** ObjectToBeMappedTo static ctor called ***");
    }

    private int _propertyA;
    public int PropertyA
    {
        get { return _propertyA; }

        set
        {
            Console.WriteLine("ObjectToBeMappedTo.PropertyA.Set");
            _propertyA = value;
        }
    }
}

public class TestObjectMappingWithStaticCtor
{
    public void TestWithStaticCtor()
    {
        SimpleObjectToMap objToMap = new SimpleObjectToMap { PropertyA = 27 };
        var mappedObject = SimpleObjectToMap.GetMapper().Map<ObjectToBeMappedTo>(objToMap);
    }
}

相关问题