Skip to content

Commit 690e816

Browse files
author
MPCoreDeveloper
committed
check in demo ref
1 parent 21b95b1 commit 690e816

File tree

4 files changed

+566
-0
lines changed

4 files changed

+566
-0
lines changed

SharpCoreDB.sln

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDb.Orchardcore", "
6464
EndProject
6565
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpCoreDB.Examples.TimeSeries", "Examples\Desktop\SharpCoreDB.Examples.TimeSeries\SharpCoreDB.Examples.TimeSeries.csproj", "{A0C49171-D013-CE79-41C2-2B01E3F6E0BF}"
6666
EndProject
67+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Snippets", "Snippets", "{53FBF8A2-0A92-44AD-9CD0-4FBDF7F62C9B}"
68+
EndProject
69+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RefFieldDemo", "tests\Manual\RefFieldDemo\RefFieldDemo.csproj", "{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}"
70+
EndProject
6771
Global
6872
GlobalSection(SolutionConfigurationPlatforms) = preSolution
6973
Debug|Any CPU = Debug|Any CPU
@@ -254,6 +258,18 @@ Global
254258
{A0C49171-D013-CE79-41C2-2B01E3F6E0BF}.Release|x64.Build.0 = Release|Any CPU
255259
{A0C49171-D013-CE79-41C2-2B01E3F6E0BF}.Release|x86.ActiveCfg = Release|Any CPU
256260
{A0C49171-D013-CE79-41C2-2B01E3F6E0BF}.Release|x86.Build.0 = Release|Any CPU
261+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
262+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
263+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|x64.ActiveCfg = Debug|Any CPU
264+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|x64.Build.0 = Debug|Any CPU
265+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|x86.ActiveCfg = Debug|Any CPU
266+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Debug|x86.Build.0 = Debug|Any CPU
267+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
268+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|Any CPU.Build.0 = Release|Any CPU
269+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|x64.ActiveCfg = Release|Any CPU
270+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|x64.Build.0 = Release|Any CPU
271+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|x86.ActiveCfg = Release|Any CPU
272+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0}.Release|x86.Build.0 = Release|Any CPU
257273
EndGlobalSection
258274
GlobalSection(SolutionProperties) = preSolution
259275
HideSolutionNode = FALSE
@@ -279,6 +295,8 @@ Global
279295
{B8EFDB5D-C99E-47D5-91E7-251915885AF0} = {A3546A87-CE5C-44A8-84EE-B5FA96737516}
280296
{11359F89-59B7-E251-FC8F-D8A95313CA91} = {B8EFDB5D-C99E-47D5-91E7-251915885AF0}
281297
{A0C49171-D013-CE79-41C2-2B01E3F6E0BF} = {F4AC329D-E0EF-4EC6-A21B-DD74A344A3A5}
298+
{53FBF8A2-0A92-44AD-9CD0-4FBDF7F62C9B} = {2F8A8533-DAA8-4CF9-A6C0-2F663AF7FD2E}
299+
{800EBE19-FE8B-EBD4-D5F7-AFB27C1755A0} = {A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D}
282300
EndGlobalSection
283301
GlobalSection(ExtensibilityGlobals) = postSolution
284302
SolutionGuid = {F40825F5-26A1-4E85-9D0A-B0121A7ED5F8}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# 🛡️ Ref-Field Wrapper Pattern: Preventing Silent Struct Copy Bugs
2+
3+
## Het Probleem
4+
5+
Bij het itereren over mutable structs in C# is het **gevaarlijk makkelijk** om het `ref` keyword te vergeten:
6+
7+
```csharp
8+
// ❌ BUG: 'handle' is een KOPIE — mutaties gaan stilletjes verloren!
9+
foreach (var handle in store.EnumerateHandles())
10+
{
11+
handle.Clear(); // Muteert een tijdelijke kopie, origineel blijft ongewijzigd
12+
}
13+
14+
// ✅ CORRECT: 'ref' zorgt dat we het origineel muteren
15+
foreach (ref var handle in store.EnumerateHandles())
16+
{
17+
handle.Clear(); // Muteert het origineel in-place
18+
}
19+
```
20+
21+
Er is **geen compiler warning** voor deze fout. De code compileert en draait — maar doet stilletjes niets. Dit leidt tot extreem moeilijk te vinden bugs.
22+
23+
> **Oorspronkelijke vraag:** *"I wish there was a way to prevent copies like in C++, or at least trigger a warning."*
24+
25+
---
26+
27+
## De Oplossing: Ref-Field Wrapper Struct
28+
29+
C# 11 introduceerde **ref fields** — een veld in een `ref struct` dat een *referentie* naar een andere waarde vasthoudt. We gebruiken dit om een dunne wrapper te maken die **altijd naar het origineel wijst**, zelfs als de wrapper zelf gekopieerd wordt.
30+
31+
### Hoe Het Werkt
32+
33+
```
34+
┌─────────────────────────────────────────────────────┐
35+
│ foreach (var wrapper in ...) │
36+
│ │
37+
│ wrapper (stack kopie) │
38+
│ ┌──────────────────────┐ │
39+
│ │ ref Handle Value ──────────► Origineel Handle[i] │
40+
│ └──────────────────────┘ in Span<Handle> │
41+
│ │
42+
│ Kopiëren van de wrapper kopieert de REF, │
43+
│ niet de data. │
44+
│ wrapper.Clear() muteert altijd het origineel. │
45+
└─────────────────────────────────────────────────────┘
46+
```
47+
48+
| Scenario | Zonder Wrapper | Met Wrapper |
49+
|---|---|---|
50+
| `foreach (ref var h in ...)` | ✅ Muteert origineel | ✅ Muteert origineel |
51+
| `foreach (var h in ...)` |**Stille bug** — muteert kopie |**Muteert nog steeds origineel** |
52+
53+
---
54+
55+
## Het Patroon (3 Stappen)
56+
57+
### Stap 1: Definieer de Wrapper
58+
59+
```csharp
60+
public readonly ref struct RefHandle
61+
{
62+
public readonly ref Handle Value; // C# 11+ ref field
63+
64+
public RefHandle(ref Handle value) => Value = ref value;
65+
66+
// Forward mutatie-methoden voor ergonomische API
67+
public void Clear() => Value.Clear();
68+
public int Id => Value.Id;
69+
public bool IsActive => Value.IsActive;
70+
}
71+
```
72+
73+
### Stap 2: Enumerator Levert Wrappers
74+
75+
```csharp
76+
public ref struct HandleEnumerator
77+
{
78+
private readonly Span<Handle> _span;
79+
private int _index;
80+
81+
public HandleEnumerator(Span<Handle> span)
82+
{
83+
_span = span;
84+
_index = -1;
85+
}
86+
87+
public bool MoveNext() => ++_index < _span.Length;
88+
public RefHandle Current => new(ref _span[_index]);
89+
public HandleEnumerator GetEnumerator() => this;
90+
}
91+
```
92+
93+
### Stap 3: Gebruik Het — `ref` Keyword Niet Meer Nodig
94+
95+
```csharp
96+
// Beide muteren correct de originelen:
97+
foreach (var handle in store.EnumerateHandles()) // ✅ Veilig!
98+
handle.Clear();
99+
100+
foreach (ref var handle in store.EnumerateHandles()) // ✅ Ook veilig, maar overbodig
101+
handle.Value.Clear();
102+
```
103+
104+
---
105+
106+
## Eigenschappen
107+
108+
| Eigenschap | Waarde |
109+
|---|---|
110+
| **Runtime kosten** | Nul — stack-only, geen heap allocaties |
111+
| **Unsafe code** | Niet nodig |
112+
| **Custom analyzers** | Niet nodig |
113+
| **Minimum C# versie** | C# 11 (`ref` fields) |
114+
| **Werkt met** | `Span<T>`, `ref struct` enumerators |
115+
116+
---
117+
118+
## Demo Draaien
119+
120+
```bash
121+
cd tests/Manual/RefFieldDemo
122+
dotnet run
123+
```
124+
125+
### Verwachte Output
126+
127+
```
128+
=== Ref-Field Wrapper Pattern Demo ===
129+
130+
── Demo 1: 'foreach (var h in ...)' WITH wrapper ──
131+
(No 'ref' keyword — should still mutate originals)
132+
133+
Before:
134+
Handle(Id=1, IsActive=True)
135+
Handle(Id=2, IsActive=True)
136+
Handle(Id=3, IsActive=True)
137+
After Clear() via 'var':
138+
Handle(Id=0, IsActive=False)
139+
Handle(Id=0, IsActive=False)
140+
Handle(Id=0, IsActive=False)
141+
142+
✅ SUCCESS: All handles mutated in-place WITHOUT 'ref'!
143+
144+
── Demo 2: Explicit wrapper copy still mutates original ──
145+
146+
Before: Handle(Id=42, IsActive=True)
147+
After copy2.Clear(): Handle(Id=0, IsActive=False)
148+
✅ SUCCESS: Double-copied wrapper still mutated the original!
149+
150+
── Demo 3: The BUG without wrapper ──
151+
(Raw Span<T> + foreach var = silent copy bug)
152+
153+
Before:
154+
Handle(Id=10, IsActive=True)
155+
Handle(Id=20, IsActive=True)
156+
After Clear() via 'var' (NO wrapper):
157+
Handle(Id=10, IsActive=True)
158+
Handle(Id=20, IsActive=True)
159+
160+
⚠️ BUG DEMONSTRATED: Originals are UNCHANGED — Clear() was lost!
161+
→ This is exactly the bug the wrapper pattern prevents.
162+
```
163+
164+
---
165+
166+
## Wanneer Dit Patroon Gebruiken
167+
168+
- Je slaat **mutable structs** op in aaneengesloten geheugen (`Span<T>`, arrays)
169+
- Je enumereert ze en moet **in-place muteren**
170+
- Je wilt de **hele klasse van bugs elimineren** waar `ref` vergeten wordt
171+
- Je hebt **zero-allocation** iteratie nodig
172+
173+
---
174+
175+
## Alternatief: Delegate-Based Approach
176+
177+
Als je geen wrapper wilt, kun je een callback-patroon gebruiken:
178+
179+
```csharp
180+
public delegate void RefAction<T>(ref T item);
181+
182+
public void ForEachHandle(RefAction<Handle> action)
183+
{
184+
for (int i = 0; i < _handles.Length; i++)
185+
{
186+
action(ref _handles[i]);
187+
}
188+
}
189+
190+
// Gebruik:
191+
store.ForEachHandle(static (ref Handle h) => h.Clear());
192+
```
193+
194+
Dit is minder ergonomisch maar ook veilig — de caller kan `ref` niet vergeten omdat de delegate het afdwingt.
195+
196+
---
197+
198+
## Zie Ook
199+
200+
- [C# 11 ref fields specificatie](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct#ref-fields)
201+
- [`tests/Manual/RefFieldDemo/`](../tests/Manual/RefFieldDemo/) — Werkende demo

0 commit comments

Comments
 (0)