diff --git a/src/Mapster/Adapter.cs b/src/Mapster/Adapter.cs index 8987a92a..dc81b793 100644 --- a/src/Mapster/Adapter.cs +++ b/src/Mapster/Adapter.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Mapster { @@ -20,8 +21,11 @@ public TypeAdapterBuilder BuildAdapter(TSource source) public TDestination Adapt(object source) { - dynamic fn = _config.GetMapFunction(source.GetType(), typeof(TDestination)); - return (TDestination)fn((dynamic)source); + if (source == null) + return default(TDestination); + var type = source.GetType(); + var fn = _config.GetDynamicMapFunction(type); + return fn(source); } public TDestination Adapt(TSource source) @@ -38,14 +42,34 @@ public TDestination Adapt(TSource source, TDestination de public object Adapt(object source, Type sourceType, Type destinationType) { - var fn = _config.GetMapFunction(sourceType, destinationType); - return fn.DynamicInvoke(source); + var del = _config.GetMapFunction(sourceType, destinationType); + if (sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible) + { + dynamic fn = del; + return fn((dynamic)source); + } + else + { + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return del.DynamicInvoke(source); + } } public object Adapt(object source, object destination, Type sourceType, Type destinationType) { - dynamic fn = _config.GetMapFunction(sourceType, destinationType); - return fn((dynamic)source); + var del = _config.GetMapToTargetFunction(sourceType, destinationType); + if (sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible) + { + dynamic fn = del; + return fn((dynamic)source, (dynamic)destination); + } + else + { + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return del.DynamicInvoke(source, destination); + } } } diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index f1033bd2..a67e78c0 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -54,24 +54,10 @@ protected virtual void DecorateRule(TypeAdapterRule rule) { } protected virtual bool CanInline(Expression source, Expression destination, CompileArgument arg) { - if (arg.MapType == MapType.MapToTarget) - return false; - var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg); - if (constructUsing != null && - constructUsing.Body.NodeType != ExpressionType.New && - constructUsing.Body.NodeType != ExpressionType.MemberInit) - { - if (arg.MapType == MapType.Projection) - throw new InvalidOperationException("Input ConstructUsing is invalid for projection"); - return false; - } - - //IgnoreIf, PreserveReference, AfterMapping, Includes aren't supported by Projection + //PreserveReference, AfterMapping, Includes aren't supported by Projection if (arg.MapType == MapType.Projection) return true; - if (arg.Settings.IgnoreIfs.Any(item => item.Value != null)) - return false; if (arg.Settings.PreserveReference == true && !arg.SourceType.GetTypeInfo().IsValueType && !arg.DestinationType.GetTypeInfo().IsValueType) diff --git a/src/Mapster/Adapters/ClassAdapter.cs b/src/Mapster/Adapters/ClassAdapter.cs index dacf69d7..c4649372 100644 --- a/src/Mapster/Adapters/ClassAdapter.cs +++ b/src/Mapster/Adapters/ClassAdapter.cs @@ -31,11 +31,25 @@ protected override bool CanInline(Expression source, Expression destination, Com if (!base.CanInline(source, destination, arg)) return false; - //IgnoreNullValue isn't supported by projection + if (arg.MapType == MapType.MapToTarget) + return false; + var constructUsing = arg.Settings.ConstructUsingFactory?.Invoke(arg); + if (constructUsing != null && + constructUsing.Body.NodeType != ExpressionType.New && + constructUsing.Body.NodeType != ExpressionType.MemberInit) + { + if (arg.MapType == MapType.Projection) + throw new InvalidOperationException("ConstructUsing for projection is support only New and MemberInit expression."); + return false; + } + + //IgnoreIfs, IgnoreNullValue isn't supported by projection if (arg.MapType == MapType.Projection) return true; if (arg.Settings.IgnoreNullValues == true) return false; + if (arg.Settings.IgnoreIfs.Any(item => item.Value != null)) + return false; return true; } diff --git a/src/Mapster/Adapters/DelegateAdapter.cs b/src/Mapster/Adapters/DelegateAdapter.cs index 807825c2..ea5bd7b5 100644 --- a/src/Mapster/Adapters/DelegateAdapter.cs +++ b/src/Mapster/Adapters/DelegateAdapter.cs @@ -29,7 +29,7 @@ protected override Expression CreateBlockExpression(Expression source, Expressio protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) { - return Expression.Empty(); + return CreateInstantiationExpression(source, arg); } } } diff --git a/src/Mapster/Adapters/DictionaryAdapter.cs b/src/Mapster/Adapters/DictionaryAdapter.cs index b0fe4de0..8802f139 100644 --- a/src/Mapster/Adapters/DictionaryAdapter.cs +++ b/src/Mapster/Adapters/DictionaryAdapter.cs @@ -10,7 +10,7 @@ namespace Mapster.Adapters { internal class DictionaryAdapter : ClassAdapter { - protected override int Score => -124; + protected override int Score => -124; //must do before CollectionAdapter protected override bool CanMap(PreCompileArgument arg) { diff --git a/src/Mapster/Adapters/EnumAdapter.cs b/src/Mapster/Adapters/EnumAdapter.cs new file mode 100644 index 00000000..a69ada6b --- /dev/null +++ b/src/Mapster/Adapters/EnumAdapter.cs @@ -0,0 +1,48 @@ +using Mapster.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Mapster.Adapters +{ + internal class EnumAdapter : PrimitiveAdapter + { + protected override int Score => -109; //must do before StringAdapter + protected override bool CheckExplicitMapping => false; + + protected override bool CanMap(PreCompileArgument arg) + { + return arg.SourceType.GetTypeInfo().IsEnum || arg.DestinationType.GetTypeInfo().IsEnum; + } + + protected override Expression ConvertType(Expression source, Type destinationType, CompileArgument arg) + { + var srcType = source.Type; + if (destinationType == typeof(string)) + { + var method = typeof(Enum<>).MakeGenericType(srcType).GetMethod("ToString", new[] { srcType }); + return Expression.Call(method, source); + } + else if (srcType == typeof(string)) + { + var method = typeof(Enum<>).MakeGenericType(destinationType).GetMethod("Parse", new[] { typeof(string) }); + return Expression.Call(method, source); + } + else if (destinationType.GetTypeInfo().IsEnum && srcType.GetTypeInfo().IsEnum && arg.Settings.MapEnumByName == true) + { + var method = typeof(Enum<>).MakeGenericType(srcType).GetMethod("ToString", new[] { srcType }); + var tostring = Expression.Call(method, source); + var methodParse = typeof(Enum<>).MakeGenericType(destinationType).GetMethod("Parse", new[] { typeof(string) }); + + return Expression.Call(methodParse, tostring); + } + + + return base.ConvertType(source, destinationType, arg); + } + } +} diff --git a/src/Mapster/Adapters/ObjectAdapter.cs b/src/Mapster/Adapters/ObjectAdapter.cs index 7720c13d..f8a71094 100644 --- a/src/Mapster/Adapters/ObjectAdapter.cs +++ b/src/Mapster/Adapters/ObjectAdapter.cs @@ -5,36 +5,35 @@ namespace Mapster.Adapters { internal class ObjectAdapter : BaseAdapter { - protected override int Score => -111; + protected override int Score => -111; //must do before all class adapters + protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) { return arg.SourceType == typeof(object) || arg.DestinationType == typeof(object); } + protected override Expression CreateInstantiationExpression(Expression source, Expression destination, CompileArgument arg) + { + var srcType = arg.SourceType; + var destType = arg.DestinationType; + if (srcType != destType) + return source; + else if (destType == typeof(object)) + return Expression.Convert(source, destType); + else //if (srcType == typeof(object)) + return ReflectionUtils.CreateConvertMethod(srcType, destType, source) + ?? Expression.Convert(source, destType); + } + protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) { - throw new NotImplementedException(); + return Expression.Empty(); } protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) { - // object, T - - //if (src.GetType() == typeof(object)) - // return (T)src - - //return src.Adapt(); - - // poco, object - - //return (object)src.Adapt(); - - // object, object - - //if (src.GetType() == typeof(object)) - // return src; - // return src.Adapt(src.GetType(), src.GetType()); + return CreateInstantiationExpression(source, arg); } } } diff --git a/src/Mapster/Adapters/PrimitiveAdapter.cs b/src/Mapster/Adapters/PrimitiveAdapter.cs index 0acb6898..33b1b849 100644 --- a/src/Mapster/Adapters/PrimitiveAdapter.cs +++ b/src/Mapster/Adapters/PrimitiveAdapter.cs @@ -8,7 +8,7 @@ namespace Mapster.Adapters { internal class PrimitiveAdapter : BaseAdapter { - protected override int Score => -200; + protected override int Score => -200; //must do last protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) @@ -24,10 +24,11 @@ protected override Expression CreateExpressionBody(Expression source, Expression if (sourceType != destinationType) { if (sourceType.IsNullable()) - { convert = Expression.Convert(convert, sourceType.GetGenericArguments()[0]); - } - convert = ConvertType(convert, arg); + var destType = arg.DestinationType.UnwrapNullable(); + + if (convert.Type != destType) + convert = ConvertType(convert, destType, arg); if (convert.Type != destinationType) convert = Expression.Convert(convert, destinationType); @@ -43,32 +44,14 @@ protected override Expression CreateExpressionBody(Expression source, Expression return convert; } - protected virtual Expression ConvertType(Expression source, CompileArgument arg) + protected virtual Expression ConvertType(Expression source, Type destinationType, CompileArgument arg) { - var srcType = arg.SourceType.UnwrapNullable(); - var destType = arg.DestinationType.UnwrapNullable(); - - if (srcType == destType) - return source; - - if (destType.GetTypeInfo().IsEnum && srcType.GetTypeInfo().IsEnum && arg.Settings.MapEnumByName == true) - { - var method = typeof(Enum<>).MakeGenericType(srcType).GetMethod("ToString", new[] { srcType }); - var tostring = Expression.Call(method, source); - var methodParse = typeof(Enum<>).MakeGenericType(destType).GetMethod("Parse", new[] { typeof(string) }); - - return Expression.Call(methodParse, tostring); - } - - if (IsObjectToPrimitiveConversion(srcType, destType)) - { - return CreateConvertMethod(_primitiveTypes[destType], srcType, destType, source); - } + var srcType = source.Type; //try using type casting try { - return Expression.Convert(source, destType); + return Expression.Convert(source, destinationType); } catch { @@ -79,28 +62,12 @@ protected virtual Expression ConvertType(Expression source, CompileArgument arg) throw new InvalidOperationException("Cannot convert immutable type, please consider using 'MapWith' method to create mapping"); //using Convert - if (_primitiveTypes.ContainsKey(destType)) - { - return CreateConvertMethod(_primitiveTypes[destType], srcType, destType, source); - } + var result = ReflectionUtils.CreateConvertMethod(srcType, destinationType, source); + if (result != null) + return result; var changeTypeMethod = typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) }); - return Expression.Convert(Expression.Call(changeTypeMethod, Expression.Convert(source, typeof(object)), Expression.Constant(destType)), destType); - } - - private static bool IsObjectToPrimitiveConversion(Type sourceType, Type destinationType) - { - return (sourceType == typeof(object)) && _primitiveTypes.ContainsKey(destinationType); - } - - private static Expression CreateConvertMethod(string name, Type srcType, Type destType, Expression source) - { - var method = typeof(Convert).GetMethod(name, new[] { srcType }); - if (method != null) - return Expression.Call(method, source); - - method = typeof(Convert).GetMethod(name, new[] { typeof(object) }); - return Expression.Convert(Expression.Call(method, Expression.Convert(source, typeof(object))), destType); + return Expression.Convert(Expression.Call(changeTypeMethod, Expression.Convert(source, typeof(object)), Expression.Constant(destinationType)), destinationType); } protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) @@ -112,22 +79,5 @@ protected override Expression CreateInlineExpression(Expression source, CompileA { throw new NotImplementedException(); } - - // Primitive types with their conversion methods from System.Convert class. - private static Dictionary _primitiveTypes = new Dictionary() { - { typeof(bool), "ToBoolean" }, - { typeof(short), "ToInt16" }, - { typeof(int), "ToInt32" }, - { typeof(long), "ToInt64" }, - { typeof(float), "ToSingle" }, - { typeof(double), "ToDouble" }, - { typeof(decimal), "ToDecimal" }, - { typeof(ushort), "ToUInt16" }, - { typeof(uint), "ToUInt32" }, - { typeof(ulong), "ToUInt64" }, - { typeof(byte), "ToByte" }, - { typeof(sbyte), "ToSByte" }, - { typeof(DateTime), "ToDateTime" } - }; } } diff --git a/src/Mapster/Adapters/StringAdapter.cs b/src/Mapster/Adapters/StringAdapter.cs index a3d37793..dd98fbc4 100644 --- a/src/Mapster/Adapters/StringAdapter.cs +++ b/src/Mapster/Adapters/StringAdapter.cs @@ -7,7 +7,7 @@ namespace Mapster.Adapters { internal class StringAdapter : PrimitiveAdapter { - protected override int Score => -110; + protected override int Score => -110; //must do before all class adapters protected override bool CheckExplicitMapping => false; protected override bool CanMap(PreCompileArgument arg) @@ -15,53 +15,21 @@ protected override bool CanMap(PreCompileArgument arg) return arg.SourceType == typeof(string) || arg.DestinationType == typeof(string); } - protected override Expression CreateExpressionBody(Expression source, Expression destination, CompileArgument arg) + protected override Expression ConvertType(Expression source, Type destinationType, CompileArgument arg) { - var sourceType = arg.SourceType; - var destinationType = arg.DestinationType; - - if (sourceType == destinationType) - return source; - if (destinationType == typeof(string)) { - if (sourceType.GetTypeInfo().IsEnum) - { - var method = typeof(Enum<>).MakeGenericType(sourceType).GetMethod("ToString", new[] { sourceType }); - return Expression.Call(method, source); - } - else - { - var method = sourceType.GetMethod("ToString", Type.EmptyTypes); - return Expression.Call(source, method); - } + var method = source.Type.GetMethod("ToString", Type.EmptyTypes); + return Expression.Call(source, method); } else //if (sourceType == typeof(string)) { - if (destinationType.GetTypeInfo().IsEnum) - { - var method = typeof(Enum<>).MakeGenericType(destinationType).GetMethod("Parse", new[] { typeof(string) }); + var method = destinationType.GetMethod("Parse", new[] { typeof(string) }); + if (method != null) return Expression.Call(method, source); - } - else - { - var method = destinationType.GetMethod("Parse", new[] { typeof(string) }); - if (method != null) - return Expression.Call(method, source); - } } - return base.ConvertType(source, arg); - } - - protected override Expression CreateBlockExpression(Expression source, Expression destination, CompileArgument arg) - { - throw new NotImplementedException(); - } - - protected override Expression CreateInlineExpression(Expression source, CompileArgument arg) - { - throw new NotImplementedException(); + return base.ConvertType(source, destinationType, arg); } } } diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 7ca3ab53..cb97d09c 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -62,6 +62,7 @@ + diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index bf2f6f35..caef3ad7 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -118,12 +118,18 @@ public static object Adapt(this object source, Type sourceType, Type destination /// Adapted destination type. public static object Adapt(this object source, Type sourceType, Type destinationType, TypeAdapterConfig config) { - dynamic fn = config.GetMapFunction(sourceType, destinationType); - return fn((dynamic)source); - - var fn = config.GetDynamicMapFunction(sourceType, destinationType); - return fn(source); - + var del = config.GetMapFunction(sourceType, destinationType); + if (sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible) + { + dynamic fn = del; + return fn((dynamic)source); + } + else + { + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return del.DynamicInvoke(source); + } } /// @@ -150,8 +156,18 @@ public static object Adapt(this object source, object destination, Type sourceTy /// Adapted destination type. public static object Adapt(this object source, object destination, Type sourceType, Type destinationType, TypeAdapterConfig config) { - dynamic fn = config.GetMapToTargetFunction(sourceType, destinationType); - return fn((dynamic)source, (dynamic)destination); + var del = config.GetMapToTargetFunction(sourceType, destinationType); + if (sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible) + { + dynamic fn = del; + return fn((dynamic)source, (dynamic)destination); + } + else + { + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return del.DynamicInvoke(source, destination); + } } /// diff --git a/src/Mapster/TypeAdapterConfig.cs b/src/Mapster/TypeAdapterConfig.cs index b3a0e7d8..844c6501 100644 --- a/src/Mapster/TypeAdapterConfig.cs +++ b/src/Mapster/TypeAdapterConfig.cs @@ -27,7 +27,9 @@ private static List CreateRuleTemplate() new ClassAdapter().CreateRule(), //-150 new CollectionAdapter().CreateRule(), //-125 new DictionaryAdapter().CreateRule(), //-124 + new ObjectAdapter().CreateRule(), //-111 new StringAdapter().CreateRule(), //-110 + new EnumAdapter().CreateRule(), //-109 //fallback rules new TypeAdapterRule @@ -294,17 +296,13 @@ internal MethodCallExpression GetProjectionCallExpression(Type sourceType, Type private Hashtable _dynamicMapDict; internal Func GetDynamicMapFunction(Type sourceType) - { - return (Func)GetDynamicMapFunction(sourceType, typeof(TDestination)); - } - internal Delegate GetDynamicMapFunction(Type sourceType, Type destinationType) { if (_dynamicMapDict == null) _dynamicMapDict = new Hashtable(); - var key = new TypeTuple(sourceType, destinationType); + var key = new TypeTuple(sourceType, typeof(TDestination)); object del = _dynamicMapDict[key] ?? AddToHash(_dynamicMapDict, key, tuple => Compiler(CreateDynamicMapExpression(tuple))); - return (Delegate)del; + return (Func)del; } internal LambdaExpression CreateMapExpression(TypeTuple tuple, MapType mapType) diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index e12e7912..2f0f1384 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -13,6 +13,22 @@ internal static class ReflectionUtils { private static readonly Type _stringType = typeof (string); + // Primitive types with their conversion methods from System.Convert class. + private static Dictionary _primitiveTypes = new Dictionary() { + { typeof(bool), "ToBoolean" }, + { typeof(short), "ToInt16" }, + { typeof(int), "ToInt32" }, + { typeof(long), "ToInt64" }, + { typeof(float), "ToSingle" }, + { typeof(double), "ToDouble" }, + { typeof(decimal), "ToDecimal" }, + { typeof(ushort), "ToUInt16" }, + { typeof(uint), "ToUInt32" }, + { typeof(ulong), "ToUInt64" }, + { typeof(byte), "ToByte" }, + { typeof(sbyte), "ToSByte" }, + { typeof(DateTime), "ToDateTime" } + }; #if NET40 public static Type GetTypeInfo(this Type type) { @@ -79,6 +95,21 @@ public static Type GetGenericEnumerableType(this Type type) return type.GetInterface(IsGenericEnumerableType); } + public static Expression CreateConvertMethod(Type srcType, Type destType, Expression source) + { + var name = _primitiveTypes.GetValueOrDefault(destType); + + if (name == null) + return null; + + var method = typeof (Convert).GetMethod(name, new[] {srcType}); + if (method != null) + return Expression.Call(method, source); + + method = typeof (Convert).GetMethod(name, new[] {typeof (object)}); + return Expression.Convert(Expression.Call(method, Expression.Convert(source, typeof (object))), destType); + } + public static object GetDefault(this Type type) { return type.GetTypeInfo().IsValueType && !type.IsNullable()