diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc9b02ee10..2ce9576b96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: run: ./Push.ps1 shell: pwsh - name: Artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: artifacts path: artifacts/**/* \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index affbcbd18c..1820dee63b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: run: ./Push.ps1 shell: pwsh - name: Artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: artifacts path: artifacts/**/* \ No newline at end of file diff --git a/README.md b/README.md index a14c843b7c..7943dd8d40 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ If you're still running into problems, file an issue above. This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). -AutoMapper is Copyright © 2009 [Jimmy Bogard](https://jimmybogard.com) and other contributors under the [MIT license](LICENSE.txt). +AutoMapper is Copyright © 2009 [Jimmy Bogard](https://jimmybogard.com) and other contributors under the [MIT license](https://github.com/AutoMapper/AutoMapper?tab=MIT-1-ov-file#MIT-1-ov-file). ### .NET Foundation diff --git a/src/AutoMapper/ApiCompatBaseline.txt b/src/AutoMapper/ApiCompatBaseline.txt index 95b0733f02..da0d0a6bc2 100644 --- a/src/AutoMapper/ApiCompatBaseline.txt +++ b/src/AutoMapper/ApiCompatBaseline.txt @@ -39,8 +39,25 @@ CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMappe CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.UseExistingValueAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueConverterAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'AutoMapper.Configuration.Annotations.ValueResolverAttribute' changed from '[AttributeUsageAttribute(384)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)]' in the implementation. +CannotSealType : Type 'AutoMapper.Internal.Mappers.AssignableMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.CollectionMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ConstructorMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ConversionOperatorMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ConvertMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.EnumToEnumMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.FromDynamicMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.FromStringDictionaryMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.KeyValueMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. CannotSealType : Type 'AutoMapper.Internal.Mappers.MultidimensionalArrayFiller' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. TypeCannotChangeClassification : Type 'AutoMapper.Internal.Mappers.MultidimensionalArrayFiller' is a 'struct' in the implementation but is a 'class' in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.NullableDestinationMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.NullableSourceMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ParseStringMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.StringToEnumMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ToDynamicMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ToStringDictionaryMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.ToStringMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +CannotSealType : Type 'AutoMapper.Internal.Mappers.UnderlyingTypeEnumMapper' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableContextAttribute' exists on 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, System.Type, AutoMapper.IConfigurationProvider)' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableContextAttribute' exists on 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, System.Type, AutoMapper.IConfigurationProvider, System.Collections.Generic.IDictionary, System.String[])' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttribute' exists on parameter 'parameters' on member 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, System.Type, AutoMapper.IConfigurationProvider, System.Collections.Generic.IDictionary, System.String[])' in the contract but not the implementation. @@ -52,4 +69,4 @@ CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttri CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableContextAttribute' exists on 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression>[])' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttribute' exists on parameter 'parameters' on member 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression>[])' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.NullableAttribute' exists on generic param 'TDestination' on member 'AutoMapper.QueryableExtensions.Extensions.ProjectTo(System.Linq.IQueryable, AutoMapper.IConfigurationProvider, System.Object, System.Linq.Expressions.Expression>[])' in the contract but not the implementation. -Total Issues: 53 +Total Issues: 70 diff --git a/src/AutoMapper/AutoMapper.csproj b/src/AutoMapper/AutoMapper.csproj index 1526d2b212..0757bf73ed 100644 --- a/src/AutoMapper/AutoMapper.csproj +++ b/src/AutoMapper/AutoMapper.csproj @@ -3,7 +3,7 @@ A convention-based object-object mapper. A convention-based object-object mapper. - net6.0 + net8.0 true AutoMapper ..\..\AutoMapper.snk @@ -38,7 +38,7 @@ - + diff --git a/src/AutoMapper/ConstructorMap.cs b/src/AutoMapper/ConstructorMap.cs index c8e990b967..c3bffeb25f 100644 --- a/src/AutoMapper/ConstructorMap.cs +++ b/src/AutoMapper/ConstructorMap.cs @@ -67,9 +67,13 @@ public bool ApplyMap(TypeMap typeMap, IncludedMember includedMember = null) [EditorBrowsable(EditorBrowsableState.Never)] public class ConstructorParameterMap : MemberMap { - public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberInfo[] sourceMembers) : base(typeMap) + public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberInfo[] sourceMembers) : base(typeMap, parameter.ParameterType) { Parameter = parameter; + if(DestinationType.IsByRef) + { + DestinationType = DestinationType.GetElementType(); + } if (sourceMembers.Length > 0) { MapByConvention(sourceMembers); @@ -80,7 +84,6 @@ public ConstructorParameterMap(TypeMap typeMap, ParameterInfo parameter, MemberI } } public ParameterInfo Parameter { get; } - public override Type DestinationType => Parameter.ParameterType; public override IncludedMember IncludedMember { get; protected set; } public override MemberInfo[] SourceMembers { get; set; } public override string DestinationName => Parameter.Name; diff --git a/src/AutoMapper/Mappers/AssignableMapper.cs b/src/AutoMapper/Mappers/AssignableMapper.cs index 855a832cf0..6935a72070 100644 --- a/src/AutoMapper/Mappers/AssignableMapper.cs +++ b/src/AutoMapper/Mappers/AssignableMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.Internal.Mappers; -public class AssignableMapper : IObjectMapper +public sealed class AssignableMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.DestinationType.IsAssignableFrom(context.SourceType); public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, diff --git a/src/AutoMapper/Mappers/CollectionMapper.cs b/src/AutoMapper/Mappers/CollectionMapper.cs index 94be40d56d..757592de07 100644 --- a/src/AutoMapper/Mappers/CollectionMapper.cs +++ b/src/AutoMapper/Mappers/CollectionMapper.cs @@ -3,7 +3,7 @@ using System.Collections.Specialized; namespace AutoMapper.Internal.Mappers; using static ReflectionHelper; -public class CollectionMapper : IObjectMapper +public sealed class CollectionMapper : IObjectMapper { static readonly MethodInfo IListAdd = typeof(IList).GetMethod(nameof(IList.Add)); public TypePair? GetAssociatedTypes(TypePair context) => new(GetElementType(context.SourceType), GetElementType(context.DestinationType)); @@ -241,24 +241,23 @@ bool MustMap(Type sourceType, Type destinationType) => !destinationType.IsAssign } public readonly struct MultidimensionalArrayFiller(Array destination) { - private readonly int[] _indices = new int[destination.Rank]; - private readonly Array _destination = destination; + readonly int[] _indices = new int[destination.Rank]; public void NewValue(object value) { - var dimension = _destination.Rank - 1; + var dimension = destination.Rank - 1; var changedDimension = false; - while (_indices[dimension] == _destination.GetLength(dimension)) + while (_indices[dimension] == destination.GetLength(dimension)) { _indices[dimension] = 0; dimension--; if (dimension < 0) { - throw new InvalidOperationException("Not enough room in destination array " + _destination); + throw new InvalidOperationException("Not enough room in destination array " + destination); } _indices[dimension]++; changedDimension = true; } - _destination.SetValue(value, _indices); + destination.SetValue(value, _indices); if (changedDimension) { _indices[dimension + 1]++; diff --git a/src/AutoMapper/Mappers/ConstructorMapper.cs b/src/AutoMapper/Mappers/ConstructorMapper.cs index 23a5b70f83..3ed8b0a2e5 100644 --- a/src/AutoMapper/Mappers/ConstructorMapper.cs +++ b/src/AutoMapper/Mappers/ConstructorMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class ConstructorMapper : IObjectMapper +public sealed class ConstructorMapper : IObjectMapper { public bool IsMatch(TypePair context) => GetConstructor(context.SourceType, context.DestinationType) != null; private static ConstructorInfo GetConstructor(Type sourceType, Type destinationType) => diff --git a/src/AutoMapper/Mappers/ConversionOperatorMapper.cs b/src/AutoMapper/Mappers/ConversionOperatorMapper.cs index af727dd964..72f2cd386f 100644 --- a/src/AutoMapper/Mappers/ConversionOperatorMapper.cs +++ b/src/AutoMapper/Mappers/ConversionOperatorMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class ConversionOperatorMapper : IObjectMapper +public sealed class ConversionOperatorMapper : IObjectMapper { private readonly string _operatorName; public ConversionOperatorMapper(string operatorName) => _operatorName = operatorName; diff --git a/src/AutoMapper/Mappers/ConvertMapper.cs b/src/AutoMapper/Mappers/ConvertMapper.cs index dfa8a9a24e..53c952c966 100644 --- a/src/AutoMapper/Mappers/ConvertMapper.cs +++ b/src/AutoMapper/Mappers/ConvertMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class ConvertMapper : IObjectMapper +public sealed class ConvertMapper : IObjectMapper { public static bool IsPrimitive(Type type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal); public bool IsMatch(TypePair types) => (types.SourceType == typeof(string) && types.DestinationType == typeof(DateTime)) || diff --git a/src/AutoMapper/Mappers/EnumToEnumMapper.cs b/src/AutoMapper/Mappers/EnumToEnumMapper.cs index 9eba8acac9..e182b2b365 100644 --- a/src/AutoMapper/Mappers/EnumToEnumMapper.cs +++ b/src/AutoMapper/Mappers/EnumToEnumMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class EnumToEnumMapper : IObjectMapper +public sealed class EnumToEnumMapper : IObjectMapper { private static readonly MethodInfo TryParseMethod = typeof(Enum).StaticGenericMethod("TryParse", parametersCount: 3); public bool IsMatch(TypePair context) => context.IsEnumToEnum(); diff --git a/src/AutoMapper/Mappers/FromDynamicMapper.cs b/src/AutoMapper/Mappers/FromDynamicMapper.cs index fabb11cd5f..146e9411c6 100644 --- a/src/AutoMapper/Mappers/FromDynamicMapper.cs +++ b/src/AutoMapper/Mappers/FromDynamicMapper.cs @@ -2,7 +2,7 @@ using Microsoft.CSharp.RuntimeBinder; using Binder = Microsoft.CSharp.RuntimeBinder.Binder; namespace AutoMapper.Internal.Mappers; -public class FromDynamicMapper : IObjectMapper +public sealed class FromDynamicMapper : IObjectMapper { private static readonly MethodInfo MapMethodInfo = typeof(FromDynamicMapper).GetStaticMethod(nameof(Map)); private static object Map(object source, object destination, Type destinationType, ResolutionContext context, ProfileMap profileMap) diff --git a/src/AutoMapper/Mappers/FromStringDictionaryMapper.cs b/src/AutoMapper/Mappers/FromStringDictionaryMapper.cs index 333d652de0..1656098136 100644 --- a/src/AutoMapper/Mappers/FromStringDictionaryMapper.cs +++ b/src/AutoMapper/Mappers/FromStringDictionaryMapper.cs @@ -1,6 +1,6 @@ using StringDictionary = System.Collections.Generic.IDictionary; namespace AutoMapper.Internal.Mappers; -public class FromStringDictionaryMapper : IObjectMapper +public sealed class FromStringDictionaryMapper : IObjectMapper { private static readonly MethodInfo MapDynamicMethod = typeof(FromStringDictionaryMapper).GetStaticMethod(nameof(MapDynamic)); public bool IsMatch(TypePair context) => typeof(StringDictionary).IsAssignableFrom(context.SourceType); diff --git a/src/AutoMapper/Mappers/KeyValueMapper.cs b/src/AutoMapper/Mappers/KeyValueMapper.cs index 2ab2eb447c..9643e07181 100644 --- a/src/AutoMapper/Mappers/KeyValueMapper.cs +++ b/src/AutoMapper/Mappers/KeyValueMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class KeyValueMapper : IObjectMapper +public sealed class KeyValueMapper : IObjectMapper { public bool IsMatch(TypePair context) => IsKeyValue(context.SourceType) && IsKeyValue(context.DestinationType); public static bool IsKeyValue(Type type) => type.IsGenericType(typeof(KeyValuePair<,>)); diff --git a/src/AutoMapper/Mappers/NullableDestinationMapper.cs b/src/AutoMapper/Mappers/NullableDestinationMapper.cs index 04ffbf2cda..3defec9d80 100644 --- a/src/AutoMapper/Mappers/NullableDestinationMapper.cs +++ b/src/AutoMapper/Mappers/NullableDestinationMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.Internal.Mappers; -public class NullableDestinationMapper : IObjectMapper +public sealed class NullableDestinationMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.DestinationType.IsNullableType(); public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) => diff --git a/src/AutoMapper/Mappers/NullableSourceMapper.cs b/src/AutoMapper/Mappers/NullableSourceMapper.cs index 7f574b7621..969da15201 100644 --- a/src/AutoMapper/Mappers/NullableSourceMapper.cs +++ b/src/AutoMapper/Mappers/NullableSourceMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.Internal.Mappers; -public class NullableSourceMapper : IObjectMapper +public sealed class NullableSourceMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.SourceType.IsNullableType(); public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) => diff --git a/src/AutoMapper/Mappers/ParseStringMapper.cs b/src/AutoMapper/Mappers/ParseStringMapper.cs index 7026d4d5d7..76939c336b 100644 --- a/src/AutoMapper/Mappers/ParseStringMapper.cs +++ b/src/AutoMapper/Mappers/ParseStringMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.Internal.Mappers; -public class ParseStringMapper : IObjectMapper +public sealed class ParseStringMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.SourceType == typeof(string) && HasParse(context.DestinationType); static bool HasParse(Type type) => type == typeof(Guid) || type == typeof(TimeSpan) || type == typeof(DateTimeOffset); diff --git a/src/AutoMapper/Mappers/StringToEnumMapper.cs b/src/AutoMapper/Mappers/StringToEnumMapper.cs index 9044dd78be..771ffe5748 100644 --- a/src/AutoMapper/Mappers/StringToEnumMapper.cs +++ b/src/AutoMapper/Mappers/StringToEnumMapper.cs @@ -1,6 +1,6 @@ using System.Runtime.Serialization; namespace AutoMapper.Internal.Mappers; -public class StringToEnumMapper : IObjectMapper +public sealed class StringToEnumMapper : IObjectMapper { private static readonly MethodInfo EqualsMethod = typeof(StringToEnumMapper).GetMethod(nameof(StringCompareOrdinalIgnoreCase)); private static readonly MethodInfo ParseMethod = typeof(Enum).StaticGenericMethod("Parse", parametersCount: 2); diff --git a/src/AutoMapper/Mappers/ToDynamicMapper.cs b/src/AutoMapper/Mappers/ToDynamicMapper.cs index 4bf8c6c457..85b029c620 100644 --- a/src/AutoMapper/Mappers/ToDynamicMapper.cs +++ b/src/AutoMapper/Mappers/ToDynamicMapper.cs @@ -2,7 +2,7 @@ using Microsoft.CSharp.RuntimeBinder; using Binder = Microsoft.CSharp.RuntimeBinder.Binder; namespace AutoMapper.Internal.Mappers; -public class ToDynamicMapper : IObjectMapper +public sealed class ToDynamicMapper : IObjectMapper { private static readonly MethodInfo MapMethodInfo = typeof(ToDynamicMapper).GetStaticMethod(nameof(Map)); private static object Map(object source, object destination, Type destinationType, ResolutionContext context, ProfileMap profileMap) diff --git a/src/AutoMapper/Mappers/ToStringDictionaryMapper.cs b/src/AutoMapper/Mappers/ToStringDictionaryMapper.cs index 21d4400f3f..5a8d495c00 100644 --- a/src/AutoMapper/Mappers/ToStringDictionaryMapper.cs +++ b/src/AutoMapper/Mappers/ToStringDictionaryMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class ToStringDictionaryMapper : IObjectMapper +public sealed class ToStringDictionaryMapper : IObjectMapper { private static readonly MethodInfo MembersDictionaryMethodInfo = typeof(ToStringDictionaryMapper).GetStaticMethod(nameof(MembersDictionary)); public bool IsMatch(TypePair context) => typeof(IDictionary).IsAssignableFrom(context.DestinationType); diff --git a/src/AutoMapper/Mappers/ToStringMapper.cs b/src/AutoMapper/Mappers/ToStringMapper.cs index 243b05db0f..81b8e836c4 100644 --- a/src/AutoMapper/Mappers/ToStringMapper.cs +++ b/src/AutoMapper/Mappers/ToStringMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.Internal.Mappers; -public class ToStringMapper : IObjectMapper +public sealed class ToStringMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.DestinationType == typeof(string); public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, MemberMap memberMap, Expression sourceExpression, Expression destExpression) diff --git a/src/AutoMapper/Mappers/UnderlyingEnumTypeMapper.cs b/src/AutoMapper/Mappers/UnderlyingEnumTypeMapper.cs index 388abc8571..7592bc4337 100644 --- a/src/AutoMapper/Mappers/UnderlyingEnumTypeMapper.cs +++ b/src/AutoMapper/Mappers/UnderlyingEnumTypeMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.Internal.Mappers; -public class UnderlyingTypeEnumMapper : IObjectMapper +public sealed class UnderlyingTypeEnumMapper : IObjectMapper { public bool IsMatch(TypePair context) => context.IsEnumToUnderlyingType() || context.IsUnderlyingTypeToEnum(); public Expression MapExpression(IGlobalConfiguration configuration, ProfileMap profileMap, diff --git a/src/AutoMapper/MemberMap.cs b/src/AutoMapper/MemberMap.cs index 6a281f8c8a..cde161cef8 100644 --- a/src/AutoMapper/MemberMap.cs +++ b/src/AutoMapper/MemberMap.cs @@ -6,8 +6,8 @@ namespace AutoMapper; public class MemberMap : IValueResolver { private protected Type _sourceType; - protected MemberMap(TypeMap typeMap) => TypeMap = typeMap; - internal static readonly MemberMap Instance = new(null); + protected MemberMap(TypeMap typeMap, Type destinationType) => (TypeMap, DestinationType) = (typeMap, destinationType); + internal static readonly MemberMap Instance = new(null, null); public TypeMap TypeMap { get; protected set; } public LambdaExpression CustomMapExpression => Resolver?.ProjectToExpression; public bool IsResolveConfigured => Resolver != null && Resolver != this; @@ -22,7 +22,7 @@ public void SetResolver(IValueResolver resolver) public virtual MemberInfo[] SourceMembers { get => []; set { } } public virtual IncludedMember IncludedMember { get => default; protected set { } } public virtual string DestinationName => default; - public virtual Type DestinationType { get => default; protected set { } } + public Type DestinationType { get; protected set; } public virtual TypePair Types() => new(SourceType, DestinationType); public bool CanResolveValue => !Ignored && Resolver != null; public bool IsMapped => Ignored || Resolver != null; diff --git a/src/AutoMapper/PathMap.cs b/src/AutoMapper/PathMap.cs index 1f5f74afe6..83dce8cbed 100644 --- a/src/AutoMapper/PathMap.cs +++ b/src/AutoMapper/PathMap.cs @@ -1,7 +1,8 @@ namespace AutoMapper; [DebuggerDisplay("{DestinationExpression}")] [EditorBrowsable(EditorBrowsableState.Never)] -public sealed class PathMap(LambdaExpression destinationExpression, MemberPath memberPath, TypeMap typeMap) : MemberMap(typeMap) +public sealed class PathMap(LambdaExpression destinationExpression, MemberPath memberPath, TypeMap typeMap) + : MemberMap(typeMap, memberPath.Last.GetMemberType()) { public PathMap(PathMap pathMap, TypeMap typeMap, IncludedMember includedMember) : this(pathMap.DestinationExpression, pathMap.MemberPath, typeMap) { @@ -13,7 +14,6 @@ public PathMap(PathMap pathMap, TypeMap typeMap, IncludedMember includedMember) public override Type SourceType => Resolver.ResolvedType; public LambdaExpression DestinationExpression { get; } = destinationExpression; public MemberPath MemberPath { get; } = memberPath; - public override Type DestinationType => MemberPath.Last.GetMemberType(); public override string DestinationName => MemberPath.ToString(); public override bool CanBeSet => ReflectionHelper.CanBeSet(MemberPath.Last); public override bool Ignored { get; set; } diff --git a/src/AutoMapper/PropertyMap.cs b/src/AutoMapper/PropertyMap.cs index 617bd98f04..543584f79f 100644 --- a/src/AutoMapper/PropertyMap.cs +++ b/src/AutoMapper/PropertyMap.cs @@ -5,19 +5,15 @@ namespace AutoMapper; public sealed class PropertyMap : MemberMap { private MemberMapDetails _details; - public PropertyMap(MemberInfo destinationMember, Type destinationMemberType, TypeMap typeMap) : base(typeMap) - { + public PropertyMap(MemberInfo destinationMember, Type destinationMemberType, TypeMap typeMap) : base(typeMap, destinationMemberType) => DestinationMember = destinationMember; - DestinationType = destinationMemberType; - } - public PropertyMap(PropertyMap inheritedMappedProperty, TypeMap typeMap) : base(typeMap) + public PropertyMap(PropertyMap inheritedMappedProperty, TypeMap typeMap) : base(typeMap, inheritedMappedProperty.DestinationType) { DestinationMember = inheritedMappedProperty.DestinationMember; if (DestinationMember.DeclaringType.ContainsGenericParameters) { DestinationMember = typeMap.DestinationSetters.Single(m => m.Name == DestinationMember.Name); } - DestinationType = inheritedMappedProperty.DestinationType; if (DestinationType.ContainsGenericParameters) { DestinationType = DestinationMember.GetMemberType(); @@ -29,7 +25,6 @@ public PropertyMap(PropertyMap includedMemberMap, TypeMap typeMap, IncludedMembe private MemberMapDetails Details => _details ??= new(); public MemberInfo DestinationMember { get; } public override string DestinationName => DestinationMember?.Name; - public override Type DestinationType { get; protected set; } public override MemberInfo[] SourceMembers { get; set; } = []; public override bool CanBeSet => DestinationMember.CanBeSet(); public override bool Ignored { get; set; } diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs index f92c42f1a7..a996e2f1da 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/AssignableProjectionMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.QueryableExtensions.Impl; [EditorBrowsable(EditorBrowsableState.Never)] -public class AssignableProjectionMapper : IProjectionMapper +public sealed class AssignableProjectionMapper : IProjectionMapper { public bool IsMatch(TypePair context) => context.DestinationType.IsAssignableFrom(context.SourceType); public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs index f2524549d7..37c327dc40 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumProjectionMapper.cs @@ -1,6 +1,6 @@ namespace AutoMapper.QueryableExtensions.Impl; [EditorBrowsable(EditorBrowsableState.Never)] -public class EnumProjectionMapper : IProjectionMapper +public sealed class EnumProjectionMapper : IProjectionMapper { public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => Convert(resolvedSource, request.DestinationType); diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs index a2cbbe2a6a..dc4ec7aa6e 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/EnumerableProjectionMapper.cs @@ -1,7 +1,7 @@ namespace AutoMapper.QueryableExtensions.Impl; using static ReflectionHelper; [EditorBrowsable(EditorBrowsableState.Never)] -public class EnumerableProjectionMapper : IProjectionMapper +public sealed class EnumerableProjectionMapper : IProjectionMapper { private static readonly MethodInfo SelectMethod = typeof(Enumerable).StaticGenericMethod("Select", parametersCount: 2); private static readonly MethodInfo ToArrayMethod = typeof(Enumerable).GetStaticMethod("ToArray"); diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs index f3b7f77e66..dd0c137a0b 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/NullableSourceProjectionMapper.cs @@ -1,5 +1,5 @@ namespace AutoMapper.QueryableExtensions.Impl; -internal class NullableSourceProjectionMapper : IProjectionMapper +public sealed class NullableSourceProjectionMapper : IProjectionMapper { public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) => Coalesce(resolvedSource, New(request.DestinationType)); diff --git a/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs b/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs index a635ce7c2d..06f4291b6c 100644 --- a/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs +++ b/src/AutoMapper/QueryableExtensions/ProjectionMappers/StringProjectionMapper.cs @@ -1,7 +1,7 @@ namespace AutoMapper.QueryableExtensions.Impl; [EditorBrowsable(EditorBrowsableState.Never)] -public class StringProjectionMapper : IProjectionMapper +public sealed class StringProjectionMapper : IProjectionMapper { public bool IsMatch(TypePair context) => context.DestinationType == typeof(string); public Expression Project(IGlobalConfiguration configuration, in ProjectionRequest request, Expression resolvedSource, LetPropertyMaps letPropertyMaps) diff --git a/src/UnitTests/Bug/ByrefConstructorParameter.cs b/src/UnitTests/Bug/ByrefConstructorParameter.cs new file mode 100644 index 0000000000..d0feda88c0 --- /dev/null +++ b/src/UnitTests/Bug/ByrefConstructorParameter.cs @@ -0,0 +1,41 @@ +namespace AutoMapper.UnitTests.Bug; + +public class ByrefConstructorParameter : AutoMapperSpecBase +{ + private Destination _destination; + + class Source + { + public TimeSpan X { get; set; } + } + + class Destination + { + public Destination(in TimeSpan x) + { + Y = x; + } + + public TimeSpan Y { get; } + } + + protected override MapperConfiguration CreateConfiguration() => new(cfg => + { + cfg.CreateMap(); + }); + + protected override void Because_of() + { + var source = new Source + { + X = TimeSpan.FromSeconds(17) + }; + _destination = Mapper.Map(source); + } + + [Fact] + public void should_just_work() + { + _destination.Y.ShouldBe(TimeSpan.FromSeconds(17)); + } +} \ No newline at end of file