Маппинг классов в C# по имени свойств?

113
26 ноября 2020, 20:30

Как на C# или через LINQ сделать такое:

class A
{
    public string First { get; set; }
    public string Second { get; set; }
    //...
    //другие свойства
};
class B
{
    public string Third { get; set; }
    public string Four { get; set; }
    //...
    //другие свойства
};
class Result
{
    public string First { get; set; }
    public string Second { get; set; }
    public string Third { get; set; }
    public string Four { get; set; }
    //...
    //другие свойства
};

Как примапить A & B к Result?

Можно конечно написать вручную:

r.First = a.First;
r.Second = a.Second;
r.Third = b.Third;
r.Four = b.Four;

Но так слишком много писать, хотелось бы по короче и побыстрее.

Answer 1

Ответы-ссылки тут не приветствуются, но в этом случае без ссылки никак: AutoMapper.

Package Manager:

Install-Package AutoMapper

C#:

Mapper.CreateMap<A, Result>();
Mapper.CreateMap<B, Result>();
var a = new A() { First = "1", Second = "2" };
var b = new B() { Third = "3",  Four = "4" };
var res = Mapper.Map<Result>(a);
Mapper.Map(b, res); // 1, 2, 3, 4

или с динамическим маппингом:

var a = new A() { First = "1", Second = "2" };
var b = new B() { Third = "3",  Four = "4" };
var res = Mapper.DynamicMap<Result>(a);
Mapper.DynamicMap(b, res);
Answer 2

В таких случаях обычно используют рефлексию. Простой пример копирования публичных свойств с идентичными типами:

static TTarget Copy<TTarget>(object source)
    where TTarget : new()
{
    if (source == null)
        return default(TTarget);
    const BindingFlags publicInstance = BindingFlags.Public | BindingFlags.Instance;
    var result = new TTarget();
    var targetType = typeof(TTarget);
    var sourceProperties = source.GetType().GetProperties(publicInstance);
    foreach (var sourceProperty in sourceProperties)
    {
        if (sourceProperty.CanRead)
        {
            var targetProperty = targetType.GetProperty(sourceProperty.Name, publicInstance);
            if (targetProperty != null && sourceProperty.PropertyType == targetProperty.PropertyType)
                targetProperty.SetValue(sourceProperty.GetValue(source), result);
        }
    }
    return result;
}

Проблема в таких мапперах заключается в том, что они требуют серьёзного тестирования для разнообразных граничных случаев и нетривиальных преобразований типа. В качестве работающей альтернативы действительно хорошо подходит автомаппер, который уже хорошо протестирован.

Answer 3

Вот тот же пример для .NET 4

static TTarget Copy<TTarget>(object source) where TTarget : new() {
        if (source == null)
            return default(TTarget);
        const BindingFlags publicInstance = BindingFlags.Public | BindingFlags.Instance;
        TTarget result = new TTarget();
        Type targetType = typeof(TTarget);
        PropertyInfo[] sourceProperties = source.GetType().GetProperties(publicInstance);
        foreach (PropertyInfo sourceProperty in sourceProperties) {
            if (sourceProperty.CanRead) {
                PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name, publicInstance);
                if (targetProperty != null && sourceProperty.PropertyType == targetProperty.PropertyType) {
                    object[] myArgs = new object[1] { sourceProperty.GetGetMethod().Invoke(source, null) };
                    targetProperty.GetSetMethod().Invoke(result, myArgs);
                }
            }
        }
        return result;
    }
Answer 4

Вот доработанная версия для .NET 4, теперь копируются и подклассы и конвертируются массивы. Код конечно очень плохо написан.

private class MDS_Tools {
    public static TTarget Copy<TTarget>(object source) where TTarget : new() {
        if (source == null)
            return default(TTarget);
        const BindingFlags publicInstance = BindingFlags.Public | BindingFlags.Instance;
        TTarget result = new TTarget();
        Type targetType = typeof(TTarget);
        PropertyInfo[] sourceProperties = source.GetType().GetProperties(publicInstance);
        foreach (PropertyInfo sourceProperty in sourceProperties) {
            if (sourceProperty.CanRead) {
                PropertyInfo targetProperty = targetType.GetProperty(sourceProperty.Name, publicInstance);
                if (targetProperty != null) {
                    MethodInfo smi = sourceProperty.GetGetMethod();
                    if (sourceProperty.PropertyType == targetProperty.PropertyType) {
                        object[] myArgs = new object[1] { smi.Invoke(source, null) };
                        targetProperty.GetSetMethod().Invoke(result, myArgs);
                    }
                    else {
                        if (smi.ReturnType.GetInterfaces().Where(t => t.Name == "IEnumerable").FirstOrDefault() != null) {
                            object arrToCopy = Activator.CreateInstance(targetProperty.GetGetMethod().ReturnType);
                            MethodInfo addMeth = arrToCopy.GetType().GetMethod("Add");
                            IEnumerable enAr = (IEnumerable)smi.Invoke(source, null);
                            if (enAr != null) {
                                foreach (var s in (IEnumerable)smi.Invoke(source, null)) {
                                    addMeth.Invoke(arrToCopy, new object[1] { typeof(MDS_Tools).GetMethod("Copy").MakeGenericMethod(((IEnumerable)arrToCopy).AsQueryable().ElementType).Invoke(null, new object[] { s }) });
                                }
                                object[] myArgs = new object[1] { arrToCopy };
                                targetProperty.GetSetMethod().Invoke(result, myArgs);
                            }
                        }
                        else {
                            if (smi.ReturnType != typeof(Object)) {
                                object[] myArgs = new object[1] {
                                            typeof(MDS_Tools).GetMethod("Copy")
                                                .MakeGenericMethod(targetProperty.GetGetMethod().ReturnType)
                                                .Invoke(targetProperty, new object[] { smi.Invoke(source, null) })
                                        };
                                targetProperty.GetSetMethod().Invoke(result, myArgs);
                            }
                        }
                    }
                }
            }
        }
        return result;
    }
}
READ ALSO
Размещение remarks документации к методам

Размещение remarks документации к методам

Скажите, куда необходимо помещать блок <remarks>

81
Можно ли с помощью google drive получать видео для unity

Можно ли с помощью google drive получать видео для unity

Ищу варианты для получения видео с облачного хранилищаПришла мысль, что может как то можно получить ссылку на видео с гугл диска в приложение

103
Навсегда свернуть #region или часть кода

Навсегда свернуть #region или часть кода

Часто бывает так, что когда в коде создаешь пару из #region #endregion то после перезапуска VS, или даже просто запуска приложения для отладки (и по другим...

96
Ошибка HRESULT: 0x80131040 при создании миграции

Ошибка HRESULT: 0x80131040 при создании миграции

когда я делаю миграцию выдаёт такую ошибку:

76