2016-06-08 5 views
0

Я пытаюсь использовать AutoMapper для сопоставления model-viewmodel и хотел, чтобы конфигурация сопоставления выполнялась один раз в статическом конструкторе типа. Статический конструктор типа не вызывается, когда 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 

Не статический конструктор называется (если он предусмотрен), когда тип и члены доступны через отражение в первый раз?

ответ

4

Вы не обращаетесь к любым членам SampleViewModel - этого недостаточно, чтобы просто ссылаться на сам тип.

Mapper.Map имеет доступ только к собственному внутреннему «словарю» сопоставлений - прежде чем он сможет добраться до точки, где он имеет дело с SampleViewModel, он терпит неудачу. Статический конструктор никогда не запускается, поэтому он не может добавить «сам» в Mapper.

Теперь, если это не связано с отражением, вы были бы правы, что статический конструктор будет называться - просто потому, что это произошло бы во время компиляции метода, содержащего доступ, например:

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

В этом случае, поскольку метод ссылается на поле на SampleViewModel, статический конструктор для SampleViewModel будет вызываться во время компиляции JIT содержащего метода, и как таковая строка Mapper.Map<SampleViewModel>(s) будет выполняться правильно, так как отображение теперь присутствует. Излишне говорить это неправильное решение вашей проблемы. Было бы просто сделать код абсолютно ужасен поддерживать :)

ОТКАЗ: Несмотря на то, что это может решить эту проблему прямо сейчас, это зависит от внедоговорного поведения в текущей реализации MS.NET на Windows. Контракт указывает, что инициализатор типа вызывается перед любым доступом к члену типа, но это все равно означает, что действительная реализация CIL может вызывать только инициализатор типа послеMapper.Map, если это происходит до obj.SomeField - и даже то может быть, что obj.SomeField оптимизируется, если компилятор может гарантировать, что это безопасно. Единственный реальный способ: привести в исполнение вызов инициализатора типа должен вызвать RuntimeHelpers.RunClassConstructor, но к этому моменту вы могли бы также добавить статический метод Init или что-то в этом роде.

Настоящая проблема заключается в том, что вы не должны инициализировать такие вещи, как это, в статическом конструкторе в первую очередь. Сопоставления должны быть заданы в каком-то детерминистском процессе инициализации, например, явно вызванном методе InitMappings. В противном случае вы открываете себе огромную банку из Heisenbugs, не говоря уже о тонких изменениях в CLR, разрушающих все ваше приложение без видимых причин.

Статические конструкторы просто не предназначены для «регистрации», а просто инициализации самого типа - что-то еще является злоупотреблением и вызовет проблемы (или команды совместимости .NET).

+0

Вы уверены, что редактируете? Почему инициализатор статического типа запускается во время JIT-метода, когда к нему обращается элемент _instance_? Разве инициализатор типа не запускается в определенное время реализации, поэтому он может (планироваться) работать между «Map()» и «WriteLine()»? – CodeCaster

+0

ах .. это объясняет. Картер проверяет внутреннее сопоставление, прежде чем он будет иметь дело с типом. –

+1

@CodeCaster Вот что происходит в текущих реализациях MS.NET, но вы правы, что мне нужно добавить отказ от ответственности - это не контрактное поведение, просто «случайное». По контракту инициализатор типа требуется только для вызова до доступа к члену, поэтому для реализации CIL вполне справедливо вызывать его только до того, как произойдет «obj.SomeField». Это пример того, что я имел в виду под «тонкими изменениями в CLR, разбивающими все ваше приложение»: D – Luaan

3

Статические конструкторы запускаются в определенное время реализации непосредственно перед тем, как будет создан первый экземпляр этого класса, или до того, как будет получен доступ к любому статическому члену этого типа. См. When is a static constructor called in C#?.

Mapper пытается найти сопоставление перед тем, как выполняет свою работу по созданию экземпляра класса для отображения в и, следовательно, не может найти сопоставление, потому что этот класс никогда не был создан до этого момента.

Просто переместите код инициализации отображения в файл AutoMapperBootstrap.cs или что-то еще, и вызовите это в своей инициализации приложения.