@cratis/pi 7.102.0 → 7.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +85 -1
- package/package.json +1 -1
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Critical .NET Performance Anti-Patterns
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Async / Tasks
|
|
6
|
+
- Memory / Allocation
|
|
7
|
+
- Strings
|
|
8
|
+
- Regular Expressions
|
|
9
|
+
- JSON Serialization
|
|
10
|
+
- Networking
|
|
11
|
+
- General
|
|
12
|
+
- Detection
|
|
13
|
+
|
|
14
|
+
17 patterns that cause deadlocks, order-of-magnitude regressions, or excessive allocations.
|
|
15
|
+
|
|
16
|
+
## Async / Tasks
|
|
17
|
+
|
|
18
|
+
### Never Block on Async (Sync-over-Async)
|
|
19
|
+
🔴 **AVOID** | .NET Core+
|
|
20
|
+
|
|
21
|
+
❌
|
|
22
|
+
```csharp
|
|
23
|
+
public string GetData()
|
|
24
|
+
=> GetDataAsync().Result;
|
|
25
|
+
```
|
|
26
|
+
✅
|
|
27
|
+
```csharp
|
|
28
|
+
public async Task<string> GetDataAsync()
|
|
29
|
+
=> await GetDataInternalAsync();
|
|
30
|
+
```
|
|
31
|
+
**Impact: Deadlocks or thread pool starvation; wastes threads, destroys scalability.**
|
|
32
|
+
|
|
33
|
+
### Never Await a ValueTask Multiple Times
|
|
34
|
+
🔴 **AVOID** | .NET Core 2.1+
|
|
35
|
+
|
|
36
|
+
❌
|
|
37
|
+
```csharp
|
|
38
|
+
ValueTask<int> vt = SomeMethodAsync();
|
|
39
|
+
int a = await vt;
|
|
40
|
+
int b = await vt;
|
|
41
|
+
```
|
|
42
|
+
✅
|
|
43
|
+
```csharp
|
|
44
|
+
int result = await SomeMethodAsync();
|
|
45
|
+
```
|
|
46
|
+
**Impact: Undefined behavior — silent data corruption or exceptions.**
|
|
47
|
+
|
|
48
|
+
## Memory / Allocation
|
|
49
|
+
|
|
50
|
+
### Use Span\<T\> / AsSpan Instead of Substring for Slicing
|
|
51
|
+
🔴 **DO** | .NET Core 2.1+
|
|
52
|
+
|
|
53
|
+
❌
|
|
54
|
+
```csharp
|
|
55
|
+
string sub = input.Substring(5, 10);
|
|
56
|
+
```
|
|
57
|
+
✅
|
|
58
|
+
```csharp
|
|
59
|
+
ReadOnlySpan<char> sub = input.AsSpan(5, 10);
|
|
60
|
+
```
|
|
61
|
+
**Impact: Eliminates per-slice allocations; 2-4x faster via vectorization.**
|
|
62
|
+
|
|
63
|
+
### Use ArrayPool\<T\> for Temporary Buffers
|
|
64
|
+
🔴 **DO** | .NET Core+
|
|
65
|
+
|
|
66
|
+
❌
|
|
67
|
+
```csharp
|
|
68
|
+
byte[] buf = new byte[4096];
|
|
69
|
+
```
|
|
70
|
+
✅
|
|
71
|
+
```csharp
|
|
72
|
+
byte[] buf = ArrayPool<byte>.Shared.Rent(4096);
|
|
73
|
+
Process(buf);
|
|
74
|
+
ArrayPool<byte>.Shared.Return(buf);
|
|
75
|
+
```
|
|
76
|
+
**Impact: Dramatically reduces GC pressure for buffer-heavy workloads.**
|
|
77
|
+
|
|
78
|
+
### Avoid stackalloc in Loops
|
|
79
|
+
🔴 **AVOID** | .NET 5+
|
|
80
|
+
|
|
81
|
+
❌
|
|
82
|
+
```csharp
|
|
83
|
+
for (int i = 0; i < 10_000; i++)
|
|
84
|
+
Span<byte> buf = stackalloc byte[1024];
|
|
85
|
+
```
|
|
86
|
+
✅
|
|
87
|
+
```csharp
|
|
88
|
+
Span<byte> buf = stackalloc byte[1024];
|
|
89
|
+
for (int i = 0; i < 10_000; i++) { Process(buf); }
|
|
90
|
+
```
|
|
91
|
+
**Impact: StackOverflowException — unrecoverable, no catch possible.**
|
|
92
|
+
|
|
93
|
+
### Avoid Boxing Value Types
|
|
94
|
+
🔴 **AVOID** | .NET 6+
|
|
95
|
+
|
|
96
|
+
❌
|
|
97
|
+
```csharp
|
|
98
|
+
string s = string.Format("{0}.{1}", major, minor);
|
|
99
|
+
```
|
|
100
|
+
✅
|
|
101
|
+
```csharp
|
|
102
|
+
string s = $"{major}.{minor}";
|
|
103
|
+
```
|
|
104
|
+
**Impact: When replacing `string.Format` with C# 10+ interpolation, typical improvements are ~40% faster with significantly less allocation. Actual gains vary by call site.**
|
|
105
|
+
|
|
106
|
+
## Strings
|
|
107
|
+
|
|
108
|
+
### Use StringComparison.Ordinal for Non-Linguistic Comparisons
|
|
109
|
+
🔴 **DO** | .NET Core+
|
|
110
|
+
|
|
111
|
+
❌
|
|
112
|
+
```csharp
|
|
113
|
+
bool found = text.IndexOf("Content-Type") >= 0;
|
|
114
|
+
```
|
|
115
|
+
✅
|
|
116
|
+
```csharp
|
|
117
|
+
bool found = text.Contains("Content-Type", StringComparison.Ordinal);
|
|
118
|
+
```
|
|
119
|
+
**Impact: 2-3x faster; OrdinalIgnoreCase hash codes ~3.3x faster.**
|
|
120
|
+
|
|
121
|
+
### Use AsSpan Instead of Substring
|
|
122
|
+
🔴 **DO** | .NET Core 2.1+
|
|
123
|
+
|
|
124
|
+
❌
|
|
125
|
+
```csharp
|
|
126
|
+
int val = int.Parse(str.Substring(5, 3));
|
|
127
|
+
```
|
|
128
|
+
✅
|
|
129
|
+
```csharp
|
|
130
|
+
int val = int.Parse(str.AsSpan(5, 3));
|
|
131
|
+
```
|
|
132
|
+
**Impact: Eliminates one string allocation per parse operation.**
|
|
133
|
+
|
|
134
|
+
## Regular Expressions
|
|
135
|
+
|
|
136
|
+
### Use Source-Generated Regex [GeneratedRegex]
|
|
137
|
+
🔴 **ALWAYS** use `[GeneratedRegex]` for all static regex patterns | .NET 7+
|
|
138
|
+
|
|
139
|
+
❌
|
|
140
|
+
```csharp
|
|
141
|
+
private static readonly Regex s_re =
|
|
142
|
+
new(@"\w+@\w+\.\w+", RegexOptions.Compiled);
|
|
143
|
+
```
|
|
144
|
+
✅
|
|
145
|
+
```csharp
|
|
146
|
+
[GeneratedRegex(@"\w+@\w+\.\w+")]
|
|
147
|
+
private static partial Regex EmailRegex();
|
|
148
|
+
```
|
|
149
|
+
**Impact: Always beneficial or neutral for static patterns — near-zero startup, better throughput, and required for AOT/trimming scenarios.**
|
|
150
|
+
|
|
151
|
+
### Avoid Nested Quantifiers (Catastrophic Backtracking)
|
|
152
|
+
🔴 **AVOID** | .NET Core+
|
|
153
|
+
|
|
154
|
+
❌
|
|
155
|
+
```csharp
|
|
156
|
+
var r = new Regex(@"^(\w+)+$");
|
|
157
|
+
```
|
|
158
|
+
✅
|
|
159
|
+
```csharp
|
|
160
|
+
var r = new Regex(@"^\w+$", RegexOptions.NonBacktracking);
|
|
161
|
+
```
|
|
162
|
+
**Impact: Can hang process indefinitely on crafted input.**
|
|
163
|
+
|
|
164
|
+
### Use TryGetValue Instead of ContainsKey + Indexer
|
|
165
|
+
🔴 **DO** | .NET Core+
|
|
166
|
+
|
|
167
|
+
❌
|
|
168
|
+
```csharp
|
|
169
|
+
if (dict.ContainsKey(key))
|
|
170
|
+
Use(dict[key]);
|
|
171
|
+
```
|
|
172
|
+
✅
|
|
173
|
+
```csharp
|
|
174
|
+
if (dict.TryGetValue(key, out var value))
|
|
175
|
+
Use(value);
|
|
176
|
+
```
|
|
177
|
+
**Impact: ~2x faster (50% reduction in lookup time).**
|
|
178
|
+
|
|
179
|
+
### Avoid LINQ in Hot Paths
|
|
180
|
+
🔴 **AVOID** | .NET Core+
|
|
181
|
+
|
|
182
|
+
❌
|
|
183
|
+
```csharp
|
|
184
|
+
bool found = items.Any(x => x.Name == target);
|
|
185
|
+
```
|
|
186
|
+
✅
|
|
187
|
+
```csharp
|
|
188
|
+
bool found = false;
|
|
189
|
+
foreach (var item in items)
|
|
190
|
+
if (item.Name == target) { found = true; break; }
|
|
191
|
+
```
|
|
192
|
+
**Impact: Eliminates 1-3 allocations per call; measurable in tight loops.**
|
|
193
|
+
|
|
194
|
+
### Don't Iterate IEnumerable Multiple Times
|
|
195
|
+
🔴 **AVOID** | .NET Core+
|
|
196
|
+
|
|
197
|
+
❌
|
|
198
|
+
```csharp
|
|
199
|
+
foreach (Type t in types) { Validate(t); }
|
|
200
|
+
_types = types.ToArray();
|
|
201
|
+
```
|
|
202
|
+
✅
|
|
203
|
+
```csharp
|
|
204
|
+
Type[] arr = types.ToArray();
|
|
205
|
+
foreach (Type t in arr) { Validate(t); }
|
|
206
|
+
_types = arr;
|
|
207
|
+
```
|
|
208
|
+
**Impact: Halves enumeration cost; prevents bugs from re-executing deferred queries.**
|
|
209
|
+
|
|
210
|
+
## JSON Serialization
|
|
211
|
+
|
|
212
|
+
### Use System.Text.Json Source Generator
|
|
213
|
+
🔴 **DO** | .NET 6+
|
|
214
|
+
|
|
215
|
+
❌
|
|
216
|
+
```csharp
|
|
217
|
+
string json = JsonSerializer.Serialize(post);
|
|
218
|
+
```
|
|
219
|
+
✅
|
|
220
|
+
```csharp
|
|
221
|
+
[JsonSerializable(typeof(BlogPost))]
|
|
222
|
+
internal partial class AppJsonCtx : JsonSerializerContext { }
|
|
223
|
+
string json = JsonSerializer.Serialize(post, AppJsonCtx.Default.BlogPost);
|
|
224
|
+
```
|
|
225
|
+
**Impact: 37-44% faster; enables trimming and Native AOT.**
|
|
226
|
+
|
|
227
|
+
### Cache JsonSerializerOptions
|
|
228
|
+
🔴 **DO** | .NET 5+
|
|
229
|
+
|
|
230
|
+
❌
|
|
231
|
+
```csharp
|
|
232
|
+
JsonSerializer.Serialize(obj, new JsonSerializerOptions());
|
|
233
|
+
```
|
|
234
|
+
✅
|
|
235
|
+
```csharp
|
|
236
|
+
private static readonly JsonSerializerOptions s_opts = new();
|
|
237
|
+
JsonSerializer.Serialize(obj, s_opts);
|
|
238
|
+
```
|
|
239
|
+
**Impact: Up to 592x slower without caching (.NET 6); always cache or use defaults.**
|
|
240
|
+
|
|
241
|
+
## Networking
|
|
242
|
+
|
|
243
|
+
### Reuse HttpClient Instances
|
|
244
|
+
🔴 **DO** | .NET Core 2.1+
|
|
245
|
+
|
|
246
|
+
❌
|
|
247
|
+
```csharp
|
|
248
|
+
using var client = new HttpClient();
|
|
249
|
+
await client.GetStringAsync(url);
|
|
250
|
+
```
|
|
251
|
+
✅
|
|
252
|
+
```csharp
|
|
253
|
+
private static readonly HttpClient s_http = new(new SocketsHttpHandler
|
|
254
|
+
{ PooledConnectionLifetime = TimeSpan.FromMinutes(5) });
|
|
255
|
+
await s_http.GetStringAsync(url);
|
|
256
|
+
```
|
|
257
|
+
**Impact: Prevents socket exhaustion; 6-12x faster concurrent HTTPS.**
|
|
258
|
+
|
|
259
|
+
## General
|
|
260
|
+
|
|
261
|
+
### Use SearchValues\<T\> for Repeated Set Searches
|
|
262
|
+
🔴 **DO** | .NET 8+
|
|
263
|
+
|
|
264
|
+
❌
|
|
265
|
+
```csharp
|
|
266
|
+
int pos = text.IndexOfAny("ABCDEF".ToCharArray());
|
|
267
|
+
```
|
|
268
|
+
✅
|
|
269
|
+
```csharp
|
|
270
|
+
private static readonly SearchValues<char> s_hex = SearchValues.Create("ABCDEF");
|
|
271
|
+
int pos = text.AsSpan().IndexOfAny(s_hex);
|
|
272
|
+
```
|
|
273
|
+
**Impact: 2-10x faster for chars; 10-30x faster for multi-string (.NET 9+).**
|
|
274
|
+
|
|
275
|
+
## Detection
|
|
276
|
+
|
|
277
|
+
Scan recipes for critical anti-patterns. Run these and report exact counts of issues found in each case.
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
# .IndexOf(string) without StringComparison (culture-aware, 2-3x slower)
|
|
281
|
+
grep -rn --include='*.cs' -E '\.IndexOf\("[^"]+"\)' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
282
|
+
|
|
283
|
+
# .Substring( calls (allocates new string — consider AsSpan)
|
|
284
|
+
grep -rn --include='*.cs' '\.Substring(' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
285
|
+
|
|
286
|
+
# .StartsWith/.EndsWith without StringComparison (culture-aware, 2-3x slower)
|
|
287
|
+
grep -rn --include='*.cs' -E '\.(StartsWith|EndsWith)\("[^"]+"\)' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
288
|
+
|
|
289
|
+
# .Contains(string) without StringComparison — NOTE: will also match collection .Contains() calls; filter to string receivers
|
|
290
|
+
grep -rn --include='*.cs' -E '\.Contains\("[^"]+"\)' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
291
|
+
```
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# I/O, Serialization & General Patterns
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Use HttpCompletionOption.ResponseHeadersRead for Streaming
|
|
6
|
+
- Use Async FileStream Operations
|
|
7
|
+
- Use Memory\<byte\> Overloads for Stream.ReadAsync/WriteAsync
|
|
8
|
+
- Use Span-Based TryFormat for Number Formatting
|
|
9
|
+
- Use static readonly for Runtime Devirtualization
|
|
10
|
+
- Avoid Explicit Static Constructors — Use Field Initializers
|
|
11
|
+
- Detection
|
|
12
|
+
- Patterns Requiring Manual Review
|
|
13
|
+
|
|
14
|
+
### Use HttpCompletionOption.ResponseHeadersRead for Streaming
|
|
15
|
+
🟡 **DO** use `ResponseHeadersRead` when downloading large responses | .NET Core 3.0+
|
|
16
|
+
|
|
17
|
+
❌
|
|
18
|
+
```csharp
|
|
19
|
+
var response = await client.GetAsync(uri);
|
|
20
|
+
```
|
|
21
|
+
✅
|
|
22
|
+
```csharp
|
|
23
|
+
using var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
|
24
|
+
using var stream = await response.Content.ReadAsStreamAsync();
|
|
25
|
+
await stream.CopyToAsync(destinationStream);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Impact: ~2x faster for large downloads (10MB+), dramatically reduced memory usage.**
|
|
29
|
+
|
|
30
|
+
### Use Async FileStream Operations
|
|
31
|
+
🟡 **DO** use `FileStream` with `useAsync: true` for scalable file I/O | .NET 6+
|
|
32
|
+
|
|
33
|
+
❌
|
|
34
|
+
```csharp
|
|
35
|
+
using var fs = new FileStream(path, FileMode.Open);
|
|
36
|
+
```
|
|
37
|
+
✅
|
|
38
|
+
```csharp
|
|
39
|
+
await using var fs = new FileStream(path, FileMode.Open, FileAccess.Read,
|
|
40
|
+
FileShare.Read, bufferSize: 4096, useAsync: true);
|
|
41
|
+
|
|
42
|
+
byte[] buffer = new byte[1024];
|
|
43
|
+
while (await fs.ReadAsync(buffer) != 0) { /* process */ }
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Impact: Up to 3x faster async reads; allocation reduced from megabytes to hundreds of bytes.**
|
|
47
|
+
|
|
48
|
+
### Use Memory\<byte\> Overloads for Stream.ReadAsync/WriteAsync
|
|
49
|
+
🟡 **DO** use `Memory<byte>`-based stream overloads instead of `byte[]` overloads | .NET 5+
|
|
50
|
+
|
|
51
|
+
❌
|
|
52
|
+
```csharp
|
|
53
|
+
await stream.ReadAsync(buffer, 0, buffer.Length);
|
|
54
|
+
await stream.WriteAsync(buffer, 0, buffer.Length);
|
|
55
|
+
```
|
|
56
|
+
✅
|
|
57
|
+
```csharp
|
|
58
|
+
await stream.ReadAsync(buffer.AsMemory());
|
|
59
|
+
await stream.WriteAsync(buffer.AsMemory());
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Impact: Eliminates ~72 KB allocation per 1,000 read/write pairs on NetworkStream.**
|
|
63
|
+
|
|
64
|
+
### Use Span-Based TryFormat for Number Formatting
|
|
65
|
+
🟡 **DO** use `TryFormat` to format numbers into `Span<char>` buffers | .NET Core 2.1+
|
|
66
|
+
|
|
67
|
+
❌
|
|
68
|
+
```csharp
|
|
69
|
+
string formatted = value.ToString();
|
|
70
|
+
destination.Write(formatted);
|
|
71
|
+
```
|
|
72
|
+
✅
|
|
73
|
+
```csharp
|
|
74
|
+
Span<char> buffer = stackalloc char[20];
|
|
75
|
+
if (value.TryFormat(buffer, out int charsWritten))
|
|
76
|
+
destination.Write(buffer[..charsWritten]);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Impact: Int32.ToString() ~2x faster in .NET Core 2.1, Int32 parsing ~5x faster in .NET Core 3.0.**
|
|
80
|
+
|
|
81
|
+
### Use static readonly for Runtime Devirtualization
|
|
82
|
+
🟡 **DO** store implementations in `static readonly` fields for JIT devirtualization | .NET Core 3.0+
|
|
83
|
+
|
|
84
|
+
❌
|
|
85
|
+
```csharp
|
|
86
|
+
private static Base s_impl = new DerivedImpl();
|
|
87
|
+
s_impl.Process();
|
|
88
|
+
```
|
|
89
|
+
✅
|
|
90
|
+
```csharp
|
|
91
|
+
private static readonly Base s_impl = new DerivedImpl();
|
|
92
|
+
s_impl.Process();
|
|
93
|
+
|
|
94
|
+
private static readonly bool s_feature =
|
|
95
|
+
Environment.GetEnvironmentVariable("Feature") == "1";
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Impact: Virtual call eliminated entirely — can be inlined to zero overhead. Dead code elimination in tier 1.**
|
|
99
|
+
|
|
100
|
+
### Avoid Explicit Static Constructors — Use Field Initializers
|
|
101
|
+
🟡 **AVOID** explicit `static` constructors when field initializers suffice | .NET Core 3.0+
|
|
102
|
+
|
|
103
|
+
❌
|
|
104
|
+
```csharp
|
|
105
|
+
class Foo
|
|
106
|
+
{
|
|
107
|
+
static readonly int s_value;
|
|
108
|
+
static Foo() { s_value = ComputeValue(); }
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
✅
|
|
112
|
+
```csharp
|
|
113
|
+
class Foo
|
|
114
|
+
{
|
|
115
|
+
static readonly int s_value = ComputeValue();
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Impact: Enables better JIT optimization and reduces potential lock overhead on static method access.**
|
|
120
|
+
|
|
121
|
+
## Detection
|
|
122
|
+
|
|
123
|
+
Scan recipes for I/O and serialization anti-patterns. Run these and report exact counts.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# new HttpClient() (socket exhaustion risk)
|
|
127
|
+
grep -rn --include='*.cs' 'new HttpClient(' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
128
|
+
|
|
129
|
+
# new JsonSerializerOptions() not cached (592x slower in .NET 6)
|
|
130
|
+
grep -rn --include='*.cs' 'new JsonSerializerOptions' --exclude-dir=bin --exclude-dir=obj . | grep -v 'static\|readonly' | wc -l
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Patterns Requiring Manual Review
|
|
134
|
+
|
|
135
|
+
- **`JsonSerializer.Serialize/Deserialize` without source-gen context**: Can't determine from grep if a context parameter is passed
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Memory & String Patterns
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- Use ReadOnlySpan\<byte\> for Constant Byte Data
|
|
6
|
+
- Use stackalloc for Small Temporary Buffers
|
|
7
|
+
- Use Span.TryWrite for Allocation-Free Interpolation
|
|
8
|
+
- Use Span.Split() for Zero-Allocation Splitting
|
|
9
|
+
- Use UTF8 String Literals (u8 suffix)
|
|
10
|
+
- Use ReadOnlySpan\<char\> Pattern Matching with switch
|
|
11
|
+
- Use params ReadOnlySpan\<T\> to Eliminate Array Allocations
|
|
12
|
+
- Avoid Chained String-Returning Operations
|
|
13
|
+
- Cache char.ToString() for Known Character Sets
|
|
14
|
+
- Detection
|
|
15
|
+
- Patterns Requiring Manual Review
|
|
16
|
+
|
|
17
|
+
### Use ReadOnlySpan\<byte\> for Constant Byte Data
|
|
18
|
+
🟡 **DO** assign constant byte arrays to `ReadOnlySpan<byte>` | .NET 5+
|
|
19
|
+
|
|
20
|
+
❌
|
|
21
|
+
```csharp
|
|
22
|
+
byte[] data = new byte[] { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
|
|
23
|
+
```
|
|
24
|
+
✅
|
|
25
|
+
```csharp
|
|
26
|
+
ReadOnlySpan<byte> data = [0x48, 0x65, 0x6C, 0x6C, 0x6F];
|
|
27
|
+
ReadOnlySpan<int> primes = [2, 3, 5, 7, 11, 13];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Impact: ~100x faster access than static byte[] field, zero allocation.**
|
|
31
|
+
|
|
32
|
+
### Use stackalloc for Small Temporary Buffers
|
|
33
|
+
🟡 **DO** use `stackalloc` for small, fixed-size temporary buffers | .NET Core+
|
|
34
|
+
|
|
35
|
+
❌
|
|
36
|
+
```csharp
|
|
37
|
+
char[] buffer = new char[64];
|
|
38
|
+
guid.TryFormat(buffer, out int written);
|
|
39
|
+
```
|
|
40
|
+
✅
|
|
41
|
+
```csharp
|
|
42
|
+
Span<char> buffer = stackalloc char[64];
|
|
43
|
+
guid.TryFormat(buffer, out int written);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Impact: Zero heap allocation, no GC pressure, instant alloc/dealloc.**
|
|
47
|
+
|
|
48
|
+
### Use Span.TryWrite for Allocation-Free Interpolation
|
|
49
|
+
🟡 **DO** use `MemoryExtensions.TryWrite` to format into `Span<char>` buffers | .NET 6+
|
|
50
|
+
|
|
51
|
+
❌
|
|
52
|
+
```csharp
|
|
53
|
+
string formatted = $"Date: {dt:R}";
|
|
54
|
+
destination.Write(formatted);
|
|
55
|
+
```
|
|
56
|
+
✅
|
|
57
|
+
```csharp
|
|
58
|
+
Span<char> buffer = stackalloc char[64];
|
|
59
|
+
buffer.TryWrite($"Date: {dt:R}", out int charsWritten);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Impact: Zero heap allocation for formatting operations.**
|
|
63
|
+
|
|
64
|
+
### Use Span.Split() for Zero-Allocation Splitting
|
|
65
|
+
🟡 **DO** use `MemoryExtensions.Split` for allocation-free string splitting | .NET 9+
|
|
66
|
+
|
|
67
|
+
❌
|
|
68
|
+
```csharp
|
|
69
|
+
string[] parts = input.Split(',');
|
|
70
|
+
```
|
|
71
|
+
✅
|
|
72
|
+
```csharp
|
|
73
|
+
foreach (Range range in input.AsSpan().Split(','))
|
|
74
|
+
{
|
|
75
|
+
ReadOnlySpan<char> segment = input.AsSpan(range);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Impact: 208 bytes → 0 bytes per split, 2x faster.**
|
|
80
|
+
|
|
81
|
+
### Use UTF8 String Literals (u8 suffix)
|
|
82
|
+
🟡 **DO** use the `u8` suffix for compile-time UTF8 `ReadOnlySpan<byte>` | .NET 7+
|
|
83
|
+
|
|
84
|
+
❌
|
|
85
|
+
```csharp
|
|
86
|
+
byte[] header = Encoding.UTF8.GetBytes("Content-Type");
|
|
87
|
+
```
|
|
88
|
+
✅
|
|
89
|
+
```csharp
|
|
90
|
+
ReadOnlySpan<byte> header = "Content-Type"u8;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Impact: 17ns → 0.006ns — eliminates runtime transcoding entirely.**
|
|
94
|
+
|
|
95
|
+
### Use ReadOnlySpan\<char\> Pattern Matching with switch
|
|
96
|
+
🟡 **DO** use `switch` on `ReadOnlySpan<char>` for allocation-free string matching | C# 11+
|
|
97
|
+
|
|
98
|
+
❌
|
|
99
|
+
```csharp
|
|
100
|
+
switch (attr.Value.Trim()) { case "preserve": /* ... */ break; }
|
|
101
|
+
```
|
|
102
|
+
✅
|
|
103
|
+
```csharp
|
|
104
|
+
switch (attr.Value.AsSpan().Trim())
|
|
105
|
+
{
|
|
106
|
+
case "preserve": return Preserve;
|
|
107
|
+
case "default": return Default;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Impact: Eliminates string allocation from Trim() in switch-based dispatch.**
|
|
112
|
+
|
|
113
|
+
### Use params ReadOnlySpan\<T\> to Eliminate Array Allocations
|
|
114
|
+
🟡 **DO** add `params ReadOnlySpan<T>` overloads to library methods | C# 13 / .NET 9+
|
|
115
|
+
|
|
116
|
+
❌
|
|
117
|
+
```csharp
|
|
118
|
+
public static void Log(params string[] messages) { /* ... */ }
|
|
119
|
+
Log("Starting", "Processing", "Done");
|
|
120
|
+
```
|
|
121
|
+
✅
|
|
122
|
+
```csharp
|
|
123
|
+
public static void Log(params ReadOnlySpan<string> messages) { /* ... */ }
|
|
124
|
+
Log("Starting", "Processing", "Done");
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Impact: Eliminates params array allocation. E.g., Path.Join with 5+ segments saves 64 bytes per call.**
|
|
128
|
+
|
|
129
|
+
### Avoid Chained String-Returning Operations
|
|
130
|
+
🟡 **AVOID** chains of 3+ string-returning method calls that each allocate intermediates | .NET Core+
|
|
131
|
+
|
|
132
|
+
**Pattern 1: Chained .Replace() calls**
|
|
133
|
+
|
|
134
|
+
❌
|
|
135
|
+
```csharp
|
|
136
|
+
string result = input.Replace("a", "b").Replace("c", "d").Replace("e", "f");
|
|
137
|
+
```
|
|
138
|
+
✅
|
|
139
|
+
```csharp
|
|
140
|
+
var sb = new StringBuilder(input.Length);
|
|
141
|
+
// single pass replacing all patterns
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Pattern 2: Chained Regex.Replace() calls**
|
|
145
|
+
|
|
146
|
+
❌
|
|
147
|
+
```csharp
|
|
148
|
+
public static string Underscore(this string input) =>
|
|
149
|
+
Regex3.Replace(Regex2.Replace(Regex1.Replace(input, "$1_$2"), "$1_$2"), "_").ToLower();
|
|
150
|
+
```
|
|
151
|
+
✅
|
|
152
|
+
```csharp
|
|
153
|
+
return string.Create(totalLength, state, (span, s) => { /* write directly */ });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Pattern 3: += string concatenation in loops**
|
|
157
|
+
|
|
158
|
+
❌
|
|
159
|
+
```csharp
|
|
160
|
+
string result = "";
|
|
161
|
+
foreach (var part in parts)
|
|
162
|
+
result += separator + part;
|
|
163
|
+
```
|
|
164
|
+
✅
|
|
165
|
+
```csharp
|
|
166
|
+
var sb = new StringBuilder();
|
|
167
|
+
foreach (var part in parts)
|
|
168
|
+
sb.Append(separator).Append(part);
|
|
169
|
+
return sb.ToString();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Impact: Eliminates N-1 intermediate string allocations per chain. For `+=` in loops, eliminates O(n²) total allocation.**
|
|
173
|
+
|
|
174
|
+
### Cache char.ToString() for Known Character Sets
|
|
175
|
+
🟡 **DO** cache `char.ToString()` results when the set of characters is small and known | .NET Core+
|
|
176
|
+
|
|
177
|
+
❌
|
|
178
|
+
```csharp
|
|
179
|
+
return symbol.ToString();
|
|
180
|
+
|
|
181
|
+
foreach (var prefix in UnitPrefixes)
|
|
182
|
+
input = input.Replace(prefix.Value.Name, prefix.Key.ToString());
|
|
183
|
+
```
|
|
184
|
+
✅
|
|
185
|
+
```csharp
|
|
186
|
+
private static readonly FrozenDictionary<char, string> s_charStrings =
|
|
187
|
+
new Dictionary<char, string>
|
|
188
|
+
{
|
|
189
|
+
['k'] = "k", ['M'] = "M", ['G'] = "G",
|
|
190
|
+
}.ToFrozenDictionary();
|
|
191
|
+
|
|
192
|
+
return s_charStrings[symbol];
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Impact: Eliminates one string allocation per char.ToString() call. Significant when called in loops or on hot paths.**
|
|
196
|
+
|
|
197
|
+
## Detection
|
|
198
|
+
|
|
199
|
+
Scan recipes for memory and string anti-patterns. Run these and report exact counts.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# .ToLower()/.ToUpper() without culture parameter (allocates + culture-sensitive)
|
|
203
|
+
grep -rn --include='*.cs' -E '\.(ToLower|ToUpper)\(\)' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
204
|
+
|
|
205
|
+
# Chained .Replace( calls (3+ on one line — intermediate string allocations)
|
|
206
|
+
grep -rn --include='*.cs' '\.Replace(.*\.Replace(.*\.Replace(' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
207
|
+
|
|
208
|
+
# params in method signatures (array allocation per call)
|
|
209
|
+
grep -rn --include='*.cs' 'params ' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
210
|
+
|
|
211
|
+
# LINQ on strings — .All/.Any on IEnumerable<char> (replace with foreach loop)
|
|
212
|
+
grep -rn --include='*.cs' -E '\.(All|Any)\(char\.' --exclude-dir=bin --exclude-dir=obj . | wc -l
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Patterns Requiring Manual Review
|
|
216
|
+
|
|
217
|
+
- **Boxing via string.Format**: Can't determine argument types from grep — needs type analysis
|
|
218
|
+
- **`+=` string concatenation in loops**: `+=` matches all types (int, list, event, string) — needs type context to confirm string
|
|
219
|
+
- **`char.ToString()`**: Requires knowing the variable type is `char` — not reliably greppable
|