使用一些现有的Mapper,可以:
var target = Mapper.Map(source).To<Dto>();
其中source
是IEnumerable<(string Foo, int Bar)>
,而Dto
是具有属性Foo
和Bar
的类吗?
示例代码:
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
it is possible在运行时访问值元组元素,特别是它的名称。
答案 0 :(得分: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; }
}
答案 1 :(得分:1)
这里是一个ValueTuple
映射器,它使用提供的方法中的项目名称。在此方法有效的同时,我建议使用匿名对象,并从该对象进行映射比使用ValueTuple
更好,除非您遇到性能问题,并且如果匿名对象存在性能问题,那么使用ValueTuple
会有所帮助,您将无法通过执行Reflection来进行自动映射。还要注意,任何带有名称的嵌套元组类型都可能无法正常工作。
在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;
}
}
}
使用这些方法,您现在可以将ValueTuple
自动映射到Dto
:
var target = source.TupleMapper(typeof(DataAccessLayer), nameof(DataAccessLayer.Method)).To<Dto>().ToList();
答案 2 :(得分:0)
这里的基本困难是namedTuple只是一个语法糖,您无法在运行时使用Tuple Name。
来自Document
这些同义词由编译器和语言处理,因此 您可以有效使用命名元组。 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]]};
}
个人而言,我喜欢第二种解决方案。
反射具有某种程度的类型安全性,但是它很慢,当您有大量数据时,性能是一个问题,对于dictionary来说,类型安全性较差,但性能会更好(理论上,未经测试) ),对于您的问题,类型安全性是一个基本问题,您只需要使用防御性编码并具有更好的错误处理能力,或训练API用户按规则进行操作,我认为类型安全性反射不会给您带来任何麻烦做很多事。