Почему Automapper сопоставляет конкретную коллекцию с новым экземпляром при каждой ссылке на исходную коллекцию?

У меня есть исходный объект, который содержит 2 ссылки на одну и ту же коллекцию. Если я сопоставлю исходный тип со структурно-эквивалентным целевым типом, AutoMapper создаст два экземпляра коллекции в целевом экземпляре.

class SourceThing
{
    public string Name { get; set; }
    public List<int> Numbers { get; set; }
    public List<int> MoreNumbers { get; set; }
}

class TargetThing
{
    public string Name { get; set; }
    public List<int> Numbers { get; set; }
    public List<int> MoreNumbers { get; set; }
}

Если я создам SourceThing который имеет две ссылки на один и тот же список, сопоставьте его с TargetThing, результат TargetThing с двумя отдельными экземплярами коллекции.

public void MapObjectWithTwoReferencesToSameList()
{
    Mapper.CreateMap<SourceThing, TargetThing>();
    //Mapper.CreateMap<List<int>, List<int>>(); // passes when mapping here

    var source = new SourceThing() { Name = "source" };
    source.Numbers = new List<int>() { 1, 2, 3 };
    source.MoreNumbers = source.Numbers;
    Assert.AreSame(source.Numbers, source.MoreNumbers);

    var target = Mapper.Map<TargetThing>(source);
    Assert.IsNotNull(target.Numbers);
    Assert.AreSame(target.Numbers, target.MoreNumbers); // fails
}

Должно ли это быть поведением отображения по умолчанию для конкретных коллекций в AutoMapper? Через тестирование я понял, что если я сопоставил List<int> в List<int>Я добиваюсь того поведения, которое хочу, но не понимаю почему. Если AutoMapper отслеживает ссылки и не отображает сопоставленный объект, он не увидит, что source.MoreNumbers указывает на тот же список, что и source.Numbersи установить цель соответственно?

2 ответа

Решение

Я сделал еще несколько исследований и мастерить. Внутренне, поскольку механизм отображения проходит по графу объектов, он выбирает лучший модуль отображения для каждого типа источника / типа назначения. Если нет нестандартного сопоставления (упрощенно), движок затем будет искать зарегистрированного сопоставителя для типа источника и назначения. Если он находит его, он создает целевой объект, а затем обходит и сопоставляет все свойства. Он также помещает этот целевой объект в ResolutionContext.InstanceCache, который является Dictionary<ResolutionContext, object>, Если тот же исходный объект встречается снова в том же вызове корневого отображения, он извлечет объект из кэша, вместо того, чтобы тратить время на повторное сопоставление.

Однако, если нет зарегистрированного преобразователя, двигатель выбирает следующий применимый преобразователь, который в этом случае является AutoMapper.Mappers.CollectionMapper, Отображатель коллекции создает целевую коллекцию, перечисляет исходную коллекцию и сопоставляет каждый элемент. Он не добавляет целевой объект в кеш. Это явно дизайн.

Контекст разрешения

Что мне действительно интересно, так это то, как объекты кэшируются в InstanceCache. Ключом является текущий ResolutionContext, который содержит тип источника и назначения, а также значение источника. ResolutionContext переопределяет GetHashCode() и Equals(), которые используют те же методы базового значения источника. Я могу определить равенство в пользовательском классе так, чтобы исходная коллекция с несколькими равными, но различными экземплярами этого класса отображалась в коллекцию с несколькими ссылками на один и тот же экземпляр.

Этот класс:

class EquatableThing 
{
    public string Name { get; set; }

    public override bool Equals(object other)
    {
        if (ReferenceEquals(this, other)) return true;
        if (ReferenceEquals(null, other)) return false;

        return this.Name == ((EquatableThing)other).Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

Сопоставьте коллекцию с 2 одинаковыми (но отдельными) вещами, и в результате вы получите коллекцию с 2 указателями на одну и ту же вещь!

    public void MapCollectionWithTwoEqualItems()
    {
        Mapper.CreateMap<EquatableThing, EquatableThing>();

        var thing1 = new EquatableThing() { Name = "foo"};
        var thing2 = new EquatableThing() { Name = "foo"};

        Assert.AreEqual(thing1, thing2);
        Assert.AreEqual(thing1.GetHashCode(), thing2.GetHashCode());
        Assert.AreNotSame(thing1, thing2);

        // create list and map this thing across
        var list = new List<EquatableThing>() { thing1, thing2};
        var result = Mapper.Map<List<EquatableThing>, List<EquatableThing>>(list);
        Assert.AreSame(result[0], result[1]);
    }

Сохранить ссылки

Я, например, задаюсь вопросом, почему поведение AutoMapper по умолчанию не будет отображать граф объекта как можно ближе к структуре назначения. N исходных объектов приводит к N целевым объектам. Но так как это не так, я хотел бы видеть опцию в методе Map для Preserve References, как это сделал бы сериализатор. Если выбрана эта опция, то каждая отображаемая ссылка помещается в Словарь с использованием средства сравнения равенств ссылок и исходного объекта для ключа и пункта назначения в качестве значения. По сути, если что-то уже сопоставлено, используется объект результата этой карты.

Нет ничего плохого в поведении, это просто, как карты отображаются автоматически.

В верхнем разделе вы создаете список чисел, а затем применяете его ко второму списку чисел. Затем вы можете сравнить, и они одинаковы, потому что объект имеет 2 указателя на один и тот же список. Он не копировал цифры, он просто сделал новую ссылку, как вы и просили.

Теперь перейдите к автомату. Он проходит и отображает от одного объекта до эквивалентного объекта. Он отображает каждое из свойств отдельно, копируя информацию. Таким образом, даже несмотря на то, что в источнике есть указатели на номера в виде указателя на один и тот же список, Autopper отображает каждое из них отдельно. Зачем? Это отображение свойств, а не изучение указателей свойств. И вы бы не хотели, чтобы это делалось в большинстве случаев.

Имеет ли это смысл?

Если конечная цель состоит в том, чтобы пройти тестирование, вопрос заключается не в том, "делать числа и больше чисел, указывающих на один и тот же объект", а в том, чтобы "делать числа и больше чисел, содержащих точно такой же список". В первом случае ответом на оба вопроса является "да", поскольку существует один объект (список) для чисел и большего числа чисел. Во втором ответ - ложь, затем истина, поскольку список эквивалентен, но он не указывает на тот же самый точный объект.

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

Другие вопросы по тегам