Skip to content

Commit e96ca54

Browse files
committed
reflection: optimize anonymous types
1 parent 5562fa7 commit e96ca54

File tree

14 files changed

+1206
-174
lines changed

14 files changed

+1206
-174
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
using System.Dynamic;
2+
using BenchmarkDotNet.Attributes;
3+
using Force.DeepCloner;
4+
5+
namespace FastCloner.Benchmark;
6+
7+
[RankColumn]
8+
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
9+
[MemoryDiagnoser]
10+
[GroupBenchmarksBy(BenchmarkDotNet.Configs.BenchmarkLogicalGroupRule.ByCategory)]
11+
[CategoriesColumn]
12+
public class BenchDynamic
13+
{
14+
private dynamic _simple = null!;
15+
private dynamic _nested = null!;
16+
private dynamic _withCollections = null!;
17+
private dynamic _deepNested = null!;
18+
private dynamic _circular = null!;
19+
private StaticContainer _mixed = null!;
20+
private dynamic _large = null!;
21+
22+
[GlobalSetup]
23+
public void Setup()
24+
{
25+
_simple = CreateSimpleExpando();
26+
_nested = CreateNestedExpando();
27+
_withCollections = CreateExpandoWithCollections();
28+
_deepNested = CreateDeepNestedExpando(10);
29+
_circular = CreateCircularExpando();
30+
_mixed = CreateMixedTypes();
31+
_large = CreateLargeExpando(100);
32+
}
33+
34+
// Simple ExpandoObject
35+
/*[Benchmark, BenchmarkCategory("Simple")]
36+
public object FastCloner_Simple() => FastCloner.DeepClone(_simple);
37+
38+
[Benchmark(Baseline = true), BenchmarkCategory("Simple")]
39+
public object? DeepCloner_Simple() => _simple.DeepClone();
40+
41+
// Nested ExpandoObject
42+
[Benchmark, BenchmarkCategory("Nested")]
43+
public object FastCloner_Nested() => FastCloner.DeepClone(_nested);
44+
45+
[Benchmark(Baseline = true), BenchmarkCategory("Nested")]
46+
public object? DeepCloner_Nested() => ((object)_nested).DeepClone();
47+
48+
// ExpandoObject with Collections
49+
[Benchmark, BenchmarkCategory("Collections")]
50+
public object FastCloner_Collections() => FastCloner.DeepClone(_withCollections);
51+
52+
[Benchmark(Baseline = true), BenchmarkCategory("Collections")]
53+
public object? DeepCloner_Collections() => ((object)_withCollections).DeepClone();
54+
55+
// Deep Nested (10 levels)
56+
[Benchmark, BenchmarkCategory("DeepNested")]
57+
public object FastCloner_DeepNested() => FastCloner.DeepClone(_deepNested);
58+
59+
[Benchmark(Baseline = true), BenchmarkCategory("DeepNested")]
60+
public object? DeepCloner_DeepNested() => ((object)_deepNested).DeepClone();
61+
62+
// Circular Reference
63+
[Benchmark, BenchmarkCategory("Circular")]
64+
public object FastCloner_Circular() => FastCloner.DeepClone(_circular);
65+
66+
[Benchmark(Baseline = true), BenchmarkCategory("Circular")]
67+
public object? DeepCloner_Circular() => ((object)_circular).DeepClone();
68+
69+
// Mixed Dynamic + Static Types
70+
[Benchmark, BenchmarkCategory("Mixed")]
71+
public object? FastCloner_Mixed() => FastCloner.DeepClone(_mixed);
72+
73+
[Benchmark(Baseline = true), BenchmarkCategory("Mixed")]
74+
public object? DeepCloner_Mixed() => _mixed.DeepClone();*/
75+
76+
// Large ExpandoObject (100 properties)
77+
[Benchmark, BenchmarkCategory("Large")]
78+
public object FastCloner_Large() => FastCloner.DeepClone(_large);
79+
80+
[Benchmark(Baseline = true), BenchmarkCategory("Large")]
81+
public object? DeepCloner_Large() => ((object)_large).DeepClone();
82+
83+
#region Test Data Creation
84+
85+
private static dynamic CreateSimpleExpando()
86+
{
87+
dynamic expando = new ExpandoObject();
88+
expando.Name = "Test Object";
89+
expando.Id = 42;
90+
expando.IsActive = true;
91+
expando.CreatedAt = DateTime.UtcNow;
92+
expando.Price = 99.99m;
93+
return expando;
94+
}
95+
96+
private static dynamic CreateNestedExpando()
97+
{
98+
dynamic root = new ExpandoObject();
99+
root.Name = "Root";
100+
root.Level = 0;
101+
102+
dynamic child1 = new ExpandoObject();
103+
child1.Name = "Child1";
104+
child1.Level = 1;
105+
child1.Data = "Some data for child 1";
106+
107+
dynamic child2 = new ExpandoObject();
108+
child2.Name = "Child2";
109+
child2.Level = 1;
110+
child2.Value = 123.456;
111+
112+
dynamic grandchild = new ExpandoObject();
113+
grandchild.Name = "Grandchild";
114+
grandchild.Level = 2;
115+
grandchild.Tags = new[] { "tag1", "tag2", "tag3" };
116+
117+
child1.Child = grandchild;
118+
root.Children = new List<object> { child1, child2 };
119+
120+
return root;
121+
}
122+
123+
private static dynamic CreateExpandoWithCollections()
124+
{
125+
dynamic expando = new ExpandoObject();
126+
expando.Name = "Collection Test";
127+
128+
var items = new List<ExpandoObject>();
129+
for (int i = 0; i < 10; i++)
130+
{
131+
dynamic item = new ExpandoObject();
132+
item.Index = i;
133+
item.Label = $"Item {i}";
134+
item.Value = i * 10.5;
135+
items.Add(item);
136+
}
137+
expando.Items = items;
138+
139+
var dict = new Dictionary<string, ExpandoObject>();
140+
for (int i = 0; i < 5; i++)
141+
{
142+
dynamic entry = new ExpandoObject();
143+
entry.Key = $"key_{i}";
144+
entry.Data = $"Data for key {i}";
145+
dict[$"entry_{i}"] = entry;
146+
}
147+
expando.Lookup = dict;
148+
149+
expando.Numbers = new[] { 1, 2, 3, 4, 5 };
150+
expando.Strings = new List<string> { "a", "b", "c" };
151+
152+
return expando;
153+
}
154+
155+
private static dynamic CreateDeepNestedExpando(int depth)
156+
{
157+
dynamic current = new ExpandoObject();
158+
current.Level = depth;
159+
current.Name = $"Level_{depth}";
160+
current.Data = $"Data at depth {depth}";
161+
162+
if (depth > 0)
163+
current.Child = CreateDeepNestedExpando(depth - 1);
164+
165+
return current;
166+
}
167+
168+
private static dynamic CreateCircularExpando()
169+
{
170+
dynamic parent = new ExpandoObject();
171+
parent.Name = "Parent";
172+
parent.Id = 1;
173+
174+
dynamic child = new ExpandoObject();
175+
child.Name = "Child";
176+
child.Id = 2;
177+
child.Parent = parent;
178+
179+
parent.Child = child;
180+
parent.Self = parent;
181+
182+
return parent;
183+
}
184+
185+
private static StaticContainer CreateMixedTypes()
186+
{
187+
dynamic dynamicConfig = new ExpandoObject();
188+
dynamicConfig.Setting1 = "Value1";
189+
dynamicConfig.Setting2 = 42;
190+
dynamicConfig.Nested = new ExpandoObject();
191+
dynamicConfig.Nested.SubSetting = "SubValue";
192+
193+
var container = new StaticContainer
194+
{
195+
Id = 100,
196+
Name = "Container",
197+
DynamicData = dynamicConfig,
198+
Items = new List<StaticItem>
199+
{
200+
new() { ItemId = 1, ItemName = "Item1" },
201+
new() { ItemId = 2, ItemName = "Item2" },
202+
}
203+
};
204+
205+
dynamicConfig.Container = container;
206+
return container;
207+
}
208+
209+
private static dynamic CreateLargeExpando(int propertyCount)
210+
{
211+
dynamic expando = new ExpandoObject();
212+
var dict = (IDictionary<string, object?>)expando;
213+
214+
for (int i = 0; i < propertyCount; i++)
215+
{
216+
switch (i % 5)
217+
{
218+
case 0:
219+
dict[$"StringProp_{i}"] = $"String value {i}";
220+
break;
221+
case 1:
222+
dict[$"IntProp_{i}"] = i * 10;
223+
break;
224+
case 2:
225+
dict[$"DoubleProp_{i}"] = i * 1.5;
226+
break;
227+
case 3:
228+
dict[$"DateProp_{i}"] = DateTime.UtcNow.AddDays(i);
229+
break;
230+
case 4:
231+
dict[$"NestedProp_{i}"] = new
232+
{
233+
NestedId = i,
234+
NestedValue = $"Nested {i}"
235+
};;
236+
break;
237+
}
238+
}
239+
240+
return expando;
241+
}
242+
243+
#endregion
244+
}
245+
246+
public class StaticContainer
247+
{
248+
public int Id { get; set; }
249+
public string Name { get; set; } = "";
250+
public dynamic? DynamicData { get; set; }
251+
public List<StaticItem> Items { get; set; } = new();
252+
}
253+
254+
public class StaticItem
255+
{
256+
public int ItemId { get; set; }
257+
public string ItemName { get; set; } = "";
258+
}

src/FastCloner.Benchmark/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ static void Main(string[] args)
1212
ManualConfig config = DefaultConfig.Instance
1313
.WithOptions(ConfigOptions.DisableOptimizationsValidator);
1414

15-
Summary summary = BenchmarkRunner.Run<BenchMinimal>(config);
15+
Summary summary = BenchmarkRunner.Run<BenchDynamic>(config);
1616
Console.WriteLine(summary);
1717
}
1818
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<LangVersion>preview</LangVersion>
7+
<ImplicitUsings>enable</ImplicitUsings>
8+
<Nullable>enable</Nullable>
9+
<DebugType>full</DebugType>
10+
<DebugSymbols>true</DebugSymbols>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<ProjectReference Include="..\FastCloner\FastCloner.csproj" />
15+
<ProjectReference Include="..\FastCloner.SourceGenerator\FastCloner.SourceGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
16+
<ProjectReference Include="..\FastCloner.SourceGenerator.Shared\FastCloner.SourceGenerator.Shared.csproj" />
17+
</ItemGroup>
18+
19+
</Project>

0 commit comments

Comments
 (0)