namespace SumaTube.Crosscutting.Mappers { using System; using System.Linq; using System.Reflection; public static class GenericMapper { public static TDestination MapTo(this TSource source) where TDestination : class { if (source == null) return default; var destType = typeof(TDestination); // Verifica se o tipo de destino tem um construtor público var constructors = destType.GetConstructors(BindingFlags.Public | BindingFlags.Instance) .OrderByDescending(c => c.GetParameters().Length) .ToList(); // Se o tipo de destino tem um construtor com parâmetros, tenta mapear para ele if (constructors.Any() && constructors[0].GetParameters().Length > 0) { return MapToImmutableObject(source, constructors); } // Caso contrário, usa a abordagem de mapeamento de propriedades para objetos mutáveis else { return MapToMutableObject(source); } } private static TDestination MapToImmutableObject(TSource source, List constructors) where TDestination : class { var sourceType = typeof(TSource); var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance); // Tenta cada construtor, começando pelo que tem mais parâmetros foreach (var constructor in constructors) { var parameters = constructor.GetParameters(); if (parameters.Length == 0) { // Construtor sem parâmetros, cria instância diretamente var instance = constructor.Invoke(null); return (TDestination)instance; } var parameterValues = new object[parameters.Length]; bool canUseConstructor = true; // Tenta mapear os parâmetros do construtor for (int i = 0; i < parameters.Length; i++) { var param = parameters[i]; // Procura propriedade com o mesmo nome (case insensitive) var matchingProp = sourceProps.FirstOrDefault(p => string.Equals(p.Name, param.Name, StringComparison.OrdinalIgnoreCase)); if (matchingProp != null) { var value = matchingProp.GetValue(source); // Se os tipos são compatíveis, usa o valor diretamente if (param.ParameterType.IsAssignableFrom(matchingProp.PropertyType)) { parameterValues[i] = value; } // Verifica se existe uma conversão implícita else if (value != null && TryImplicitConversion(value, param.ParameterType, out var convertedValue)) { parameterValues[i] = convertedValue; } // Se o valor é um tipo complexo, tenta mapear recursivamente else if (value != null && !matchingProp.PropertyType.IsPrimitive && !matchingProp.PropertyType.Namespace.StartsWith("System")) { try { var method = typeof(GenericMapper).GetMethod(nameof(MapTo)); var genericMethod = method.MakeGenericMethod(matchingProp.PropertyType, param.ParameterType); parameterValues[i] = genericMethod.Invoke(null, new[] { value }); } catch { canUseConstructor = false; break; } } else { canUseConstructor = false; break; } } else { // Se não encontrou uma propriedade correspondente, verifica se o parâmetro é opcional if (param.IsOptional) { parameterValues[i] = param.DefaultValue; } else { canUseConstructor = false; break; } } } if (canUseConstructor) { try { var instance = constructor.Invoke(parameterValues); return (TDestination)instance; } catch { // Se falhou ao criar a instância, tenta o próximo construtor continue; } } } // Se não conseguiu usar nenhum construtor, lança exceção throw new InvalidOperationException( $"Não foi possível mapear {sourceType.Name} para {typeof(TDestination).Name} " + $"usando os construtores disponíveis. Verifique se os nomes das propriedades " + $"correspondem aos nomes dos parâmetros do construtor (case insensitive)."); } private static TDestination MapToMutableObject(TSource source) where TDestination : class { var destType = typeof(TDestination); // Tenta criar uma instância usando o construtor sem parâmetros TDestination destination; try { destination = Activator.CreateInstance(); } catch (Exception ex) { throw new InvalidOperationException( $"Não foi possível criar uma instância de {destType.Name}. " + $"Certifique-se de que a classe tenha um construtor sem parâmetros acessível.", ex); } var sourceType = typeof(TSource); var sourceProps = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance); var destProps = destType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.SetProperty); foreach (var destProp in destProps) { if (!destProp.CanWrite) continue; // Procura propriedade com o mesmo nome (case insensitive) var sourceProp = sourceProps.FirstOrDefault(p => string.Equals(p.Name, destProp.Name, StringComparison.OrdinalIgnoreCase)); if (sourceProp != null) { var value = sourceProp.GetValue(source); if (value != null) { // Se os tipos são compatíveis, atribui diretamente if (destProp.PropertyType.IsAssignableFrom(sourceProp.PropertyType)) { destProp.SetValue(destination, value); } // Verifica se existe uma conversão implícita else if (TryImplicitConversion(value, destProp.PropertyType, out var convertedValue)) { destProp.SetValue(destination, convertedValue); } // Se o valor é um tipo complexo, tenta mapear recursivamente else if (!sourceProp.PropertyType.IsPrimitive && !sourceProp.PropertyType.Namespace.StartsWith("System")) { try { var method = typeof(GenericMapper).GetMethod(nameof(MapTo)); var genericMethod = method.MakeGenericMethod(sourceProp.PropertyType, destProp.PropertyType); var mappedValue = genericMethod.Invoke(null, new[] { value }); destProp.SetValue(destination, mappedValue); } catch { // Ignora se não conseguir mapear } } } } } return destination; } private static bool TryImplicitConversion(object source, Type destinationType, out object result) { result = null; if (source == null) return false; var sourceType = source.GetType(); // Verifica operador de conversão implícita no tipo de origem var methodSource = sourceType.GetMethods(BindingFlags.Public | BindingFlags.Static) .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == destinationType && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == sourceType); if (methodSource != null) { result = methodSource.Invoke(null, new[] { source }); return true; } // Verifica operador de conversão implícita no tipo de destino var methodDest = destinationType.GetMethods(BindingFlags.Public | BindingFlags.Static) .FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == destinationType && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == sourceType); if (methodDest != null) { result = methodDest.Invoke(null, new[] { source }); return true; } // Tenta converter usando Convert.ChangeType try { if (destinationType.IsValueType || destinationType == typeof(string)) { result = Convert.ChangeType(source, destinationType); return true; } } catch { } return false; } } }