1+ using System . Reflection ;
2+ using System . Text ;
3+
4+ [ AttributeUsage ( AttributeTargets . Class , AllowMultiple = true ) ]
5+ public class MapperAttribute : Attribute
6+ {
7+ public Type SourceType { get ; }
8+ public Type DestType { get ; }
9+ public bool IsBidirectional { get ; }
10+
11+ public MapperAttribute ( Type sourceType , Type destType , bool isBidirectional = false )
12+ {
13+ SourceType = sourceType ;
14+ DestType = destType ;
15+ IsBidirectional = isBidirectional ;
16+ }
17+ }
18+
19+ [ AttributeUsage ( AttributeTargets . Class , AllowMultiple = true ) ]
20+ public class ManualMapperAttribute : Attribute
21+ {
22+ public Type SourceType { get ; }
23+ public Type DestType { get ; }
24+ public bool IsBidirectional { get ; }
25+
26+ public ManualMapperAttribute ( Type sourceType , Type destType , bool isBidirectional = false )
27+ {
28+ SourceType = sourceType ;
29+ DestType = destType ;
30+ IsBidirectional = isBidirectional ;
31+ }
32+ }
33+ public class ManualMapperGeneratorAuto
34+ {
35+ private readonly string ? _outputPath ;
36+ private readonly HashSet < string > _generatedMappers = new ( ) ;
37+ private readonly HashSet < ( Type Source , Type Dest ) > _processingTypes = new ( ) ;
38+ private readonly Dictionary < string , string > _generatedCode = new ( ) ;
39+ private readonly bool _isSourceGenerator ;
40+
41+ public ManualMapperGeneratorAuto ( string outputPath )
42+ {
43+ _outputPath = outputPath ;
44+ _isSourceGenerator = false ;
45+ }
46+
47+ public ManualMapperGeneratorAuto ( )
48+ {
49+ _isSourceGenerator = true ;
50+ }
51+
52+ public void GenerateMappers ( Dictionary < ( Type Source , Type Dest ) , bool > mappings )
53+ {
54+ foreach ( var mapping in mappings )
55+ {
56+ GenerateMapper ( mapping . Key . Source , mapping . Key . Dest ) ;
57+ }
58+
59+ GenerateMapperRegistration ( mappings ) ;
60+ }
61+
62+ public void GenerateMappers ( params Assembly [ ] assemblies )
63+ {
64+ var mappings = new Dictionary < ( Type Source , Type Dest ) , bool > ( ) ;
65+
66+ foreach ( var assembly in assemblies )
67+ {
68+ var types = assembly . GetTypes ( )
69+ . Where ( t => t . GetCustomAttributes < ManualMapperAttribute > ( ) . Any ( ) ) ;
70+
71+ foreach ( var type in types )
72+ {
73+ foreach ( var attr in type . GetCustomAttributes < ManualMapperAttribute > ( ) )
74+ {
75+ if ( ! mappings . ContainsKey ( ( attr . SourceType , attr . DestType ) ) )
76+ {
77+ mappings [ ( attr . SourceType , attr . DestType ) ] = attr . IsBidirectional ;
78+ }
79+
80+ if ( attr . IsBidirectional && ! mappings . ContainsKey ( ( attr . DestType , attr . SourceType ) ) )
81+ {
82+ mappings [ ( attr . DestType , attr . SourceType ) ] = true ;
83+ }
84+ }
85+ }
86+ }
87+
88+ GenerateMappers ( mappings ) ;
89+ }
90+
91+ public Dictionary < string , string > GetGeneratedCode ( )
92+ {
93+ return _generatedCode ;
94+ }
95+
96+ private void GenerateMapper ( Type sourceType , Type destType )
97+ {
98+ var mapperKey = $ "{ sourceType . Name } To{ destType . Name } ";
99+ if ( _generatedMappers . Contains ( mapperKey ) )
100+ {
101+ return ;
102+ }
103+
104+ if ( _processingTypes . Contains ( ( sourceType , destType ) ) )
105+ {
106+ return ;
107+ }
108+ _processingTypes . Add ( ( sourceType , destType ) ) ;
109+
110+ var mappings = new StringBuilder ( ) ;
111+
112+ // 处理属性映射
113+ var sourceProps = sourceType . GetProperties ( BindingFlags . Public | BindingFlags . Instance )
114+ . Where ( p => p . CanRead )
115+ . ToDictionary ( p => p . Name ) ;
116+
117+ var destProps = destType . GetProperties ( BindingFlags . Public | BindingFlags . Instance )
118+ . Where ( p => p . CanWrite ) ;
119+
120+ foreach ( var destProp in destProps )
121+ {
122+ if ( sourceProps . TryGetValue ( destProp . Name , out var sourceProp ) )
123+ {
124+ var mapping = GenerateMapping ( sourceProp . PropertyType , destProp . PropertyType ,
125+ $ "source.{ sourceProp . Name } ", $ "dest.{ destProp . Name } ") ;
126+ if ( ! string . IsNullOrEmpty ( mapping ) )
127+ {
128+ mappings . AppendLine ( mapping ) ;
129+ }
130+ }
131+ }
132+
133+ // 处理字段映射
134+ var sourceFields = sourceType . GetFields ( BindingFlags . Public | BindingFlags . Instance )
135+ . ToDictionary ( f => f . Name ) ;
136+
137+ var destFields = destType . GetFields ( BindingFlags . Public | BindingFlags . Instance ) ;
138+
139+ foreach ( var destField in destFields )
140+ {
141+ if ( sourceFields . TryGetValue ( destField . Name , out var sourceField ) )
142+ {
143+ var mapping = GenerateMapping ( sourceField . FieldType , destField . FieldType ,
144+ $ "source.{ sourceField . Name } ", $ "dest.{ destField . Name } ") ;
145+ if ( ! string . IsNullOrEmpty ( mapping ) )
146+ {
147+ mappings . AppendLine ( mapping ) ;
148+ }
149+ }
150+ }
151+
152+ var code = $@ "
153+ namespace FastMapper
154+ {{
155+ public static class { mapperKey } Mapper
156+ {{
157+ public static { destType . FullName } Map({ sourceType . FullName } source)
158+ {{
159+ if (source == null) return null;
160+ var dest = new { destType . FullName } ();
161+ { mappings }
162+ return dest;
163+ }}
164+ }}
165+ }}" ;
166+
167+ if ( _isSourceGenerator )
168+ {
169+ _generatedCode [ mapperKey ] = code ;
170+ }
171+ else
172+ {
173+ var filePath = Path . Combine ( _outputPath ! , $ "{ mapperKey } Mapper.g.cs") ;
174+ File . WriteAllText ( filePath , code ) ;
175+ Console . WriteLine ( $ "Generated mapper: { filePath } ") ;
176+ }
177+
178+ _generatedMappers . Add ( mapperKey ) ;
179+ _processingTypes . Remove ( ( sourceType , destType ) ) ;
180+ }
181+
182+ private string GenerateMapping ( Type sourceType , Type destType , string sourcePath , string destPath )
183+ {
184+ // 处理基本类型或相同类型的直接映射
185+ if ( ( /*sourceType == destType ||*/ IsBasicType ( sourceType ) ) && ! IsListType ( sourceType ) )
186+ {
187+ return $ " { destPath } = { sourcePath } ;";
188+ }
189+
190+ // 处理List类型
191+ if ( IsListType ( sourceType ) && IsListType ( destType ) )
192+ {
193+ var sourceElementType = sourceType . GetGenericArguments ( ) [ 0 ] ;
194+ var destElementType = destType . GetGenericArguments ( ) [ 0 ] ;
195+
196+ // 如果列表元素是基本类型,总是创建新的列表
197+ if ( sourceElementType == destElementType && IsBasicType ( sourceElementType ) )
198+ {
199+ return $@ " { destPath } = { sourcePath } != null ?
200+ new List<{ destElementType . FullName } >({ sourcePath } ) : null;" ;
201+ }
202+
203+ // 为复杂元素类型生成mapper
204+ GenerateMapper ( sourceElementType , destElementType ) ;
205+
206+ return $@ " { destPath } = { sourcePath } != null ?
207+ { sourcePath } .Select(item =>
208+ { sourceElementType . Name } To{ destElementType . Name } Mapper.Map(item))
209+ .ToList() : null;" ;
210+ }
211+
212+ // 处理Dictionary类型
213+ if ( IsDictionaryType ( sourceType ) && IsDictionaryType ( destType ) )
214+ {
215+ var sourceTypes = sourceType . GetGenericArguments ( ) ;
216+ var destTypes = destType . GetGenericArguments ( ) ;
217+
218+ if ( sourceTypes [ 0 ] == destTypes [ 0 ] )
219+ {
220+ GenerateMapper ( sourceTypes [ 1 ] , destTypes [ 1 ] ) ;
221+
222+ return $@ " { destPath } = { sourcePath } != null ?
223+ { sourcePath } .ToDictionary(
224+ kvp => kvp.Key,
225+ kvp => { sourceTypes [ 1 ] . Name } To{ destTypes [ 1 ] . Name } Mapper.Map(kvp.Value)) : null;" ;
226+ }
227+ }
228+
229+ // 处理复杂对象
230+ if ( ! IsBasicType ( sourceType ) && ! IsBasicType ( destType ) )
231+ {
232+ GenerateMapper ( sourceType , destType ) ;
233+ return $ " { destPath } = { sourceType . Name } To{ destType . Name } Mapper.Map({ sourcePath } );";
234+ }
235+
236+ return string . Empty ;
237+ }
238+
239+ private bool IsBasicType ( Type type )
240+ {
241+ return type . IsPrimitive || type == typeof ( string ) || type == typeof ( decimal ) ||
242+ type == typeof ( DateTime ) || type == typeof ( Guid ) ||
243+ Nullable . GetUnderlyingType ( type ) != null ;
244+ }
245+
246+ private bool IsListType ( Type type )
247+ {
248+ return type . IsGenericType &&
249+ ( type . GetGenericTypeDefinition ( ) == typeof ( List < > ) ||
250+ type . GetGenericTypeDefinition ( ) == typeof ( IList < > ) ||
251+ type . GetGenericTypeDefinition ( ) == typeof ( ICollection < > ) ||
252+ type . GetGenericTypeDefinition ( ) == typeof ( IEnumerable < > ) ) ;
253+ }
254+
255+ private bool IsDictionaryType ( Type type )
256+ {
257+ return type . IsGenericType &&
258+ ( type . GetGenericTypeDefinition ( ) == typeof ( Dictionary < , > ) ||
259+ type . GetGenericTypeDefinition ( ) == typeof ( IDictionary < , > ) ) ;
260+ }
261+
262+ private void GenerateMapperRegistration ( Dictionary < ( Type Source , Type Dest ) , bool > mappings )
263+ {
264+ var registrations = new StringBuilder ( ) ;
265+ var processedPairs = new HashSet < string > ( ) ;
266+
267+ foreach ( var mapping in mappings )
268+ {
269+ var sourceType = mapping . Key . Source ;
270+ var destType = mapping . Key . Dest ;
271+ var mapperKey = $ "{ sourceType . Name } To{ destType . Name } ";
272+
273+ if ( ! processedPairs . Contains ( mapperKey ) )
274+ {
275+ registrations . AppendLine ( $@ "
276+ _mappers[(typeof({ sourceType . FullName } ), typeof({ destType . FullName } ))] =
277+ ({ sourceType . Name } To{ destType . Name } Mapper.Map);" ) ;
278+
279+ processedPairs . Add ( mapperKey ) ;
280+ }
281+ }
282+
283+ var code = $@ "
284+ namespace FastMapper
285+ {{
286+ public static partial class Mapper
287+ {{
288+ static partial void RegisterGeneratedMappers()
289+ {{
290+ { registrations }
291+ }}
292+ }}
293+ }}" ;
294+
295+ if ( _isSourceGenerator )
296+ {
297+ _generatedCode [ "MapperRegistration" ] = code ;
298+ }
299+ else
300+ {
301+ var filePath = Path . Combine ( _outputPath ! , "MapperRegistration.g.cs" ) ;
302+ File . WriteAllText ( filePath , code ) ;
303+ Console . WriteLine ( $ "Generated registration file: { filePath } ") ;
304+ }
305+ }
306+ }
0 commit comments