1

使用一些现有的 Mapper,是否可以:

var target = Mapper.Map(source).To<Dto>();

source具有属性和的IEnumerable<(string Foo, int Bar)>类在哪里?DtoFooBar


示例代码:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace MapFromDynamicsToComplex
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            var source = DataAccessLayer.Method();
            //var target = Mapper.Map(source).To<Dto>();
            var parameterNames = string.Join(", ", Utilities.GetValueTupleNames(typeof(DataAccessLayer), nameof(DataAccessLayer.Method)));

            Console.WriteLine(parameterNames);
            Console.ReadKey();
        }
    }

    public class DataAccessLayer
    {
        public static IEnumerable<(string Foo, int bar)> Method()
        {
            return new List<(string Foo, int bar)>
            {
                ValueTuple.Create("A", 1)
            };
        }
    }

    public class Dto
    {
        public string Foo { get; set; }
        public int Bar { get; set; }
        public object Baz { get; set; }
    }

    public static class Utilities
    {
        public static IEnumerable<string> GetValueTupleNames(Type source, string action)
        {
            var method = source.GetMethod(action);
            var attr = method.ReturnParameter.GetCustomAttribute<TupleElementNamesAttribute>();

            return attr.TransformNames;
        }
    }
}

通过使用TupleElementNamesAttribute 它可以在运行时访问值元组元素,特别是它的名称。

4

3 回答 3

1

这是一个ValueTuple使用提供方法中的项目名称的映射器。虽然这可行,但我建议使用匿名对象并从中映射会比使用 a 更好,ValueTuple除非您遇到性能问题,并且如果您遇到匿名对象的性能问题,例如使用ValueTuple帮助,您将失去任何收益反射做自动映射。另请注意,任何具有名称的嵌套元组类型都可能无法正常工作。

Utility类中,我创建了一些辅助方法来使用,MemberInfo因此您可以将字段和属性视为相同,然后使用您的方法ValueTuple从方法中获取成员名称。然后我使用一个中间类(并下拉到IEnumerable),这样我就可以推断源类型,然后在第二个泛型方法中指定目标类型。

public static class Utilities {
    // ***
    // *** MemberInfo Extensions
    // ***
    public static Type GetMemberType(this MemberInfo member) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.FieldType;
            case PropertyInfo mpi:
                return mpi.PropertyType;
            case EventInfo mei:
                return mei.EventHandlerType;
            default:
                throw new ArgumentException("MemberInfo must be if type FieldInfo, PropertyInfo or EventInfo", nameof(member));
        }
    }

    public static object GetValue(this MemberInfo member, object srcObject) {
        switch (member) {
            case FieldInfo mfi:
                return mfi.GetValue(srcObject);
            case PropertyInfo mpi:
                return mpi.GetValue(srcObject);
            default:
                throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
        }
    }
    public static T GetValue<T>(this MemberInfo member, object srcObject) => (T)member.GetValue(srcObject);

    public static void SetValue<T>(this MemberInfo member, object destObject, T value) {
        switch (member) {
            case FieldInfo mfi:
                mfi.SetValue(destObject, value);
                break;
            case PropertyInfo mpi:
                mpi.SetValue(destObject, value);
                break;
            default:
                throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
        }
    }

    public static IEnumerable<string> GetValueTupleNames(Type source, string action) {
        var method = source.GetMethod(action);
        var attr = method.ReturnParameter.GetCustomAttribute<TupleElementNamesAttribute>();

        return attr.TransformNames;
    }

    public class MapSource {
        public IEnumerable src { get; }
        public Type srcType { get; }
        public Type methodClass { get; }
        public string methodReturnsTupleName { get; }

        public MapSource(IEnumerable src, Type srcType, Type methodClass, string methodReturnsTupleName) {
            this.src = src;
            this.srcType = srcType;
            this.methodClass = methodClass;
            this.methodReturnsTupleName = methodReturnsTupleName;
        }
    }

    public static MapSource TupleMapper<VT>(this IEnumerable<VT> src, Type sourceClass, string methodReturnsTupleName) =>
        new MapSource(src, typeof(VT), sourceClass, methodReturnsTupleName);

    public static IEnumerable<T> To<T>(this MapSource ms) where T : new() {
        var srcNames = GetValueTupleNames(ms.methodClass, ms.methodReturnsTupleName).Take(ms.srcType.GetFields().Length).ToList();
        var srcMIs = srcNames.Select((Name, i) => new { ItemMI = ms.srcType.GetMember($"Item{i + 1}")[0], i, Name })
                             .ToDictionary(min => min.Name, min => min.ItemMI);
        var destMIs = srcNames.Select(n => new { members = typeof(T).GetMember(n), Name = n })
                              .Where(mn => mn.members.Length == 1 && srcMIs[mn.Name].GetMemberType() == mn.members[0].GetMemberType())
                              .Select(mn => new { DestMI = mn.members[0], mn.Name })
                              .ToList();

        foreach (var s in ms.src) {
            var ans = new T();
            foreach (var MIn in destMIs)
                MIn.DestMI.SetValue(ans, srcMIs[MIn.Name].GetValue(s));
            yield return ans;
        }
    }
}

使用这些方法,您现在可以将ValueTuplesDto自动映射到:

var target = source.TupleMapper(typeof(DataAccessLayer), nameof(DataAccessLayer.Method)).To<Dto>().ToList();
于 2018-10-25T23:02:15.030 回答
1

元组类型名称由返回它们的方法定义,而不是实际的元组类型。元组名称是 100% 的语法糖,因此任何映射代码都需要了解使用元组的上下文。与普通对象相比,这使得通过反射进行映射变得困难,您可以在运行时获取对象的属性名称。

这是一种使用 linq 表达式来捕获返回元组的方法的方法:

public static class Mapper
{
  public static TupleMapper<TTuple> FromTuple<TTuple>(Expression<Func<TTuple>> tupleSource) where TTuple : struct, ITuple
  {
    if (!(tupleSource.Body is MethodCallExpression call))
    {
      throw new ArgumentException("Argument must be method call returning tuple type", nameof(tupleSource));
    }

    var tupleNamesAttribute = call.Method.ReturnParameter.GetCustomAttribute<TupleElementNamesAttribute>();

    var compiledTupleSource = tupleSource.Compile();

    return new TupleMapper<TTuple>(compiledTupleSource(), tupleNamesAttribute.TransformNames);
  }
}

public struct TupleMapper<TTuple> where TTuple : struct, ITuple
{
  private readonly IList<string> _names;
  private readonly TTuple _tuple;

  public TupleMapper(TTuple tuple, IList<string> names)
  {
    _tuple = tuple;
    _names = names;
  }

  public T Map<T>() where T : new()
  {
    var instance = new T();
    var instanceType = typeof(T);

    for (var i = 0; i < _names.Count; i++)
    {
      var instanceProp = instanceType.GetProperty(_names[i]);
      instanceProp.SetValue(instance, _tuple[i]);
    }

    return instance;
  }
}

要使用它,语法是:

static void Main(string[] args)
{
  var dto = Mapper.FromTuple(() => ReturnsATuple()).Map<Dto>();

  Console.WriteLine($"Foo: {dto.Foo}, Bar: {dto.Bar}");

  Console.Read();
}

public static (string Foo, int Bar) ReturnsATuple()
{
  return ("A", 1);
}

class Dto
{
  public string Foo { get; set; }
  public int Bar { get; set; }
}
于 2018-10-25T19:46:08.123 回答
0

这里的根本困难是 namedTuple 只是一个语法糖,你没有办法在运行时使用 Tuple Name。

文档

这些同义词由编译器和语言处理,以便您可以有效地使用命名元组。IDE 和编辑器可以使用 Roslyn API 读取这些语义名称。您可以在同一程序集中的任何位置通过这些语义名称引用命名元组的元素。生成编译输出时,编译器会将您定义的名称替换为 Item* 等效项。已编译的 Microsoft 中间语言 (MSIL) 不包括您为这些元素指定的名称。

这迫使您在运行时使用 Item*。

有两种方法可以做到这一点,我知道我的解决方案并不优雅、灵活或可消耗(我知道很多问题),但我只想指出一个方向。您可以稍后完善解决方案。

1、反射:

public static Dto ToDto((string, int) source , string[] nameMapping)
    {
        var dto = new Dto();
        var propertyInfo1 = typeof(Dto).GetProperty(nameMapping[0]);
        propertyInfo1?.SetValue(dto, source.Item1);
        var propertyInfo2 = typeof(Dto).GetProperty(nameMapping[1]);
        propertyInfo2?.SetValue(dto, source.Item2);
        return dto;
    }

2、字典

public static Dto ToDto2((string, int) source, string[] nameMapping)
        {
            var dic = new Dictionary<string, object> {{nameMapping[0], source.Item1}, {nameMapping[1], source.Item2}};
            return new Dto {Foo = (string)dic[nameMapping[0]], Bar = (int)dic[nameMapping[1]]};
        }

就个人而言,我喜欢第二种解决方案。

反射具有一定程度的类型安全,但是速度很慢,当你有很多数据时,性能是一个问题,使用字典,类型安全更差,但性能会更好(理论,未测试),使用您的问题,类型安全是一个基本问题,您只需要使用防御性编码并具有更好的错误处理能力,或者训练 API 用户按规则行事,我认为类型安全反射给您的作用不会太大。

于 2018-10-25T18:42:06.757 回答