.netreflection

.Net Create object that can be passed to method<T> with reflection


Currently I have custom mapper that accepts a TSource and a TDestination;

Edit to clarify: I want to pass var refobj= System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(sourcename); to MapToList<TSource, TDestination> as a replacement for TSource.

    public static TDestination Map<TSource, TDestination>(TSource sourceObject)
    {
       var destinationObject = Activator.CreateInstance<TDestination>();
       if (sourceObject != null)
       {
         foreach (var sourceProperty in typeof(TSource).GetProperties())
         {
             var destinationProperty = typeof(TDestination).GetProperty(sourceProperty.Name);                
             destinationProperty?.SetValue(destinationObject, sourceProperty.GetValue(sourceObject));                
         }
     }
     return destinationObject;
}

This is fine until it encounters a List. So I extended the mapper like this

public static TDestination Map<TSource, TDestination>(TSource sourceObject)
    {
       var destinationObject = Activator.CreateInstance<TDestination>();
       if (sourceObject != null)
       {
         foreach (var sourceProperty in typeof(TSource).GetProperties())
         {
             var destinationProperty = typeof(TDestination).GetProperty(sourceProperty.Name);                
             If (//check for list)
             {
                var sourcename = sourceProperty.ReflectedType?.FullName;
                var refobj= System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(sourcename);
                destinationProperty?.SetValue(destinationObject, MapToList<refobj, //destinationRefObj>(listToMap));
             }
             destinationProperty?.SetValue(destinationObject, sourceProperty.GetValue(sourceObject));                
         }
     }
     return destinationObject;
}

Mapping the list.

     private static List<TDestination> MapToList<TSource, TDestination>(List<TSource> source)
 {
     var target = new List<TDestination>(source.Count);
     foreach (var item in source)
     {
        //DOMAP
     }
     return target;
 }

How can I instantiate a class so I can use it to pass to MapToList<TSource, TDestination>? I can't use the reflected object for that because the error is "refobj is a variable but is used like a type". Thanks


Solution

  • Putting aside why you implement custom mapper, when there are many solutions available, you can try following:

        public static class Mapper
    {
        public static TDestination Map<TSource, TDestination>(TSource sourceObject)
        {
            var destinationObject = Activator.CreateInstance<TDestination>();
            if (sourceObject != null)
            {
                foreach (var sourceProperty in typeof(TSource).GetProperties())
                {
                    var destinationProperty = typeof(TDestination).GetProperty(sourceProperty.Name);
    
                    var sourceValue = sourceProperty.GetValue(sourceObject);
                    object mapped;
    
                    if (sourceValue.IsGenericList())
                    {
                        mapped = MapToList(sourceProperty.PropertyType.GenericTypeArguments.Single(),
                            destinationProperty.PropertyType.GenericTypeArguments.Single(), sourceValue);
                    }
                    else
                    {
                        mapped = Map(sourceProperty.PropertyType,
                            destinationProperty.PropertyType, sourceValue);
                    }
    
                    destinationProperty?.SetValue(destinationObject, mapped);
                }
            }
    
            return destinationObject;
        }
    
        private static object Map(Type sourceType, Type destinationType, object value)
        {
            if (destinationType.IsAssignableFrom(sourceType))
            {
                return value;
            }
    
            var genericMap = typeof(Mapper).GetMethod(nameof(Map), BindingFlags.Public | BindingFlags.Static)
                .MakeGenericMethod(sourceType, destinationType);
    
            return genericMap.Invoke(null, new[] { value });
        }
    
        private static object MapToList(Type sourceType, Type destinationType, object value)
        {
            var genericMapToList = typeof(Mapper).GetMethod(nameof(MapToList), BindingFlags.Public | BindingFlags.Static)
                .MakeGenericMethod(sourceType, destinationType);
    
            return genericMapToList.Invoke(null, new []{value});
        }
    
        private static List<TDestination> MapToList<TSource, TDestination>(List<TSource> source)
        {
            var target = new List<TDestination>(source.Count);
    
            var addMethod = target.GetType().GetMethod("Add");
    
            foreach (var item in source)
            {
                var mappedObj = Map(typeof(TSource), typeof(TDestination), source);
    
                addMethod.Invoke(target, new object[] { mappedObj });
            }
    
            return target;
        }
    
        public static bool IsGenericList(this object o)
        {
            var oType = o.GetType();
            return (oType.IsGenericType && (oType.GetGenericTypeDefinition() == typeof(List<>)));
        }
    }
    

    I hope I got it right now, it is a bit more complex problem and this code doesn't have many checks on null etc.

    for the generic list check I used: https://stackoverflow.com/a/794257/9855767