From da7258fa1aefa8ab9d970d4fe1e5026d17ee6d5a Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Sun, 1 Feb 2026 10:32:55 +0700 Subject: [PATCH 01/13] feat: netstandard2.0 support and test fixes --- .../ExpressionDebugger.Tests.csproj | 4 + src/ExpressionDebugger/ExpressionCompiler.cs | 4 +- .../ExpressionDebugger.csproj | 4 +- .../ExpressionTranslator.csproj | 4 +- src/Mapster.Async.Tests/AsyncTest.cs | 6 +- .../Mapster.Async.Tests.csproj | 12 ++- src/Mapster.Async/Mapster.Async.csproj | 2 +- .../InjectionTest.cs | 31 +++---- .../Mapster.DependencyInjection.Tests.csproj | 12 ++- .../Mapster.DependencyInjection.csproj | 2 +- .../Mapster.EFCore.Tests.csproj | 6 +- .../Mapster.Immutable.Tests.csproj | 12 ++- .../Mapster.Immutable.csproj | 2 +- .../Mapster.JsonNet.Tests.csproj | 12 ++- src/Mapster.JsonNet/Mapster.JsonNet.csproj | 2 +- src/Mapster.Tests/Mapster.Tests.csproj | 15 ++-- .../Polyfills/CompileWithDebugInfo.net48.cs | 19 +++++ .../RequiredMemberAttributes.net48.cs | 38 +++++++++ .../TypeGetConstructorOverload.net48.cs | 14 ++++ .../WhenExplicitMappingRequired.cs | 6 +- src/Mapster.Tests/WhenIgnoreMapping.cs | 6 +- src/Mapster.Tests/WhenMappingToInterface.cs | 1 - .../WhenMappingWithDictionary.cs | 4 +- .../WhenPerformingAfterMapping.cs | 2 +- .../WhenPerformingBeforeMapping.cs | 10 +-- .../Mapster.Tool.Tests.csproj | 15 +++- .../Polyfills/IsExternalInit.net48.cs | 8 ++ src/Mapster.Tool.Tests/TestBase.cs | 4 +- src/Mapster/Adapters/BaseAdapter.cs | 2 +- src/Mapster/Adapters/BaseClassAdapter.cs | 13 ++- src/Mapster/Compile/CompileArgument.cs | 23 ++++-- src/Mapster/Interfaces/IMapFrom.cs | 6 +- src/Mapster/Mapper.cs | 33 +++----- src/Mapster/Mapster.csproj | 10 ++- src/Mapster/TypeAdapter.cs | 50 +++-------- src/Mapster/Utils/CodeAnalysisAttributes.cs | 20 +++++ src/Mapster/Utils/DelegateInvokeCompat.cs | 47 +++++++++++ src/Mapster/Utils/ExpressionEx.cs | 17 ++-- src/Mapster/Utils/InterfaceDynamicMapper.cs | 20 +++-- src/Mapster/Utils/IsExternalInit.cs | 8 ++ src/Mapster/Utils/LinqCompat.cs | 82 +++++++++++++++++++ src/Mapster/Utils/ReflectionUtils.cs | 11 ++- src/Sample.CodeGen/Startup.cs | 2 +- src/TemplateTest/TemplateTest.csproj | 12 ++- 44 files changed, 437 insertions(+), 176 deletions(-) create mode 100644 src/Mapster.Tests/Polyfills/CompileWithDebugInfo.net48.cs create mode 100644 src/Mapster.Tests/Polyfills/RequiredMemberAttributes.net48.cs create mode 100644 src/Mapster.Tests/Polyfills/TypeGetConstructorOverload.net48.cs create mode 100644 src/Mapster.Tool.Tests/Polyfills/IsExternalInit.net48.cs create mode 100644 src/Mapster/Utils/CodeAnalysisAttributes.cs create mode 100644 src/Mapster/Utils/DelegateInvokeCompat.cs create mode 100644 src/Mapster/Utils/IsExternalInit.cs create mode 100644 src/Mapster/Utils/LinqCompat.cs diff --git a/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj b/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj index 5140c085..5ca1aad3 100644 --- a/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj +++ b/src/ExpressionDebugger.Tests/ExpressionDebugger.Tests.csproj @@ -6,6 +6,10 @@ false + + $(TargetFrameworks);net48 + + diff --git a/src/ExpressionDebugger/ExpressionCompiler.cs b/src/ExpressionDebugger/ExpressionCompiler.cs index 2f9203de..d0480193 100644 --- a/src/ExpressionDebugger/ExpressionCompiler.cs +++ b/src/ExpressionDebugger/ExpressionCompiler.cs @@ -125,10 +125,10 @@ from n in t.TypeNames assemblyStream.Seek(0, SeekOrigin.Begin); symbolsStream.Seek(0, SeekOrigin.Begin); -#if NETSTANDARD2_0 || NET6_0_OR_GREATER +#if NET6_0_OR_GREATER return System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(assemblyStream, symbolsStream); #else - return Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray()); + return Assembly.Load(assemblyStream.ToArray(), symbolsStream.ToArray()); #endif } diff --git a/src/ExpressionDebugger/ExpressionDebugger.csproj b/src/ExpressionDebugger/ExpressionDebugger.csproj index 59e98815..e5d48bba 100644 --- a/src/ExpressionDebugger/ExpressionDebugger.csproj +++ b/src/ExpressionDebugger/ExpressionDebugger.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 True Chaowlert Chaisrichalermpol Step into debugging from linq expressions @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/src/ExpressionTranslator/ExpressionTranslator.csproj b/src/ExpressionTranslator/ExpressionTranslator.csproj index d2da32b5..6783d9b5 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.csproj +++ b/src/ExpressionTranslator/ExpressionTranslator.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0 + netstandard2.0;net10.0;net9.0;net8.0 True Chaowlert Chaisrichalermpol Translate from linq expressions to C# code @@ -25,7 +25,7 @@ - + diff --git a/src/Mapster.Async.Tests/AsyncTest.cs b/src/Mapster.Async.Tests/AsyncTest.cs index caefe5ce..3cd87072 100644 --- a/src/Mapster.Async.Tests/AsyncTest.cs +++ b/src/Mapster.Async.Tests/AsyncTest.cs @@ -45,15 +45,15 @@ public async Task AsyncError() } } - [TestMethod, ExpectedException(typeof(InvalidOperationException))] + [TestMethod] public void Sync() { TypeAdapterConfig.NewConfig() .AfterMappingAsync(async dest => { dest.Name = await GetName(); }); var poco = new Poco {Id = "foo"}; - var dto = poco.Adapt(); - dto.Name.ShouldBe("bar"); + + Should.Throw(() => poco.Adapt()); } [TestMethod] diff --git a/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj b/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj index c031e5cf..5a967fc6 100644 --- a/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj +++ b/src/Mapster.Async.Tests/Mapster.Async.Tests.csproj @@ -1,15 +1,19 @@  - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 true false + + $(TargetFrameworks);net48 + + - - - + + + diff --git a/src/Mapster.Async/Mapster.Async.csproj b/src/Mapster.Async/Mapster.Async.csproj index 8c980a39..a1500c90 100644 --- a/src/Mapster.Async/Mapster.Async.csproj +++ b/src/Mapster.Async/Mapster.Async.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 Async supports for Mapster true Mapster;Async diff --git a/src/Mapster.DependencyInjection.Tests/InjectionTest.cs b/src/Mapster.DependencyInjection.Tests/InjectionTest.cs index a1a1cb15..cf727af6 100644 --- a/src/Mapster.DependencyInjection.Tests/InjectionTest.cs +++ b/src/Mapster.DependencyInjection.Tests/InjectionTest.cs @@ -31,23 +31,26 @@ public void Injection() } } - [TestMethod, ExpectedException(typeof(InvalidOperationException))] + [TestMethod] public void NoServiceAdapter_InjectionError() { - var expectedValue = MapContext.Current.GetService().GetName(); - var config = ConfigureMapping(expectedValue); + Should.Throw(() => + { + var expectedValue = MapContext.Current.GetService().GetName(); + var config = ConfigureMapping(expectedValue); - IServiceCollection sc = new ServiceCollection(); - sc.AddScoped(); - sc.AddSingleton(config); - // We should use ServiceMapper in normal code - // but for this test we want to be sure the code will generate the InvalidOperationException - sc.AddScoped(); + IServiceCollection sc = new ServiceCollection(); + sc.AddScoped(); + sc.AddSingleton(config); + // We should use ServiceMapper in normal code + // but for this test we want to be sure the code will generate the InvalidOperationException + sc.AddScoped(); - var sp = sc.BuildServiceProvider(); - using var scope = sp.CreateScope(); - var mapper = scope.ServiceProvider.GetService(); - MapToDto(mapper, expectedValue); + var sp = sc.BuildServiceProvider(); + using var scope = sp.CreateScope(); + var mapper = scope.ServiceProvider.GetService(); + MapToDto(mapper, expectedValue); + }); } private static TypeAdapterConfig ConfigureMapping(string expectedValue) @@ -103,4 +106,4 @@ public class Dto public string Id { get; set; } public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj b/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj index 4a3b0ed0..ea6dd598 100644 --- a/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj +++ b/src/Mapster.DependencyInjection.Tests/Mapster.DependencyInjection.Tests.csproj @@ -1,16 +1,20 @@  - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 true false + + $(TargetFrameworks);net48 + + - - - + + + diff --git a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj index 246ad210..96c93028 100644 --- a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj +++ b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 Dependency Injection supports for Mapster true Mapster;DependencyInjection diff --git a/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj b/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj index 94549270..1068310f 100644 --- a/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj +++ b/src/Mapster.EFCore.Tests/Mapster.EFCore.Tests.csproj @@ -8,9 +8,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj b/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj index 027742ff..d9e1c376 100644 --- a/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj +++ b/src/Mapster.Immutable.Tests/Mapster.Immutable.Tests.csproj @@ -1,15 +1,19 @@  - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 true false + + $(TargetFrameworks);net48 + + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Mapster.Immutable/Mapster.Immutable.csproj b/src/Mapster.Immutable/Mapster.Immutable.csproj index 1f36f685..81060d57 100644 --- a/src/Mapster.Immutable/Mapster.Immutable.csproj +++ b/src/Mapster.Immutable/Mapster.Immutable.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 Immutable collection supports for Mapster true Mapster;Immutable diff --git a/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj b/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj index b4c020b6..ab1dc88d 100644 --- a/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj +++ b/src/Mapster.JsonNet.Tests/Mapster.JsonNet.Tests.csproj @@ -1,15 +1,19 @@  - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 true false + + $(TargetFrameworks);net48 + + - - - + + + diff --git a/src/Mapster.JsonNet/Mapster.JsonNet.csproj b/src/Mapster.JsonNet/Mapster.JsonNet.csproj index 3201ca43..5689b40b 100644 --- a/src/Mapster.JsonNet/Mapster.JsonNet.csproj +++ b/src/Mapster.JsonNet/Mapster.JsonNet.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 Json.net conversion supports for Mapster true Mapster;Json.net diff --git a/src/Mapster.Tests/Mapster.Tests.csproj b/src/Mapster.Tests/Mapster.Tests.csproj index d6b78546..a4908806 100644 --- a/src/Mapster.Tests/Mapster.Tests.csproj +++ b/src/Mapster.Tests/Mapster.Tests.csproj @@ -1,7 +1,7 @@ - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 true false Mapster.Tests @@ -10,13 +10,18 @@ true 11.0 + + + $(TargetFrameworks);net48 + - - - + + + + @@ -34,4 +39,4 @@ - \ No newline at end of file + diff --git a/src/Mapster.Tests/Polyfills/CompileWithDebugInfo.net48.cs b/src/Mapster.Tests/Polyfills/CompileWithDebugInfo.net48.cs new file mode 100644 index 00000000..92302ac1 --- /dev/null +++ b/src/Mapster.Tests/Polyfills/CompileWithDebugInfo.net48.cs @@ -0,0 +1,19 @@ +#if NET48 +using System; + +namespace System.Linq.Expressions +{ + internal static class CompileWithDebugInfoPolyfill + { + public static Delegate CompileWithDebugInfo(this LambdaExpression node) + { + return node.Compile(); + } + + public static T CompileWithDebugInfo(this Expression node) + { + return node.Compile(); + } + } +} +#endif diff --git a/src/Mapster.Tests/Polyfills/RequiredMemberAttributes.net48.cs b/src/Mapster.Tests/Polyfills/RequiredMemberAttributes.net48.cs new file mode 100644 index 00000000..1d87245e --- /dev/null +++ b/src/Mapster.Tests/Polyfills/RequiredMemberAttributes.net48.cs @@ -0,0 +1,38 @@ +#if NET48 +using System; + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] + internal sealed class RequiredMemberAttribute : Attribute + { + public RequiredMemberAttribute() + { + } + } + + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + + public string FeatureName { get; } + + public bool IsOptional { get; set; } + } +} + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] + internal sealed class SetsRequiredMembersAttribute : Attribute + { + public SetsRequiredMembersAttribute() + { + } + } +} +#endif diff --git a/src/Mapster.Tests/Polyfills/TypeGetConstructorOverload.net48.cs b/src/Mapster.Tests/Polyfills/TypeGetConstructorOverload.net48.cs new file mode 100644 index 00000000..31737b23 --- /dev/null +++ b/src/Mapster.Tests/Polyfills/TypeGetConstructorOverload.net48.cs @@ -0,0 +1,14 @@ +#if NET48 +using System; + +namespace System.Reflection +{ + internal static class TypeGetConstructorOverloadPolyfill + { + public static ConstructorInfo? GetConstructor(this Type type, BindingFlags bindingAttr, Type[] types) + { + return type.GetConstructor(bindingAttr, binder: null, types: types, modifiers: null); + } + } +} +#endif diff --git a/src/Mapster.Tests/WhenExplicitMappingRequired.cs b/src/Mapster.Tests/WhenExplicitMappingRequired.cs index 25b43474..bba796ec 100644 --- a/src/Mapster.Tests/WhenExplicitMappingRequired.cs +++ b/src/Mapster.Tests/WhenExplicitMappingRequired.cs @@ -133,12 +133,12 @@ public void Mapped_Classes_Succeed_When_List_To_IList_Is_Mapped() results.Count.ShouldBe(2); } - [TestMethod, ExpectedException(typeof(CompileException))] + [TestMethod] public void UnmappedChildPocoShouldFailed() { var config = new TypeAdapterConfig {RequireExplicitMapping = true}; var setter = config.NewConfig(); - setter.Compile(); // Should fail here + Should.Throw(() => setter.Compile()); // Should fail here } [TestMethod] @@ -251,4 +251,4 @@ public class CollectionDto #endregion } -} \ No newline at end of file +} diff --git a/src/Mapster.Tests/WhenIgnoreMapping.cs b/src/Mapster.Tests/WhenIgnoreMapping.cs index 245c4e63..9bed5dc3 100644 --- a/src/Mapster.Tests/WhenIgnoreMapping.cs +++ b/src/Mapster.Tests/WhenIgnoreMapping.cs @@ -133,7 +133,11 @@ private class Data723 : InterfaceSource723, InterfaceDestination723 static ConstructorInfo? GetConstructor() { - var parameterlessCtorInfo = typeof(TDestination).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, new Type[0]); + var parameterlessCtorInfo = typeof(TDestination).GetConstructor( + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public, + binder: null, + types: Type.EmptyTypes, + modifiers: null); var ctors = typeof(TDestination).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); var validCandidateCtors = ctors.Except(new[] { parameterlessCtorInfo }).ToArray(); diff --git a/src/Mapster.Tests/WhenMappingToInterface.cs b/src/Mapster.Tests/WhenMappingToInterface.cs index 00af2104..dbe7e429 100644 --- a/src/Mapster.Tests/WhenMappingToInterface.cs +++ b/src/Mapster.Tests/WhenMappingToInterface.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Json.Serialization; using Newtonsoft.Json; namespace Mapster.Tests diff --git a/src/Mapster.Tests/WhenMappingWithDictionary.cs b/src/Mapster.Tests/WhenMappingWithDictionary.cs index 8b53a3f6..feda6e41 100644 --- a/src/Mapster.Tests/WhenMappingWithDictionary.cs +++ b/src/Mapster.Tests/WhenMappingWithDictionary.cs @@ -274,8 +274,8 @@ public void AdaptClassWithIntegerKeyDictionary() }; var result = instanceWithDictionary.Adapt(); - result.Dict.ShouldContainKey(1); - result.Dict.ShouldContainKey(100); + result.Dict.ContainsKey(1).ShouldBeTrue(); + result.Dict.ContainsKey(100).ShouldBeTrue(); result.Dict[1].Name.ShouldBe("one"); result.Dict[100].Name.ShouldBe("one hundred"); } diff --git a/src/Mapster.Tests/WhenPerformingAfterMapping.cs b/src/Mapster.Tests/WhenPerformingAfterMapping.cs index 68b06a62..0996a12d 100644 --- a/src/Mapster.Tests/WhenPerformingAfterMapping.cs +++ b/src/Mapster.Tests/WhenPerformingAfterMapping.cs @@ -71,7 +71,7 @@ public void MapToType_Support_Destination_Parameter() }; // check expression is successfully compiled - Assert.ThrowsException(() => poco.Adapt()); + Should.Throw(() => poco.Adapt()); } [TestMethod] diff --git a/src/Mapster.Tests/WhenPerformingBeforeMapping.cs b/src/Mapster.Tests/WhenPerformingBeforeMapping.cs index 5c000802..63aa26a5 100644 --- a/src/Mapster.Tests/WhenPerformingBeforeMapping.cs +++ b/src/Mapster.Tests/WhenPerformingBeforeMapping.cs @@ -43,11 +43,11 @@ public void MapToType_Support_Destination_Parameter() { Id = Guid.NewGuid(), Name = "test", - }; - - // check expression is successfully compiled - Assert.ThrowsException(() => poco.Adapt()); - } + }; + + // check expression is successfully compiled + Should.Throw(() => poco.Adapt()); + } [TestMethod] public void MapToTarget_Support_Destination_Parameter() diff --git a/src/Mapster.Tool.Tests/Mapster.Tool.Tests.csproj b/src/Mapster.Tool.Tests/Mapster.Tool.Tests.csproj index 702c02e6..32897bb6 100644 --- a/src/Mapster.Tool.Tests/Mapster.Tool.Tests.csproj +++ b/src/Mapster.Tool.Tests/Mapster.Tool.Tests.csproj @@ -8,9 +8,13 @@ false + + $(TargetFrameworks);net48 + + - + @@ -26,6 +30,9 @@ + + + @@ -45,7 +52,11 @@ - + + + + + diff --git a/src/Mapster.Tool.Tests/Polyfills/IsExternalInit.net48.cs b/src/Mapster.Tool.Tests/Polyfills/IsExternalInit.net48.cs new file mode 100644 index 00000000..1b6e9e4f --- /dev/null +++ b/src/Mapster.Tool.Tests/Polyfills/IsExternalInit.net48.cs @@ -0,0 +1,8 @@ +#if NET48 +namespace System.Runtime.CompilerServices +{ + internal sealed class IsExternalInit + { + } +} +#endif diff --git a/src/Mapster.Tool.Tests/TestBase.cs b/src/Mapster.Tool.Tests/TestBase.cs index 4bc6c6b8..221a1f06 100644 --- a/src/Mapster.Tool.Tests/TestBase.cs +++ b/src/Mapster.Tool.Tests/TestBase.cs @@ -17,7 +17,7 @@ private ServiceCollection ConfigureServiceCollection() { ServiceCollection services = new(); services.Scan(selector => selector - .FromCallingAssembly() + .FromAssemblyOf() .AddClasses() .AsMatchingInterface() .WithSingletonLifetime()); @@ -35,4 +35,4 @@ protected TInterface GetMappingInterface() return (TInterface)service; } -} \ No newline at end of file +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index a3ffb648..b519a334 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -217,7 +217,7 @@ protected Expression CreateBlockExpressionBody(Expression source, Expression? de Expression? set; var requiremembers = arg.DestinationType.GetProperties() .Where(x => x.GetCustomAttributes() - .Any(y => y.GetType() == typeof(System.Runtime.CompilerServices.RequiredMemberAttribute))); + .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")); if (requiremembers.Count() != 0) set = CreateInlineExpression(source, arg, true); diff --git a/src/Mapster/Adapters/BaseClassAdapter.cs b/src/Mapster/Adapters/BaseClassAdapter.cs index a50c4b73..4e246dc6 100644 --- a/src/Mapster/Adapters/BaseClassAdapter.cs +++ b/src/Mapster/Adapters/BaseClassAdapter.cs @@ -50,9 +50,7 @@ select fn(src, destinationMember, arg)) s.Visit(getter); - var match = arg.Settings.ProjectToTypeResolvers.GetValueOrDefault(s.MemeberName); - - if (match != null) + if (arg.Settings.ProjectToTypeResolvers.TryGetValue(s.MemeberName, out var match)) { arg.Settings.Resolvers.Add(new InvokerModel { @@ -119,7 +117,7 @@ select fn(src, destinationMember, arg)) && destinationMember.Info is PropertyInfo propinfo) { if (propinfo.GetCustomAttributes() - .Any(y => y.GetType() == typeof(System.Runtime.CompilerServices.RequiredMemberAttribute))) + .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")) { getter = destinationMember.Type.CreateDefault(); } @@ -281,8 +279,9 @@ protected virtual ClassModel GetSetterModel(CompileArgument arg) protected void IgnoreNonMapped (ClassModel classModel, CompileArgument arg) { - var notMappingToIgnore = classModel.Members - .ExceptBy(arg.Settings.Resolvers.Select(x => x.DestinationMemberName), + var notMappingToIgnore = LinqCompat.ExceptBy( + classModel.Members, + arg.Settings.Resolvers.Select(x => x.DestinationMemberName), y => y.Name); foreach (var item in notMappingToIgnore) @@ -298,7 +297,7 @@ protected virtual ClassModel GetOnlyRequiredPropertySetterModel(CompileArgument Members = arg.DestinationType.GetFieldsAndProperties(true) .Where(x => x.GetType() == typeof(PropertyModel)) .Where(y => ((PropertyInfo)y.Info).GetCustomAttributes() - .Any(y => y.GetType() == typeof(System.Runtime.CompilerServices.RequiredMemberAttribute))) + .Any(y => y.GetType().FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute")) }; } diff --git a/src/Mapster/Compile/CompileArgument.cs b/src/Mapster/Compile/CompileArgument.cs index e9015449..d324661d 100644 --- a/src/Mapster/Compile/CompileArgument.cs +++ b/src/Mapster/Compile/CompileArgument.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using Mapster.Utils; namespace Mapster { @@ -19,19 +20,23 @@ public class CompileArgument private HashSet? _srcNames; internal HashSet GetSourceNames() { - return _srcNames ??= (from it in Settings.Resolvers - where it.SourceMemberName != null - select it.SourceMemberName!.Split('.').First()).ToHashSet(); + return _srcNames ??= new HashSet( + from it in Settings.Resolvers + where it.SourceMemberName != null + select it.SourceMemberName!.Split('.').First(), + StringComparer.Ordinal); } private HashSet? _destNames; internal HashSet GetDestinationNames(bool split = true) { - return _destNames ??= (from it in Settings.Resolvers - where it.DestinationMemberName != null - select split - ? it.DestinationMemberName.Split('.').First() - : it.DestinationMemberName).ToHashSet(); + return _destNames ??= new HashSet( + from it in Settings.Resolvers + where it.DestinationMemberName != null + select split + ? it.DestinationMemberName.Split('.').First() + : it.DestinationMemberName, + StringComparer.Ordinal); } private bool _fetchConstructUsing; @@ -44,4 +49,4 @@ select split return _constructUsing; } } -} \ No newline at end of file +} diff --git a/src/Mapster/Interfaces/IMapFrom.cs b/src/Mapster/Interfaces/IMapFrom.cs index f1b004c7..0ee89685 100644 --- a/src/Mapster/Interfaces/IMapFrom.cs +++ b/src/Mapster/Interfaces/IMapFrom.cs @@ -2,8 +2,4 @@ namespace Mapster; public interface IMapFrom { - public void ConfigureMapping(TypeAdapterConfig config) - { - config.NewConfig(typeof(TSource), GetType()); - } -} \ No newline at end of file +} diff --git a/src/Mapster/Mapper.cs b/src/Mapster/Mapper.cs index 2694c7f3..8ada2530 100644 --- a/src/Mapster/Mapper.cs +++ b/src/Mapster/Mapper.cs @@ -1,6 +1,7 @@ using System; using System.Reflection; using Mapster; +using Mapster.Utils; // ReSharper disable once CheckNamespace namespace MapsterMapper @@ -84,17 +85,10 @@ public virtual TDestination Map(TSource source, TDestinat public virtual object Map(object source, Type sourceType, Type destinationType) { 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); - } + var canUseDynamic = sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible; + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return DelegateInvokeCompat.InvokeMap(del, source, canUseDynamic)!; } @@ -106,20 +100,13 @@ public virtual object Map(object source, Type sourceType, Type destinationType) /// Source type to map. /// Destination type to map. /// mapped result object - public virtual object Map(object source, object destination, Type sourceType, Type destinationType) + public virtual object Map(object source, object destination, Type sourceType, Type destinationType) { 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); - } + var canUseDynamic = sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible; + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return DelegateInvokeCompat.InvokeMapToTarget(del, source, destination, canUseDynamic)!; } } diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index bc6f2dd1..bbe51e94 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -4,7 +4,7 @@ A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. Copyright (c) 2016 Chaowlert Chaisrichalermpol, Eric Swann chaowlert;eric_swann - net10.0;net9.0;net8.0; + netstandard2.0;net10.0;net9.0;net8.0 Mapster A fast, fun and stimulating object to object mapper. Kind of like AutoMapper, just simpler and way, way faster. Mapster @@ -20,13 +20,19 @@ enable 1701;1702;8618 + + 10 + + + + - \ No newline at end of file + diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index 218045bf..94eca840 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using Mapster.Models; +using Mapster.Utils; namespace Mapster { @@ -25,7 +26,6 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so /// Destination type. /// Source object to adapt. /// Adapted destination type. - [return: NotNullIfNotNull(nameof(source))] public static TDestination? Adapt(this object? source) { return Adapt(source, TypeAdapterConfig.GlobalSettings); @@ -38,7 +38,6 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so /// Source object to adapt. /// Configuration /// Adapted destination type. - [return: NotNullIfNotNull(nameof(source))] public static TDestination? Adapt(this object? source, TypeAdapterConfig config) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse @@ -115,20 +114,9 @@ public static TDestination Adapt(this TSource source, TDe private static TDestination UpdateFuncFromPackedinObject(TSource source, TDestination destination, TypeAdapterConfig config, Type sourceType, Type destinationType) { - dynamic del = config.GetMapToTargetFunction(sourceType, destinationType); - - - if (sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible) - { - dynamic objfn = del; - return objfn((dynamic)source, (dynamic)destination); - } - else - { - //NOTE: if type is non-public, we cannot use dynamic - //DynamicInvoke is slow, but works with non-public - return (TDestination)del.DynamicInvoke(source, destination); - } + var del = config.GetMapToTargetFunction(sourceType, destinationType); + var canUseDynamic = sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible; + return DelegateInvokeCompat.InvokeMapToTarget(del, source!, destination!, canUseDynamic); } /// @@ -160,17 +148,10 @@ private static TDestination UpdateFuncFromPackedinObject( public static object? Adapt(this object source, Type sourceType, Type destinationType, TypeAdapterConfig config) { 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); - } + var canUseDynamic = sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible; + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return DelegateInvokeCompat.InvokeMap(del, source, canUseDynamic); } /// @@ -198,17 +179,10 @@ private static TDestination UpdateFuncFromPackedinObject( public static object? Adapt(this object source, object destination, Type sourceType, Type destinationType, TypeAdapterConfig config) { 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); - } + var canUseDynamic = sourceType.GetTypeInfo().IsVisible && destinationType.GetTypeInfo().IsVisible; + //NOTE: if type is non-public, we cannot use dynamic + //DynamicInvoke is slow, but works with non-public + return DelegateInvokeCompat.InvokeMapToTarget(del, source, destination, canUseDynamic); } /// diff --git a/src/Mapster/Utils/CodeAnalysisAttributes.cs b/src/Mapster/Utils/CodeAnalysisAttributes.cs new file mode 100644 index 00000000..212fb714 --- /dev/null +++ b/src/Mapster/Utils/CodeAnalysisAttributes.cs @@ -0,0 +1,20 @@ +#if NETSTANDARD2_0 +using System; + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + public sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + public bool ReturnValue { get; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue, Inherited = false)] + public sealed class NotNullIfNotNullAttribute : Attribute + { + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + public string ParameterName { get; } + } +} +#endif diff --git a/src/Mapster/Utils/DelegateInvokeCompat.cs b/src/Mapster/Utils/DelegateInvokeCompat.cs new file mode 100644 index 00000000..adea6cf3 --- /dev/null +++ b/src/Mapster/Utils/DelegateInvokeCompat.cs @@ -0,0 +1,47 @@ +using System; + +namespace Mapster.Utils +{ + internal static class DelegateInvokeCompat + { + public static object? InvokeMap(Delegate del, object source, bool canUseDynamic) + { +#if NET8_0_OR_GREATER + if (canUseDynamic) + { + dynamic fn = del; + return fn((dynamic)source); + } +#endif + return del.DynamicInvoke(source); + } + + public static object? InvokeMapToTarget(Delegate del, object source, object destination, bool canUseDynamic) + { +#if NET8_0_OR_GREATER + if (canUseDynamic) + { + dynamic fn = del; + return fn((dynamic)source, (dynamic)destination); + } +#endif + return del.DynamicInvoke(source, destination); + } + + public static TDestination InvokeMapToTarget( + Delegate del, + object source, + object destination, + bool canUseDynamic) + { +#if NET8_0_OR_GREATER + if (canUseDynamic) + { + dynamic fn = del; + return (TDestination)fn((dynamic)source, (dynamic)destination); + } +#endif + return (TDestination)del.DynamicInvoke(source, destination); + } + } +} diff --git a/src/Mapster/Utils/ExpressionEx.cs b/src/Mapster/Utils/ExpressionEx.cs index f8011b5b..090d81e1 100644 --- a/src/Mapster/Utils/ExpressionEx.cs +++ b/src/Mapster/Utils/ExpressionEx.cs @@ -2,9 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Linq.Expressions; using System.Reflection; namespace Mapster.Utils @@ -40,7 +38,7 @@ public static Expression PropertyOrFieldPath(Expression expr, string path) // For dynamically built types, it is possible to have periods in the property name. // Rejoin an incrementing number of parts with periods to try and find a property match. - if (IsPropertyOrFieldPathWithPeriods(current, props[i..], out next, out int combinationLength)) + if (IsPropertyOrFieldPathWithPeriods(current, props, i, out next, out int combinationLength)) { current = next; i += combinationLength - 1; @@ -53,18 +51,19 @@ public static Expression PropertyOrFieldPath(Expression expr, string path) return current; } - private static bool IsPropertyOrFieldPathWithPeriods(Expression expr, string[] path, [NotNullWhen(true)] out Expression? propExpr, out int combinationLength) + private static bool IsPropertyOrFieldPathWithPeriods(Expression expr, string[] path, int startIndex, out Expression? propExpr, out int combinationLength) { - if (path.Length < 2) + var remaining = path.Length - startIndex; + if (remaining < 2) { propExpr = null; combinationLength = 0; return false; } - for (int count = 2; count <= path.Length; count++) + for (int count = 2; count <= remaining; count++) { - string prop = string.Join('.', path[..count]); + string prop = string.Join(".", path, startIndex, count); if (IsPropertyOrField(expr, prop, out propExpr)) { combinationLength = count; @@ -77,7 +76,7 @@ private static bool IsPropertyOrFieldPathWithPeriods(Expression expr, string[] p return false; } - private static bool IsDictionaryKey(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr) + private static bool IsDictionaryKey(Expression expr, string prop, out Expression? propExpr) { var type = expr.Type; var dictType = type.GetDictionaryType(); @@ -96,7 +95,7 @@ private static bool IsDictionaryKey(Expression expr, string prop, [NotNullWhen(t return true; } - private static bool IsPropertyOrField(Expression expr, string prop, [NotNullWhen(true)] out Expression? propExpr) + private static bool IsPropertyOrField(Expression expr, string prop, out Expression? propExpr) { Type type = expr.Type; diff --git a/src/Mapster/Utils/InterfaceDynamicMapper.cs b/src/Mapster/Utils/InterfaceDynamicMapper.cs index e8ddee32..ea39f430 100644 --- a/src/Mapster/Utils/InterfaceDynamicMapper.cs +++ b/src/Mapster/Utils/InterfaceDynamicMapper.cs @@ -20,17 +20,27 @@ internal void ApplyMappingFromAssembly() { foreach (var type in _types) { - var instance = Activator.CreateInstance(type); var method = GetMethod(type); - method!.Invoke(instance, new object[] { _config }); + if (method != null) + { + var instance = Activator.CreateInstance(type); + method.Invoke(instance, new object[] { _config }); + continue; + } + + var sourceType = type.GetInterfaces() + .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>)) + .GetGenericArguments()[0]; + _config.NewConfig(sourceType, type); } } - private static MethodInfo GetMethod(Type type) + private static MethodInfo? GetMethod(Type type) { const string methodName = "ConfigureMapping"; var method = type.GetMethod(methodName); - if (method == null) return type.GetInterface("IMapFrom`1")!.GetMethod(methodName)!; + if (method == null) + return null; var parameters = method.GetParameters(); var condition = parameters.Length == 1 && parameters[0].ParameterType == typeof(TypeAdapterConfig); if (!condition) @@ -40,4 +50,4 @@ private static MethodInfo GetMethod(Type type) return method; } -} \ No newline at end of file +} diff --git a/src/Mapster/Utils/IsExternalInit.cs b/src/Mapster/Utils/IsExternalInit.cs new file mode 100644 index 00000000..33338a32 --- /dev/null +++ b/src/Mapster/Utils/IsExternalInit.cs @@ -0,0 +1,8 @@ +#if NETSTANDARD2_0 +namespace System.Runtime.CompilerServices +{ + internal sealed class IsExternalInit + { + } +} +#endif diff --git a/src/Mapster/Utils/LinqCompat.cs b/src/Mapster/Utils/LinqCompat.cs new file mode 100644 index 00000000..94f55be1 --- /dev/null +++ b/src/Mapster/Utils/LinqCompat.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mapster.Utils +{ + internal static class LinqCompat + { + public static IEnumerable IntersectBy( + IEnumerable source, + IEnumerable keys, + Func keySelector, + IEqualityComparer? comparer = null) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + +#if NET8_0_OR_GREATER + return source.IntersectBy(keys, keySelector, comparer); +#else + return IntersectByFallback(source, keys, keySelector, comparer); +#endif + } + + public static IEnumerable ExceptBy( + IEnumerable source, + IEnumerable keys, + Func keySelector, + IEqualityComparer? comparer = null) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keys == null) + throw new ArgumentNullException(nameof(keys)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + +#if NET8_0_OR_GREATER + return source.ExceptBy(keys, keySelector, comparer); +#else + return ExceptByFallback(source, keys, keySelector, comparer); +#endif + } + +#if !NET8_0_OR_GREATER + private static IEnumerable IntersectByFallback( + IEnumerable source, + IEnumerable keys, + Func keySelector, + IEqualityComparer? comparer) + { + var keySet = new HashSet(keys, comparer); + foreach (var item in source) + { + if (keySet.Remove(keySelector(item))) + yield return item; + } + } + + private static IEnumerable ExceptByFallback( + IEnumerable source, + IEnumerable keys, + Func keySelector, + IEqualityComparer? comparer) + { + var keySet = new HashSet(keys, comparer); + var seen = new HashSet(comparer); + foreach (var item in source) + { + var key = keySelector(item); + if (keySet.Contains(key) || !seen.Add(key)) + continue; + yield return item; + } + } +#endif + } +} diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index 9abe09f8..3b9b1a1b 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -99,7 +99,10 @@ IEnumerable GetFieldsFunc(Type t, MemberInfo[] overlapMembers) = public static IEnumerable DropHiddenMembers(this IEnumerable allMembers, ICollection currentTypeMembers) where T : MemberInfo { - var compareMemberNames = allMembers.IntersectBy(currentTypeMembers.Select(x => x.Name), x => x.Name).Select(x => x.Name); + var compareMemberNames = LinqCompat.IntersectBy( + allMembers, + currentTypeMembers.Select(x => x.Name), + x => x.Name).Select(x => x.Name); foreach (var member in allMembers) { @@ -407,8 +410,8 @@ public static bool IsInitOnly(this PropertyInfo propertyInfo) if (setMethod == null) return false; - var isExternalInitType = typeof(System.Runtime.CompilerServices.IsExternalInit); - return setMethod.ReturnParameter.GetRequiredCustomModifiers().Contains(isExternalInitType); + return setMethod.ReturnParameter.GetRequiredCustomModifiers() + .Any(it => it.FullName == "System.Runtime.CompilerServices.IsExternalInit"); } public static bool IsAssignableToGenericType(this Type derivedType, Type genericType) @@ -446,4 +449,4 @@ public static bool isDefaultCtor(this Type type) return type.GetConstructor(new Type[] { }) is not null ? true : false; } } -} \ No newline at end of file +} diff --git a/src/Sample.CodeGen/Startup.cs b/src/Sample.CodeGen/Startup.cs index 5b16a61c..f00ce1f4 100644 --- a/src/Sample.CodeGen/Startup.cs +++ b/src/Sample.CodeGen/Startup.cs @@ -31,7 +31,7 @@ public void ConfigureServices(IServiceCollection services) options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddProblemDetails(); - services.Scan(selector => selector.FromCallingAssembly() + services.Scan(selector => selector.FromAssemblyOf() .AddClasses().AsMatchingInterface().WithSingletonLifetime()); } diff --git a/src/TemplateTest/TemplateTest.csproj b/src/TemplateTest/TemplateTest.csproj index ae4dff09..9fac3670 100644 --- a/src/TemplateTest/TemplateTest.csproj +++ b/src/TemplateTest/TemplateTest.csproj @@ -1,16 +1,20 @@  - net10.0;net9.0;net8.0; + net10.0;net9.0;net8.0 enable true false + + $(TargetFrameworks);net48 + + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive From ad1d0b7b4ea6d5e1e8af409ce314db042e27a899 Mon Sep 17 00:00:00 2001 From: Alexey Sokolov Date: Fri, 6 Feb 2026 14:07:38 +0700 Subject: [PATCH 02/13] fix: restore IMapFrom fallback mapping --- src/Mapster.Tests/WhenUsingIMapFrom.cs | 17 +++++++++++++---- src/Mapster/Interfaces/IMapFrom.cs | 6 ++++++ src/Mapster/Utils/InterfaceDynamicMapper.cs | 9 +++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Mapster.Tests/WhenUsingIMapFrom.cs b/src/Mapster.Tests/WhenUsingIMapFrom.cs index 7de9f381..e280eeb8 100644 --- a/src/Mapster.Tests/WhenUsingIMapFrom.cs +++ b/src/Mapster.Tests/WhenUsingIMapFrom.cs @@ -11,24 +11,31 @@ namespace Mapster.Tests; [TestClass] public class WhenUsingIMapFrom { + private readonly TypeAdapterConfig _config; private readonly Mapper _mapper; + private readonly List _types; public WhenUsingIMapFrom() { - _mapper = new Mapper(); - var types = new List + _config = new TypeAdapterConfig + { + RequireExplicitMapping = true + }; + _mapper = new Mapper(_config); + _types = new List { typeof(SourceModel), typeof(InheritedDestinationModel), typeof(DestinationModel) }; - TypeAdapterConfig.GlobalSettings.ScanInheritedTypes(types); } [TestMethod] public void TestIMapFrom_WhenMethodIsNotImplemented() { var source = new SourceModel(DesireValues.Text); + Should.Throw(() => _mapper.Map(source)); + _config.ScanInheritedTypes(_types); var destination = _mapper.Map(source); destination.Type.ShouldBe(DesireValues.Text); } @@ -37,6 +44,8 @@ public void TestIMapFrom_WhenMethodIsNotImplemented() public void TestIMapFrom_WhenMethodImplemented() { var source = new SourceModel(DesireValues.Text); + Should.Throw(() => _mapper.Map(source)); + _config.ScanInheritedTypes(_types); var destination = _mapper.Map(source); destination.Type.ShouldBe(DesireValues.Text); destination.Value.ShouldBe(9); @@ -74,4 +83,4 @@ public void ConfigureMapping(TypeAdapterConfig config) public class DestinationModel : IMapFrom { public string Type { get; set; } -} \ No newline at end of file +} diff --git a/src/Mapster/Interfaces/IMapFrom.cs b/src/Mapster/Interfaces/IMapFrom.cs index 0ee89685..f16446a4 100644 --- a/src/Mapster/Interfaces/IMapFrom.cs +++ b/src/Mapster/Interfaces/IMapFrom.cs @@ -2,4 +2,10 @@ namespace Mapster; public interface IMapFrom { +#if !NETSTANDARD2_0 + public void ConfigureMapping(TypeAdapterConfig config) + { + config.NewConfig(typeof(TSource), GetType()); + } +#endif } diff --git a/src/Mapster/Utils/InterfaceDynamicMapper.cs b/src/Mapster/Utils/InterfaceDynamicMapper.cs index ea39f430..f3557cdf 100644 --- a/src/Mapster/Utils/InterfaceDynamicMapper.cs +++ b/src/Mapster/Utils/InterfaceDynamicMapper.cs @@ -38,9 +38,14 @@ internal void ApplyMappingFromAssembly() private static MethodInfo? GetMethod(Type type) { const string methodName = "ConfigureMapping"; - var method = type.GetMethod(methodName); - if (method == null) + var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly) + .Where(m => m.Name == methodName) + .ToList(); + if (methods.Count == 0) return null; + if (methods.Count != 1) + throw new Exception($"{methodName} is not implemented right or it's ambiguous!"); + var method = methods[0]; var parameters = method.GetParameters(); var condition = parameters.Length == 1 && parameters[0].ParameterType == typeof(TypeAdapterConfig); if (!condition) From 7e113d2aa7cf3f2e94cef8606c98e104cee9cbb5 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Mon, 16 Feb 2026 18:53:32 +0500 Subject: [PATCH 03/13] chore: Updated version numbers to 10.0.0-pre02 --- src/Mapster.Async/Mapster.Async.csproj | 2 +- src/Mapster.Core/Mapster.Core.csproj | 2 +- .../Mapster.DependencyInjection.csproj | 2 +- src/Mapster.EF6/Mapster.EF6.csproj | 2 +- src/Mapster.EFCore/Mapster.EFCore.csproj | 2 +- src/Mapster.Immutable/Mapster.Immutable.csproj | 2 +- src/Mapster.JsonNet/Mapster.JsonNet.csproj | 2 +- src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj | 2 +- src/Mapster.Tool/Mapster.Tool.csproj | 2 +- src/Mapster/Mapster.csproj | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Mapster.Async/Mapster.Async.csproj b/src/Mapster.Async/Mapster.Async.csproj index a1500c90..2e3adba7 100644 --- a/src/Mapster.Async/Mapster.Async.csproj +++ b/src/Mapster.Async/Mapster.Async.csproj @@ -7,7 +7,7 @@ Mapster;Async true Mapster.Async.snk - 10.0.0-pre01 + 10.0.0-pre02 diff --git a/src/Mapster.Core/Mapster.Core.csproj b/src/Mapster.Core/Mapster.Core.csproj index 715f5974..1eb0f7e9 100644 --- a/src/Mapster.Core/Mapster.Core.csproj +++ b/src/Mapster.Core/Mapster.Core.csproj @@ -4,7 +4,7 @@ netstandard2.0 Mapster.Core Mapster - 10.0.0-pre01 + 10.0.0-pre02 enable true true diff --git a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj index 96c93028..e8ab2973 100644 --- a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj +++ b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj @@ -7,7 +7,7 @@ Mapster;DependencyInjection true Mapster.DependencyInjection.snk - 10.0.0-pre01 + 10.0.0-pre02 diff --git a/src/Mapster.EF6/Mapster.EF6.csproj b/src/Mapster.EF6/Mapster.EF6.csproj index acfe07f5..e8ebe87a 100644 --- a/src/Mapster.EF6/Mapster.EF6.csproj +++ b/src/Mapster.EF6/Mapster.EF6.csproj @@ -8,7 +8,7 @@ True true Mapster.EF6.snk - 10.0.0-pre01 + 10.0.0-pre02 diff --git a/src/Mapster.EFCore/Mapster.EFCore.csproj b/src/Mapster.EFCore/Mapster.EFCore.csproj index d721aa9e..3c3583ce 100644 --- a/src/Mapster.EFCore/Mapster.EFCore.csproj +++ b/src/Mapster.EFCore/Mapster.EFCore.csproj @@ -8,7 +8,7 @@ True true Mapster.EFCore.snk - 10.0.0-pre01 + 10.0.0-pre02 diff --git a/src/Mapster.Immutable/Mapster.Immutable.csproj b/src/Mapster.Immutable/Mapster.Immutable.csproj index 81060d57..7247081a 100644 --- a/src/Mapster.Immutable/Mapster.Immutable.csproj +++ b/src/Mapster.Immutable/Mapster.Immutable.csproj @@ -7,7 +7,7 @@ Mapster;Immutable true Mapster.Immutable.snk - 10.0.0-pre01 + 10.0.0-pre02 enable diff --git a/src/Mapster.JsonNet/Mapster.JsonNet.csproj b/src/Mapster.JsonNet/Mapster.JsonNet.csproj index 5689b40b..d77982da 100644 --- a/src/Mapster.JsonNet/Mapster.JsonNet.csproj +++ b/src/Mapster.JsonNet/Mapster.JsonNet.csproj @@ -7,7 +7,7 @@ Mapster;Json.net true Mapster.JsonNet.snk - 10.0.0-pre01 + 10.0.0-pre02 diff --git a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj index b4b88947..16036201 100644 --- a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj +++ b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj @@ -7,7 +7,7 @@ true Mapster.SourceGenerator.snk https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources) - 10.0.0-pre01 + 10.0.0-pre02 enable false diff --git a/src/Mapster.Tool/Mapster.Tool.csproj b/src/Mapster.Tool/Mapster.Tool.csproj index 8abc6c96..06b92931 100644 --- a/src/Mapster.Tool/Mapster.Tool.csproj +++ b/src/Mapster.Tool/Mapster.Tool.csproj @@ -10,7 +10,7 @@ Mapster;Tool true Mapster.Tool.snk - 10.0.0-pre01 + 10.0.0-pre02 enable diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index bbe51e94..21b90ea7 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -16,7 +16,7 @@ true Mapster - 10.0.0-pre01 + 10.0.0-pre02 enable 1701;1702;8618 From eed3c8001c280daf2d58b1812da42a6eb46bcb10 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 6 Mar 2026 16:33:58 +0500 Subject: [PATCH 04/13] fix(docs): fix Records type description to Mapster v10 --- docs/articles/mapping/Data-types.md | 80 ++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/docs/articles/mapping/Data-types.md b/docs/articles/mapping/Data-types.md index f4951ed4..edc0c163 100644 --- a/docs/articles/mapping/Data-types.md +++ b/docs/articles/mapping/Data-types.md @@ -94,6 +94,77 @@ var dict = point.Adapt>(); dict["Y"].ShouldBe(3); ``` +## Record types + +>[!IMPORTANT] +> Mapster treats Record type as an immutable type. +> In this regard, only a with-like non-destructive mutation is available. +> +> ```csharp +> var result = source.adapt(data) +>//equal var result = data with { X = source.X.Adapt(), ...} +>``` + +### Features and Limitations: + +# [v10.0](#tab/Records-v10) + +>[!NOTE] +> By default, all [C# Records](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record) are defined as a record type. +> Limitations by count of constructors and constructor parameters used in Mapster version 7.4.0 do not apply. + + +#### Using default value in constuctor param + +If the source type does not contain members that can be used as constructor parameters, then will be used the default values ​​for the parameter type. + +Example: + +```csharp + +class SourceData +{ + public string MyString {get; set;} +} + +record RecordDestination(int myInt, string myString); + +var result = source.Adapt() + +// equal var result = new RecordDestination (default(int),source.myString) + +``` + +#### MultyConsturctor Record types + +If there is more than one constructor, by default, mapping will be performed on the constructor with the largest number of parameters. + +Example: + +```csharp +record MultiCtorRecord +{ + public MultiCtorRecord(int myInt) + { + MyInt = myInt; + } + + public MultiCtorRecord(int myInt, string myString) // This constructor will be used + : this(myInt) + { + MyString = myString; + } + +} +``` + +# [v7.4.0](#tab/Records-v7-4-0) + +>[!NOTE] +>Record type must not have a setter and have only one non-empty constructor, and all parameter names must match with properties. + +Otherwise you need to add [`MapToConstructor` configuration](xref:Mapster.Settings.ConstructorMapping#map-to-constructor). + Example for record types: ```csharp @@ -110,5 +181,12 @@ class Person { var src = new { Name = "Mapster", Age = 3 }; var target = src.Adapt(); ``` +--- + +### Support additional mapping features: -There are limitations to map Record type automatically. Record type must not have a setter and have only one non-empty constructor, and all parameter names must match with properties. Otherwise you need to add [`MapToConstructor` configuration](xref:Mapster.Settings.ConstructorMapping#map-to-constructor). +| Mapping features | v7.4.0 | v10.0 | +|:-----------------|:------:|:-----:| +|Custom constructor mapping| - | ✅ | +|Ignore| - | ✅ | +|IgnoreNullValues| - | ✅ | \ No newline at end of file From 5ab00d75d41f5230ca19ba172fbc4c4a1ef0ee2f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Fri, 6 Mar 2026 19:53:02 +0500 Subject: [PATCH 05/13] fix: restore nullable annotations to TypeAdapter --- src/Mapster/TypeAdapter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index 94eca840..b05c1ba7 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -26,6 +26,7 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so /// Destination type. /// Source object to adapt. /// Adapted destination type. + [return: NotNullIfNotNull(nameof(source))] public static TDestination? Adapt(this object? source) { return Adapt(source, TypeAdapterConfig.GlobalSettings); @@ -38,6 +39,7 @@ public static ITypeAdapterBuilder BuildAdapter(this TSource so /// Source object to adapt. /// Configuration /// Adapted destination type. + [return: NotNullIfNotNull(nameof(source))] public static TDestination? Adapt(this object? source, TypeAdapterConfig config) { // ReSharper disable once ConditionIsAlwaysTrueOrFalse From e37865d18aa91568c69bf775fe62e3bbbf17b7ad Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 7 Mar 2026 06:10:00 +0500 Subject: [PATCH 06/13] chore: Updated version numbers to 10.0.0 release --- src/ExpressionDebugger/ExpressionDebugger.csproj | 2 +- src/ExpressionTranslator/ExpressionTranslator.csproj | 2 +- src/Mapster.Async/Mapster.Async.csproj | 2 +- src/Mapster.Core/Mapster.Core.csproj | 2 +- .../Mapster.DependencyInjection.csproj | 2 +- src/Mapster.EF6/Mapster.EF6.csproj | 2 +- src/Mapster.EFCore/Mapster.EFCore.csproj | 2 +- src/Mapster.Immutable/Mapster.Immutable.csproj | 2 +- src/Mapster.JsonNet/Mapster.JsonNet.csproj | 2 +- src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj | 2 +- src/Mapster.Tool/Mapster.Tool.csproj | 2 +- src/Mapster/Mapster.csproj | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ExpressionDebugger/ExpressionDebugger.csproj b/src/ExpressionDebugger/ExpressionDebugger.csproj index e5d48bba..90abc2f4 100644 --- a/src/ExpressionDebugger/ExpressionDebugger.csproj +++ b/src/ExpressionDebugger/ExpressionDebugger.csproj @@ -12,7 +12,7 @@ True true ExpressionDebugger.snk - 10.0.0-pre01 + 10.0.0 https://github.com/chaowlert/ExpressionDebugger/blob/master/LICENSE 8.0 enable diff --git a/src/ExpressionTranslator/ExpressionTranslator.csproj b/src/ExpressionTranslator/ExpressionTranslator.csproj index 6783d9b5..635bb977 100644 --- a/src/ExpressionTranslator/ExpressionTranslator.csproj +++ b/src/ExpressionTranslator/ExpressionTranslator.csproj @@ -12,7 +12,7 @@ True true ExpressionTranslator.snk - 10.0.0-pre01 + 10.0.0 ExpressionDebugger MIT icon.png diff --git a/src/Mapster.Async/Mapster.Async.csproj b/src/Mapster.Async/Mapster.Async.csproj index 2e3adba7..092b90a2 100644 --- a/src/Mapster.Async/Mapster.Async.csproj +++ b/src/Mapster.Async/Mapster.Async.csproj @@ -7,7 +7,7 @@ Mapster;Async true Mapster.Async.snk - 10.0.0-pre02 + 10.0.0 diff --git a/src/Mapster.Core/Mapster.Core.csproj b/src/Mapster.Core/Mapster.Core.csproj index 1eb0f7e9..f57efaf1 100644 --- a/src/Mapster.Core/Mapster.Core.csproj +++ b/src/Mapster.Core/Mapster.Core.csproj @@ -4,7 +4,7 @@ netstandard2.0 Mapster.Core Mapster - 10.0.0-pre02 + 10.0.0 enable true true diff --git a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj index e8ab2973..82d46a2f 100644 --- a/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj +++ b/src/Mapster.DependencyInjection/Mapster.DependencyInjection.csproj @@ -7,7 +7,7 @@ Mapster;DependencyInjection true Mapster.DependencyInjection.snk - 10.0.0-pre02 + 10.0.0 diff --git a/src/Mapster.EF6/Mapster.EF6.csproj b/src/Mapster.EF6/Mapster.EF6.csproj index e8ebe87a..21ce481f 100644 --- a/src/Mapster.EF6/Mapster.EF6.csproj +++ b/src/Mapster.EF6/Mapster.EF6.csproj @@ -8,7 +8,7 @@ True true Mapster.EF6.snk - 10.0.0-pre02 + 10.0.0 diff --git a/src/Mapster.EFCore/Mapster.EFCore.csproj b/src/Mapster.EFCore/Mapster.EFCore.csproj index 3c3583ce..377fd86d 100644 --- a/src/Mapster.EFCore/Mapster.EFCore.csproj +++ b/src/Mapster.EFCore/Mapster.EFCore.csproj @@ -8,7 +8,7 @@ True true Mapster.EFCore.snk - 10.0.0-pre02 + 10.0.0 diff --git a/src/Mapster.Immutable/Mapster.Immutable.csproj b/src/Mapster.Immutable/Mapster.Immutable.csproj index 7247081a..7b791ce1 100644 --- a/src/Mapster.Immutable/Mapster.Immutable.csproj +++ b/src/Mapster.Immutable/Mapster.Immutable.csproj @@ -7,7 +7,7 @@ Mapster;Immutable true Mapster.Immutable.snk - 10.0.0-pre02 + 10.0.0 enable diff --git a/src/Mapster.JsonNet/Mapster.JsonNet.csproj b/src/Mapster.JsonNet/Mapster.JsonNet.csproj index d77982da..be9dd3ed 100644 --- a/src/Mapster.JsonNet/Mapster.JsonNet.csproj +++ b/src/Mapster.JsonNet/Mapster.JsonNet.csproj @@ -7,7 +7,7 @@ Mapster;Json.net true Mapster.JsonNet.snk - 10.0.0-pre02 + 10.0.0 diff --git a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj index 16036201..b6da6d42 100644 --- a/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj +++ b/src/Mapster.SourceGenerator/Mapster.SourceGenerator.csproj @@ -7,7 +7,7 @@ true Mapster.SourceGenerator.snk https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources) - 10.0.0-pre02 + 10.0.0 enable false diff --git a/src/Mapster.Tool/Mapster.Tool.csproj b/src/Mapster.Tool/Mapster.Tool.csproj index 06b92931..3628258c 100644 --- a/src/Mapster.Tool/Mapster.Tool.csproj +++ b/src/Mapster.Tool/Mapster.Tool.csproj @@ -10,7 +10,7 @@ Mapster;Tool true Mapster.Tool.snk - 10.0.0-pre02 + 10.0.0 enable diff --git a/src/Mapster/Mapster.csproj b/src/Mapster/Mapster.csproj index 21b90ea7..86ee1e21 100644 --- a/src/Mapster/Mapster.csproj +++ b/src/Mapster/Mapster.csproj @@ -16,7 +16,7 @@ true Mapster - 10.0.0-pre02 + 10.0.0 enable 1701;1702;8618 From 8ac6d316412f8dbfcc303d1320f86c0858b2d533 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 7 Mar 2026 08:50:47 +0500 Subject: [PATCH 07/13] fix(docs): replace Readme links --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 208e2074..9f1c924c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,6 @@ Writing mapping methods is a machine job. Do not waste your time, let Mapster do |------|--------|-------------| | **Mapster.Tool** | [![Mapster.Tool](https://img.shields.io/nuget/v/Mapster.Tool.svg?label=Mapster.Tool&color=informational)](https://www.nuget.org/packages/Mapster.Tool/latest) | [![Mapster.Tool](https://img.shields.io/nuget/vpre/Mapster.Tool.svg?label=Mapster.Tool&color=orange)](https://www.nuget.org/packages/Mapster.Tool) | -_Badges zeigen die jeweils aktuellste Stable-Version und die aktuellste Pre-Release-Version._ ## Installation @@ -105,7 +104,7 @@ using (MyDbContext context = new MyDbContext()) ### Generating models & mappers -No need to write your own DTO classes. Mapster provides [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) to help you generating models. And if you would like to have explicit mapping, Mapster also generates mapper class for you. +No need to write your own DTO classes. Mapster provides [Mapster.Tool](https://mapstermapper.github.io/Mapster/articles/tools/mapster-tool/Mapster-Tool-Overview.html) to help you generating models. And if you would like to have explicit mapping, Mapster also generates mapper class for you. ```csharp [AdaptTo("[name]Dto"), GenerateMapper] @@ -129,14 +128,14 @@ public static class StudentMapper { ## What's new -- [Fluent API for code generation](https://github.com/MapsterMapper/Mapster/wiki/Fluent-API-Code-generation) -- [Automatically generate mapping code on build](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) -- [Define setting to nested mapping](https://github.com/MapsterMapper/Mapster/wiki/Config-for-nested-mapping) -- [`ISet`, `IDictionary`, `IReadOnlyDictionary` support](https://github.com/MapsterMapper/Mapster/wiki/Data-types#collections) -- [`EmptyCollectionIfNull`, `CreateNewIfNull` DestinationTransform](https://github.com/MapsterMapper/Mapster/wiki/Setting-values#transform-value) +- [Fluent API for code generation](https://mapstermapper.github.io/Mapster/articles/tools/mapster-tool/Fluent-API-Code-generation.html) +- [Automatically generate mapping code on build](https://mapstermapper.github.io/Mapster/articles/tools/mapster-tool/Mapster-Tool-Overview.html) +- [Define setting to nested mapping](https://mapstermapper.github.io/Mapster/articles/configuration/Config-for-nested-mapping.html) +- [`ISet`, `IDictionary`, `IReadOnlyDictionary` support](https://mapstermapper.github.io/Mapster/articles/mapping/Data-types.html#collections) +- [`EmptyCollectionIfNull`, `CreateNewIfNull` DestinationTransform](https://mapstermapper.github.io/Mapster/articles/settings/Setting-values.html#transform-value) - [Several fixes](https://github.com/MapsterMapper/Mapster/releases/) - New plugins - - [Immutable collection support](https://github.com/MapsterMapper/Mapster/wiki/Immutable) + - [Immutable collection support](https://mapstermapper.github.io/Mapster/articles/packages/Immutable.html) ## Why Mapster? @@ -145,8 +144,8 @@ public static class StudentMapper { Mapster was designed to be efficient on both speed and memory. You could gain a 4x performance improvement whilst using only 1/3 of memory. And you could gain up to 12x faster performance with: -- [Roslyn Compiler](https://github.com/MapsterMapper/Mapster/wiki/Debugging) -- [FEC](https://github.com/MapsterMapper/Mapster/wiki/FastExpressionCompiler) +- [Roslyn Compiler](https://mapstermapper.github.io/Mapster/articles/packages/ExpressionDebugging.html) +- [FEC](https://mapstermapper.github.io/Mapster/articles/packages/FastExpressionCompiler.html) - Code generation | Method | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | @@ -175,8 +174,8 @@ Code generation allows you to There are currently two tools which you can choose based on your preferences. -- [Mapster.Tool](https://github.com/MapsterMapper/Mapster/wiki/Mapster.Tool) _**NEW!**_ -- [TextTemplate](https://github.com/MapsterMapper/Mapster/wiki/TextTemplate) +- [Mapster.Tool](https://mapstermapper.github.io/Mapster/articles/tools/mapster-tool/Mapster-Tool-Overview.html) _**NEW!**_ +- [TextTemplate](https://mapstermapper.github.io/Mapster/articles/tools/TextTemplate.html) ## Change logs From 51a960fd32191fea3710ecad95d76bf481d72e12 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Sat, 7 Mar 2026 08:51:54 +0500 Subject: [PATCH 08/13] fix(docs): fix build script --- docs/Clean-and-Build-Docs.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Clean-and-Build-Docs.ps1 b/docs/Clean-and-Build-Docs.ps1 index 53db82ff..46118e8c 100644 --- a/docs/Clean-and-Build-Docs.ps1 +++ b/docs/Clean-and-Build-Docs.ps1 @@ -30,7 +30,7 @@ Write-Information "Cleaning obsolete API documentation..." -InformationAction Co # Step 3: Build documentation Write-Information "Building documentation..." -InformationAction Continue -& .\Build-Docs.ps1 -LogLevel $LogLevel -serve $true -open-browser $true +& .\Build-Docs.ps1 -LogLevel $LogLevel -serve $true -openbrowser $true if ($LASTEXITCODE -ne 0) { Write-Error "Documentation build failed" From 21dbdc641185eed46f6a577d9bcfefc1863f293f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Tue, 10 Mar 2026 10:45:06 +0500 Subject: [PATCH 09/13] fix(docs): fix records importatnt description --- docs/articles/mapping/Data-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/mapping/Data-types.md b/docs/articles/mapping/Data-types.md index edc0c163..42aeedf1 100644 --- a/docs/articles/mapping/Data-types.md +++ b/docs/articles/mapping/Data-types.md @@ -98,7 +98,7 @@ dict["Y"].ShouldBe(3); >[!IMPORTANT] > Mapster treats Record type as an immutable type. -> In this regard, only a with-like non-destructive mutation is available. +> Only a Nondestructive mutation - creating a new object with modified properties. > > ```csharp > var result = source.adapt(data) From 62eb60a4001db3e4120882f20274fc8698a17b8a Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 11 Mar 2026 09:34:56 +0500 Subject: [PATCH 10/13] fix(docs): Add links to mapping features supported by Record --- docs/articles/mapping/Data-types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/articles/mapping/Data-types.md b/docs/articles/mapping/Data-types.md index 42aeedf1..dcdb1182 100644 --- a/docs/articles/mapping/Data-types.md +++ b/docs/articles/mapping/Data-types.md @@ -187,6 +187,6 @@ var target = src.Adapt(); | Mapping features | v7.4.0 | v10.0 | |:-----------------|:------:|:-----:| -|Custom constructor mapping| - | ✅ | -|Ignore| - | ✅ | -|IgnoreNullValues| - | ✅ | \ No newline at end of file +|[Custom constructor mapping](xref:Mapster.Settings.ConstructorMapping)| - | ✅ | +|[Ignore](xref:Mapster.Settings.Custom.IgnoringMembers#ignore-extension-method)| - | ✅ | +|[IgnoreNullValues](xref:Mapster.Settings.Custom.IgnoringMembers#ignorenullvalues-extension-method)| - | ✅ | \ No newline at end of file From 705205bf483e9bb1e84b4098ea22abb3b4009184 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 18 Mar 2026 09:35:04 +0500 Subject: [PATCH 11/13] fix: replace TFM for Mapster.EF6 --- src/Mapster.EF6/Mapster.EF6.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mapster.EF6/Mapster.EF6.csproj b/src/Mapster.EF6/Mapster.EF6.csproj index 21ce481f..199e8e13 100644 --- a/src/Mapster.EF6/Mapster.EF6.csproj +++ b/src/Mapster.EF6/Mapster.EF6.csproj @@ -1,7 +1,7 @@  - net10.0;net9.0;net8.0; + net4.6.1;netstandard2.1 EF6 plugin for Mapster true Mapster;EF6 From a6558bb209527be60f1665e061d19b106d7b2cde Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 18 Mar 2026 10:30:59 +0500 Subject: [PATCH 12/13] fix: Drop Automapper --- src/Benchmark/Benchmark.csproj | 5 +- src/Benchmark/Benchmarks/TestAll.cs | 38 ++++++------- src/Benchmark/Benchmarks/TestComplexTypes.cs | 26 ++++----- src/Benchmark/Benchmarks/TestSimpleTypes.cs | 26 ++++----- src/Benchmark/Directory.Build.props | 18 +++++++ src/Benchmark/TestAdaptHelper.cs | 56 +++++++++++--------- 6 files changed, 95 insertions(+), 74 deletions(-) create mode 100644 src/Benchmark/Directory.Build.props diff --git a/src/Benchmark/Benchmark.csproj b/src/Benchmark/Benchmark.csproj index ba4b6b9f..b6872ab8 100644 --- a/src/Benchmark/Benchmark.csproj +++ b/src/Benchmark/Benchmark.csproj @@ -19,11 +19,10 @@ - + - - + diff --git a/src/Benchmark/Benchmarks/TestAll.cs b/src/Benchmark/Benchmarks/TestAll.cs index 40388f24..17581ddf 100644 --- a/src/Benchmark/Benchmarks/TestAll.cs +++ b/src/Benchmark/Benchmarks/TestAll.cs @@ -11,47 +11,47 @@ public class TestAll [Params(100_000)]//, 1_000_000)] public int Iterations { get; set; } - [Benchmark(Description = "Mapster 7.2.0")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion}")] public void MapsterTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - [Benchmark(Description = "Mapster 7.2.0 (Roslyn)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - [Benchmark(Description = "Mapster 7.2.0 (FEC)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - [Benchmark(Description = "Mapster 7.2.0 (Codegen)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Codegen)")] public void CodegenTest() { TestAdaptHelper.TestCodeGen(_fooInstance, Iterations); TestAdaptHelper.TestCodeGen(_customerInstance, Iterations); } - [Benchmark(Description = "ExpressMapper 1.9.1")] + [Benchmark(Description = $"ExpressMapper {TestAdaptHelper.ExpressionMapperVersion}")] public void ExpressMapperTest() { TestAdaptHelper.TestExpressMapper(_fooInstance, Iterations); TestAdaptHelper.TestExpressMapper(_customerInstance, Iterations); } - [Benchmark(Description = "AutoMapper 10.1.1")] - public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); - TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); - } + //[Benchmark(Description = $"AutoMapper {TestAdaptHelper.AutoMapperVersion}")] + //public void AutoMapperTest() + //{ + // TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); + // TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); + //} [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() @@ -98,14 +98,14 @@ public void SetupExpressMapper() TestAdaptHelper.ConfigureExpressMapper(_customerInstance); } - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - } + //[GlobalSetup(Target = nameof(AutoMapperTest))] + //public void SetupAutoMapper() + //{ + // _fooInstance = TestAdaptHelper.SetupFooInstance(); + // _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + // TestAdaptHelper.ConfigureAutoMapper(_fooInstance); + // TestAdaptHelper.ConfigureAutoMapper(_customerInstance); + //} } } \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestComplexTypes.cs b/src/Benchmark/Benchmarks/TestComplexTypes.cs index 971f2e73..a61ca9d3 100644 --- a/src/Benchmark/Benchmarks/TestComplexTypes.cs +++ b/src/Benchmark/Benchmarks/TestComplexTypes.cs @@ -16,13 +16,13 @@ public void MapsterTest() TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - [Benchmark(Description = "Mapster 6.0.0 (Roslyn)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); } - [Benchmark(Description = "Mapster 6.0.0 (FEC)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { TestAdaptHelper.TestMapsterAdapter(_customerInstance, Iterations); @@ -40,11 +40,11 @@ public void ExpressMapperTest() TestAdaptHelper.TestExpressMapper(_customerInstance, Iterations); } - [Benchmark] - public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); - } + //[Benchmark] + //public void AutoMapperTest() + //{ + // TestAdaptHelper.TestAutoMapper(_customerInstance, Iterations); + //} [GlobalSetup(Target = nameof(MapsterTest))] public void SetupMapster() @@ -81,11 +81,11 @@ public void SetupExpressMapper() TestAdaptHelper.ConfigureExpressMapper(_customerInstance); } - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _customerInstance = TestAdaptHelper.SetupCustomerInstance(); - TestAdaptHelper.ConfigureAutoMapper(_customerInstance); - } + //[GlobalSetup(Target = nameof(AutoMapperTest))] + //public void SetupAutoMapper() + //{ + // _customerInstance = TestAdaptHelper.SetupCustomerInstance(); + // TestAdaptHelper.ConfigureAutoMapper(_customerInstance); + //} } } \ No newline at end of file diff --git a/src/Benchmark/Benchmarks/TestSimpleTypes.cs b/src/Benchmark/Benchmarks/TestSimpleTypes.cs index 7c832a56..81f507e5 100644 --- a/src/Benchmark/Benchmarks/TestSimpleTypes.cs +++ b/src/Benchmark/Benchmarks/TestSimpleTypes.cs @@ -16,13 +16,13 @@ public void MapsterTest() TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); } - [Benchmark(Description = "Mapster 6.0.0 (Roslyn)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (Roslyn)")] public void RoslynTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); } - [Benchmark(Description = "Mapster 6.0.0 (FEC)")] + [Benchmark(Description = $"Mapster {TestAdaptHelper.MapsterVersion} (FEC)")] public void FecTest() { TestAdaptHelper.TestMapsterAdapter(_fooInstance, Iterations); @@ -40,11 +40,11 @@ public void ExpressMapperTest() TestAdaptHelper.TestExpressMapper(_fooInstance, Iterations); } - [Benchmark] - public void AutoMapperTest() - { - TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); - } + //[Benchmark] + //public void AutoMapperTest() + //{ + // TestAdaptHelper.TestAutoMapper(_fooInstance, Iterations); + //} [GlobalSetup(Target = nameof(MapsterTest))] @@ -82,11 +82,11 @@ public void SetupExpressMapper() TestAdaptHelper.ConfigureExpressMapper(_fooInstance); } - [GlobalSetup(Target = nameof(AutoMapperTest))] - public void SetupAutoMapper() - { - _fooInstance = TestAdaptHelper.SetupFooInstance(); - TestAdaptHelper.ConfigureAutoMapper(_fooInstance); - } + //[GlobalSetup(Target = nameof(AutoMapperTest))] + //public void SetupAutoMapper() + //{ + // _fooInstance = TestAdaptHelper.SetupFooInstance(); + // TestAdaptHelper.ConfigureAutoMapper(_fooInstance); + //} } } \ No newline at end of file diff --git a/src/Benchmark/Directory.Build.props b/src/Benchmark/Directory.Build.props new file mode 100644 index 00000000..8c415f63 --- /dev/null +++ b/src/Benchmark/Directory.Build.props @@ -0,0 +1,18 @@ + + + + false + + + + chaowlert;eric_swann;andrerav + Copyright (c) $([System.DateTime]::Now.ToString(`yyyy`)) Chaowlert Chaisrichalermpol, Eric Swann, Andreas Ravnestad + false + + MIT + false + true + false + 12 + + \ No newline at end of file diff --git a/src/Benchmark/TestAdaptHelper.cs b/src/Benchmark/TestAdaptHelper.cs index 3d7db0eb..cf8918e2 100644 --- a/src/Benchmark/TestAdaptHelper.cs +++ b/src/Benchmark/TestAdaptHelper.cs @@ -1,22 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using AutoMapper; -using Benchmark.Classes; +using Benchmark.Classes; using FastExpressionCompiler; using Mapster; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; namespace Benchmark { public static class TestAdaptHelper { - private static readonly IMapper _mapper = new Mapper(new MapperConfiguration(cfg => - { - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - cfg.CreateMap(); - })); + //private static readonly IMapper _mapper = new Mapper(new MapperConfiguration(cfg => + //{ + // cfg.CreateMap(); + // cfg.CreateMap(); + // cfg.CreateMap(); + // cfg.CreateMap(); + //})); + + public const string MapsterVersion = "10.0.0"; + public const string AutoMapperVersion = "13.0.0"; + public const string ExpressionTranslatorVersion = "2.5.0"; + public const string ExpressionMapperVersion = "1.9.1"; public static Customer SetupCustomerInstance() { @@ -90,10 +94,10 @@ public static void ConfigureExpressMapper(Foo fooInstance) { //ExpressMapper.Mapper.Map(fooInstance); //exercise } - public static void ConfigureAutoMapper(Foo fooInstance) - { - _mapper.Map(fooInstance); //exercise - } + //public static void ConfigureAutoMapper(Foo fooInstance) + //{ + // _mapper.Map(fooInstance); //exercise + //} public static void ConfigureMapster(Customer customerInstance, MapsterCompilerType type) { @@ -105,10 +109,10 @@ public static void ConfigureExpressMapper(Customer customerInstance) { //ExpressMapper.Mapper.Map(customerInstance); //exercise } - public static void ConfigureAutoMapper(Customer customerInstance) - { - _mapper.Map(customerInstance); //exercise - } + //public static void ConfigureAutoMapper(Customer customerInstance) + //{ + // _mapper.Map(customerInstance); //exercise + //} public static void TestMapsterAdapter(TSrc item, int iterations) where TSrc : class @@ -124,12 +128,12 @@ public static void TestExpressMapper(TSrc item, int iterations) //Loop(item, get => ExpressMapper.Mapper.Map(get), iterations); } - public static void TestAutoMapper(TSrc item, int iterations) - where TSrc : class - where TDest : class, new() - { - Loop(item, get => _mapper.Map(get), iterations); - } + //public static void TestAutoMapper(TSrc item, int iterations) + // where TSrc : class + // where TDest : class, new() + //{ + // Loop(item, get => _mapper.Map(get), iterations); + //} public static void TestCodeGen(Foo item, int iterations) { From c9e1b77b9dfba5476d4ba4d3dbb29416c08f1f1f Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Wed, 18 Mar 2026 10:42:43 +0500 Subject: [PATCH 13/13] fix: drop Directory.Biuld.props file --- src/Benchmark/Directory.Build.props | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/Benchmark/Directory.Build.props diff --git a/src/Benchmark/Directory.Build.props b/src/Benchmark/Directory.Build.props deleted file mode 100644 index 8c415f63..00000000 --- a/src/Benchmark/Directory.Build.props +++ /dev/null @@ -1,18 +0,0 @@ - - - - false - - - - chaowlert;eric_swann;andrerav - Copyright (c) $([System.DateTime]::Now.ToString(`yyyy`)) Chaowlert Chaisrichalermpol, Eric Swann, Andreas Ravnestad - false - - MIT - false - true - false - 12 - - \ No newline at end of file