@@ -25,39 +25,112 @@ public static TDestination Map<TSource, TSourceItem, TDestination, TDestinationI
2525 return destination ;
2626 }
2727
28- var destList = destination . ToLookup ( x => equivalentComparer . GetHashCode ( x ) ) . ToDictionary ( x => x . Key , x => x . ToList ( ) ) ;
28+ // Build a lookup of existing destination items by the equivalency hash
29+ var destLookup = destination . ToLookup ( x => equivalentComparer . GetHashCode ( x ) ) . ToDictionary ( x => x . Key , x => x . ToList ( ) ) ;
2930
30- var items = source . Select ( x =>
31+ // We'll collect the items in the exact order of the source, preserving existing instances
32+ var ordered = new List < TDestinationItem > ( ) ;
33+ var toAdd = new List < TDestinationItem > ( ) ;
34+
35+ foreach ( var src in source )
3136 {
32- var sourceHash = equivalentComparer . GetHashCode ( x ) ;
37+ var sourceHash = equivalentComparer . GetHashCode ( src ) ;
3338
34- var item = default ( TDestinationItem ) ;
35- if ( destList . TryGetValue ( sourceHash , out var itemList ) )
39+ TDestinationItem match = default ;
40+ if ( destLookup . TryGetValue ( sourceHash , out var candidates ) )
3641 {
37- item = itemList . FirstOrDefault ( dest => equivalentComparer . IsEquivalent ( x , dest ) ) ;
38- if ( item != null )
42+ match = candidates . FirstOrDefault ( dest => equivalentComparer . IsEquivalent ( src , dest ) ) ;
43+ if ( match != null )
3944 {
40- itemList . Remove ( item ) ;
45+ // Reserve this destination instance and update it
46+ candidates . Remove ( match ) ;
47+ context . Mapper . Map ( src , match ) ;
48+ ordered . Add ( match ) ;
49+ continue ;
4150 }
4251 }
43- return new { SourceItem = x , DestinationItem = item } ;
44- } ) ;
4552
46- foreach ( var keypair in items )
53+ // No match found: create a new destination item
54+ var newItem = ( TDestinationItem ) context . Mapper . Map ( src , null , typeof ( TSourceItem ) , typeof ( TDestinationItem ) ) ;
55+ toAdd . Add ( newItem ) ;
56+ ordered . Add ( newItem ) ;
57+ }
58+
59+ // Remove any remaining destination items that were not matched
60+ foreach ( var removedItem in destLookup . SelectMany ( x => x . Value ) )
4761 {
48- if ( keypair . DestinationItem == null )
62+ destination . Remove ( removedItem ) ;
63+ }
64+
65+ // Ensure all new items are part of the destination collection before reordering
66+ foreach ( var add in toAdd )
67+ {
68+ if ( ! destination . Contains ( add ) )
4969 {
50- destination . Add ( ( TDestinationItem ) context . Mapper . Map ( keypair . SourceItem , null , typeof ( TSourceItem ) , typeof ( TDestinationItem ) ) ) ;
70+ destination . Add ( add ) ;
5171 }
52- else
72+ }
73+
74+ // Reorder destination to match the 'ordered' sequence while preserving the collection instance
75+ if ( destination is IList < TDestinationItem > list )
76+ {
77+ var oc = list as System . Collections . ObjectModel . ObservableCollection < TDestinationItem > ;
78+ for ( int i = 0 ; i < ordered . Count ; i ++ )
5379 {
54- context . Mapper . Map ( keypair . SourceItem , keypair . DestinationItem ) ;
80+ var target = ordered [ i ] ;
81+ if ( i < list . Count && ReferenceEquals ( list [ i ] , target ) )
82+ {
83+ continue ;
84+ }
85+
86+ // Find the current index of the target item, if it exists
87+ int currentIndex = - 1 ;
88+ for ( int j = i + 1 ; j < list . Count ; j ++ )
89+ {
90+ if ( ReferenceEquals ( list [ j ] , target ) )
91+ {
92+ currentIndex = j ;
93+ break ;
94+ }
95+ }
96+
97+ if ( currentIndex >= 0 )
98+ {
99+ if ( oc != null )
100+ {
101+ // Use Move to raise a single Move event
102+ oc . Move ( currentIndex , i ) ;
103+ }
104+ else
105+ {
106+ // Move existing item to the desired index
107+ var item = list [ currentIndex ] ;
108+ list . RemoveAt ( currentIndex ) ;
109+ list . Insert ( i , item ) ;
110+ }
111+ }
112+ else
113+ {
114+ // Insert the new item at the correct position
115+ list . Insert ( i , target ) ;
116+ }
55117 }
56- }
57118
58- foreach ( var removedItem in destList . SelectMany ( x => x . Value ) )
119+ }
120+ else
59121 {
60- destination . Remove ( removedItem ) ;
122+ // Fallback for non-IList collections: remove and re-add in order
123+ // Note: This may lose ordering guarantees for certain collection types that don't preserve insertion order
124+ // but provides best-effort behavior.
125+ var existing = destination . ToList ( ) ;
126+ foreach ( var item in existing )
127+ {
128+ destination . Remove ( item ) ;
129+ }
130+ foreach ( var item in ordered )
131+ {
132+ destination . Add ( item ) ;
133+ }
61134 }
62135
63136 return destination ;
0 commit comments