Skip to content

Commit cbee7cd

Browse files
bambomAlianBlank
authored andcommitted
【插件】 实现一个FastMapper
1 parent be08db1 commit cbee7cd

File tree

11 files changed

+822
-0
lines changed

11 files changed

+822
-0
lines changed

Server.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2222
EndProject
2323
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameFrameX.CodeGenerator", "GameFrameX.CodeGenerator\GameFrameX.CodeGenerator.csproj", "{136B1385-A2C7-4974-BF08-2236B023C568}"
2424
EndProject
25+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestFastMapper", "TestFastMapper\TestFastMapper.csproj", "{34A33EED-CB9A-4D33-BCC3-D07896738E3C}"
26+
EndProject
2527
Global
2628
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2729
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +54,10 @@ Global
5254
{136B1385-A2C7-4974-BF08-2236B023C568}.Debug|Any CPU.Build.0 = Debug|Any CPU
5355
{136B1385-A2C7-4974-BF08-2236B023C568}.Release|Any CPU.ActiveCfg = Release|Any CPU
5456
{136B1385-A2C7-4974-BF08-2236B023C568}.Release|Any CPU.Build.0 = Release|Any CPU
57+
{34A33EED-CB9A-4D33-BCC3-D07896738E3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
58+
{34A33EED-CB9A-4D33-BCC3-D07896738E3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
59+
{34A33EED-CB9A-4D33-BCC3-D07896738E3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
60+
{34A33EED-CB9A-4D33-BCC3-D07896738E3C}.Release|Any CPU.Build.0 = Release|Any CPU
5561
EndGlobalSection
5662
GlobalSection(SolutionProperties) = preSolution
5763
HideSolutionNode = FALSE
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
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+
}

TestFastMapper/Core/Mapper.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// ========================================================
2+
// 描述:Mapper.cs
3+
// 作者:Bambomtan
4+
// 创建时间:2024-12-31 16:03:41 星期二
5+
6+
// 版 本:1.0
7+
// ========================================================
8+
9+
namespace FastMapper;
10+
public static partial class Mapper // 注意这里改成 partial class
11+
{
12+
// 存储编译时生成的所有映射方法的委托
13+
private static readonly Dictionary<(Type, Type), Delegate> _mappers = new();
14+
15+
static Mapper()
16+
{
17+
// 在静态构造函数中注册所有生成的映射器
18+
RegisterGeneratedMappers();
19+
}
20+
21+
// 声明为 partial 方法,让 Source Generator 来实现
22+
static partial void RegisterGeneratedMappers();
23+
24+
public static TDestination Map<TSource, TDestination>(TSource source)
25+
{
26+
var key = (typeof(TSource), typeof(TDestination));
27+
if (!_mappers.TryGetValue(key, out var mapper))
28+
{
29+
throw new InvalidOperationException(
30+
$"No mapper registered for {typeof(TSource)} -> {typeof(TDestination)}");
31+
}
32+
33+
return ((Func<TSource, TDestination>)mapper)(source);
34+
}
35+
}

0 commit comments

Comments
 (0)