Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
High-performance .NET guidance for writing and tuning C# apps and benchmarks (BenchmarkDotNet, ASP.NET, challenges). Use when asked to make .NET code faster, reduce allocations or GC overhead, fix lock contention or thread pool starvation, inspect JIT codegen, profile apps, or tune runtime/NativeAOT settings.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
실행 점수 미검증
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
[!WARNING] Much of the advice here might not age well: as .NET evolves, old hacks and custom knobs may become obsolete or even detrimental. Apply only after careful consideration and testing. Revisit periodically to ensure continued relevance.
This skill focuses on optimizing .NET applications for maximum performance, providing tips, techniques, and best practices to achieve blazing-fast execution times. It targets .NET 10.0 and 11.0. Older versions have not been tested.
Contents
Start cheap and go deeper only when needed: counters (what kind of bottleneck?) → traces (where?) → perf and JIT disassembly.
Install the tools with:
dotnet tool install -g dotnet-counters
dotnet tool install -g dotnet-trace
dotnet tool install -g dotnet-symbol
dotnet-counters is a low-overhead, first-level triage tool. Run it while the app is under load:
dotnet-counters monitor -p <pid> # System.Runtime counters by default
dotnet-counters monitor -p <pid> --counters System.Runtime,Microsoft.AspNetCore.Hosting,System.Net.Http
dotnet-counters collect -p <pid> --format csv -o counters.csv # save for post-processing
See the System.Runtime built-in metrics (.NET 9+) for what each counter means.
dotnet-trace collects EventPipe traces on any OS without root or a native profiler (managed frames only):
dotnet-trace collect -p <pid> --duration 00:00:00:10 # default profiles: dotnet-common + dotnet-sampled-thread-time
dotnet-trace collect -p <pid> --profile gc-verbose # GCs + sampled allocations: which types/stacks allocate
dotnet-trace collect -p <pid> --clrevents contention # lock contention events with stacks
dotnet-trace collect -- ./MyApp arg1 # trace from startup
dotnet-trace list-profiles
dotnet-trace report trace.nettrace topN -n 20 # top methods (exclusive) as plain text
dotnet-trace report trace.nettrace topN -n 20 --inclusive
dotnet-trace convert trace.nettrace --format Speedscope # open in https://www.speedscope.app
For more tips on using dotnet-trace, refer to the dotnet-trace collection skill.
perfThe instructions below are mostly for CoreCLR/JIT scenarios on Linux with the native perf tool. NativeAOT/R2R scenarios should presumably work as is, without perf inject.
Typically, the following environment variables need to be set in the context of the running application:
DOTNET_PerfMapEnabled=1
DOTNET_PerfMapStubGranularity=3
DOTNET_JitFramed=1
DOTNET_EnableWriteXorExecute=0
What to profile depends on the scenario. Typically it's steady-state performance, so wait until the process is properly warmed up (e.g. 1 minute), then attach and collect traces:
perf record -k 1 -g -F 3999 -p $PROCESS_PID -o perf.data -- sleep 5
Next, symbolize the collected JIT frames using perf inject:
perf inject --input perf.data --jit --output perfjit.data
Then use the resulting perfjit.data file as needed, e.g. to generate flame graphs or annotated asm:
# annotated asm & report:
perf report --input perfjit.data --no-children --percent-limit 1 --stdio > perf_report.txt
perf annotate --stdio2 -i perfjit.data --percent-limit 1 -M intel > perf.asm
# Generate flamegraph (svg)
git clone --depth 1 --quiet https://github.com/brendangregg/FlameGraph || true
perf script -i perfjit.data | ./FlameGraph/stackcollapse-perf.pl | ./FlameGraph/flamegraph.pl > flamegraph.svg
Now perf.asm contains the annotated assembly output for the hottest methods in your application. Keep in mind that sample attribution is imprecise ("skid"): the cost is often reported on an instruction shortly after the one that actually stalls (e.g. right after a slow load or division), so inspect the neighboring instructions too.
In case of missing symbols you may need to use dotnet-symbol to download the necessary debug symbols for .dll/.dylib/.so:
dotnet-symbol --symbols $PUBLISH_FOLDER/*.dll
dotnet-symbol --symbols $PUBLISH_FOLDER/*.so
All other perf-related techniques (e.g. c2c, branch profiling, cache misses) can be applied similarly, depending on the performance aspects you want to analyze.
For simple functions, create a self-contained console application that calls the function in an infinite loop. For BenchmarkDotNet, use the in-process toolchain (-i or [InProcess]) with a long iteration duration and a very large iteration count so it is clear which process to attach perf to.
These are diagnostic switches (not tuning knobs) and work with the shipped Release runtime:
DOTNET_JitDisasm="Test" # method list: 'Test', 'Program:Test', '*Test*', 'Program:*' (space-separated)
DOTNET_JitDisasmSummary=1 # list every jitted method with its tier
DOTNET_JitStdOutFile=/tmp/jit.txt # write JIT output to a file instead of stdout
DOTNET_JitDisasmDiffable=1 # stable output (no addresses) for before/after diffs
DOTNET_JitDisasmOnlyOptimized=1 # show only Tier-1/FullOpts code
^ might require MethodImplOptions.NoInlining on the method to ensure it gets JIT-compiled and not inlined away.
[!TIP] Set them only for the app process, not for
dotnet run, which would also dump the SDK's own methods.
; Tier1 code (or ; Tier1-OSR code / ; FullOpts code), ideally with ; optimized using Dynamic PGO. ; Tier0 code and ; Instrumented Tier0 code are unoptimized.<IlcArg Include="--codegenopt:JitDisasm=Test" /> and <IlcArg Include="--codegenopt:JitStdOutFile=jit.txt" />.[!IMPORTANT] Only safe C# code is considered in these best practices. Unsafe code is highly discouraged and not covered here.
JsonTypeInfo<T> to avoid reflection-based metadata discovery and enable supported serialization fast paths. Preserve existing serialization options and behavior.[LoggerMessage] source-generated logging to avoid template parsing, boxing, and temporary allocations. Still guard expensive argument computation with IsEnabled.[GeneratedRegex] for compile-time-known patterns instead of runtime pattern compilation. Preserve options, culture, and match timeouts.Span.CopyTo, Clear, Fill, SequenceEqual, and IndexOf already have specialized implementations, including SIMD where appropriate.static ReadOnlySpan<T> RVA => new T[CNS] for all kinds of immutable data (only primitives, e.g., long, byte, char) - the data will be embedded into metadata, and the JIT will be able to perform constant folding on it.TryParse(ReadOnlySpan<byte>, ...) and TryFormat(Span<byte>, ...) avoid intermediate strings and UTF-8/UTF-16 transcoding. Preserve culture and formatting semantics.SearchValues<char> / SearchValues<byte> for repeated searches against fixed sets; use SearchValues<string> for multi-substring searches. Construction has a cost, so do not create these per call.FrozenDictionary / FrozenSet for build-once, read-many data. Their construction cost must be amortized over enough lookups; preserve the intended comparer.dictionary.GetAlternateLookup<ReadOnlySpan<char>>() avoids allocating temporary string keys when the comparer supports it. Use TryGetAlternateLookup when comparer support is not known.IEquatable<T> and a good GetHashCode for struct dictionary/set keys. This can avoid expensive default equality paths, boxing, and collision-heavy hashes.TensorPrimitives before hand-vectorizing numeric operations. Its span-based implementations handle SIMD and tails; prefer it over manual SIMD loops or LINQ for things like finding min/max, sums, etc.[LibraryImport] over [DllImport] for supported signatures to generate marshalling at build time; both DllImport and LibraryImport require AllowUnsafeBlocks=true under unsafe-v2, but if interop is not avoidable, LibraryImport is generally preferred.static readonly flags for genuinely immutable process-wide configuration. Optimized JIT code can fold their values and eliminate disabled branches.var items = _items;). The JIT often can't do it for you: calls and stores through references may modify the field, and the memory model restricts hoisting reads. Locals also enable bounds check elimination for arrays/spans.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping for UTF-8 JSON-only responses (documentation). Never embed its raw output in HTML or <script>; any custom encoder disables STJ's generated serialization fast path, so benchmark rather than assuming the gains add up.BitOperations, BinaryPrimitives, BitConverter or static numeric helpers such as uint.Log2, uint.PopCount, uint.TrailingZeroCount, etc.MemoryExtensions such as SequenceEqual, IndexOf, IndexOfAnyExcept, LastIndexOf, StartsWith, CommonPrefixLength, and a lot more - prefer these APIs over manual loops or vectorization.AggressiveOptimization is usually a deoptimization.AggressiveInlining may hurt performance (e.g. by exhausting the inliner's time budget or increasing the number of tracked local variables).string.Compare, CompareTo, StartsWith(string), EndsWith(string), IndexOf(string), LastIndexOf(string), ToUpper(), ToLower(), and default string sorting (Array.Sort, List<T>.Sort, OrderBy, SortedDictionary/SortedSet with the default comparer). Pass StringComparison.Ordinal/OrdinalIgnoreCase or StringComparer.Ordinal; analyzers CA1307, CA1309 and CA1310 find these calls.==, Equals, Contains(string), Replace(string, string), IndexOf(char), Split, GetHashCode, and the default Dictionary/HashSet comparer.string.Equals(a, b, StringComparison.OrdinalIgnoreCase) instead of a.ToLower() == b.ToLower() (two allocations plus culture rules). For ASCII data, System.Text.Ascii has vectorized EqualsIgnoreCase, ToUpper/ToLower (span to span) and IsValid.Dictionary<string, T> / HashSet<string> use fast non-randomized hashing only with the default comparer, StringComparer.Ordinal or StringComparer.OrdinalIgnoreCase (falling back to randomized hashing only on heavy collisions). Any other comparer loses it.ReadOnlySpan<byte> directly (u8 literals, SearchValues<byte>, TryParse/TryFormat on bytes, Utf8.TryWrite) and convert to string only when needed.<InvariantGlobalization>true</InvariantGlobalization> removes the ICU dependency (faster startup, less memory) and makes culture-sensitive string operations ordinal. This changes behavior, so verify the output.Writes to one shared location from many threads (Interlocked.Increment on a global counter, a shared "total" field, etc.) serialize all of them on a single cache line, so throughput can get worse as cores are added. Split the state, update only the local part, and combine on read. Two options:
1. Per-core stripes - index an array by Thread.GetCurrentProcessorId() (cheap: cached and periodically refreshed). The id is only a hint: threads migrate, and ids can exceed ProcessorCount (hence the modulo), so keep Interlocked on the stripe - it's almost always uncontended. Fixed memory and allocation-free reads; works with any number of threads (thread pool, request handlers). Stripes must be padded, see False sharing.
sealed class StripedCounter
{
private readonly PaddedLong[] _stripes = new PaddedLong[Environment.ProcessorCount];
public void Increment()
{
PaddedLong[] stripes = _stripes;
Interlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length].Value);
}
// Not an atomic snapshot, which is fine for statistics.
public long Sum()
{
PaddedLong[] stripes = _stripes;
long sum = 0;
for (int i = 0; i < stripes.Length; i++)
sum += Volatile.Read(ref stripes[i].Value);
return sum;
}
}
2. ThreadLocal<T> with trackAllValues: true - each thread updates its own slot with no atomics at all; sum Values on read. Values allocates a list and includes values of threads that have already exited, and every thread that ever touched it keeps a slot, so this suits a bounded set of long-lived threads (e.g. Parallel workers) where writes vastly outnumber reads. Dispose the ThreadLocal<T> when done.
private readonly ThreadLocal<long> _count = new(trackAllValues: true);
void OnItem() => _count.Value++;
long Total() => _count.Values.Sum();
If the work structure allows it, aggregating into plain locals and combining once per task/partition (Parallel.For with localInit/localFinally) is cheaper than both.
Threads writing different variables still contend if those variables share a cache line (64 bytes on most x64/arm64 CPUs, 128 bytes on Apple M-series): every write invalidates the line in the other cores' caches. The naive striped counter is the classic case:
// 8 longs per 64-byte line: the per-core slots look independent, but cores keep stealing the line from each other.
long[] stripes = new long[Environment.ProcessorCount];
Interlocked.Increment(ref stripes[(uint)Thread.GetCurrentProcessorId() % (uint)stripes.Length]);
Fix it by padding each slot so no two hot values can land on the same line:
struct PaddedLong
{
public long Value;
#pragma warning disable CS0169 // padding only
private InlineArray8<long> _padding; // 64 bytes: adjacent Values are 72 bytes apart
#pragma warning restore CS0169
}
In .NET 11.0 the JIT got improvements in bounds check elimination and now recognizes the following shape for safely loading and processing multiple elements from an array/span:
while (data.Length >= CNS)
{
// JIT knows that the slice has at least CNS elements, so bounds checks can be elided.
// e.g.:
// VectorX.Create(data);
// BitConverter.ToInt32(data);
// BinaryPrimitives.ReadInt32LittleEndian(data);
// etc.
data = data.Slice(CNS);
}
For example, the following C# snippet has no bounds checks in .NET 11.0:
int Sum(Span<int> data)
{
Vector128<int> sum = default;
while (data.Length >= Vector128<int>.Count)
{
sum += Vector128.Create(data);
data = data.Slice(Vector128<int>.Count);
}
int result = Vector128.Sum(sum);
foreach (var t in data)
result += t;
return result;
}
.NET 12.0 (Preview 1 or main) extends it to more complex loop shapes (e.g. multiple spans):
static bool Equal(ReadOnlySpan<ushort> a, ReadOnlySpan<ushort> b)
{
if (a.Length != b.Length)
return false;
int i = 0;
if (Vector128.IsHardwareAccelerated)
{
for (; i <= a.Length - Vector128<ushort>.Count; i += Vector128<ushort>.Count)
{
var va = Vector128.Create(a.Slice(i));
var vb = Vector128.Create(b.Slice(i));
if (va != vb)
return false;
}
}
for (; i < a.Length; i++)
{
if (a[i] != b[i])
return false;
}
return true;
}
^ has no bounds checks for the main loop in .NET 12.0 (Preview 1).
[!TIP] This only illustrates the loop shape. For this exact task, just call
a.SequenceEqual(b).
More info about vectorization and SIMD in .NET can be found in the official documentation. Also consider using TensorPrimitives.
Static abstract interface members let you specialize a generic method for values supplied by struct tags, without duplicating its source code. The JIT generates separate native versions of Big<C42> and Big<C100>. In optimized code, it can inline the small Value getters and fold expressions and branches that use T.Value. This works even when Big<T> itself is too large to inline; The tradeoff is increased native code size. T.Value is not a C# compile-time constant: constant folding depends on JIT optimizations and getter inlining.
using System.Runtime.CompilerServices;
interface IConstant {
static abstract int Value { get; }
}
readonly struct C42 : IConstant {
public static int Value => 42;
}
readonly struct C100 : IConstant {
public static int Value => 100;
}
static class Example
{
static int Big<T>(int x) where T : struct, IConstant
{
int arg = T.Value;
// The large body would go here.
return arg == 42 ? x * arg : x + arg;
}
static int Use42(int x) => Big<C42>(x);
static int Use100(int x) => Big<C100>(x);
}
It is also handy for scenarios where you want to leverage different vector sizes or constants in a generic way, allowing the JIT to generate optimized code for each specialization without duplicating the method's source code. Example: summing an array using different vector widths with static abstract members.
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
int[] values = new int[1003];
for (int i = 0; i < values.Length; i++) values[i] = i + 1;
Console.WriteLine(Sum<Vector128<int>, Ops128>(values));
Console.WriteLine(Sum<Vector256<int>, Ops256>(values));
Console.WriteLine(Sum<Vector512<int>, Ops512>(values));
[MethodImpl(MethodImplOptions.NoInlining)]
static int Sum<V, TOps>(ReadOnlySpan<int> s)
where V : struct
where TOps : struct, IOps<V>
{
V acc = default;
int count = TOps.Count;
while (s.Length >= count)
{
acc = TOps.Add(acc, TOps.Load(s));
s = s.Slice(count);
}
int sum = TOps.Sum(acc);
foreach (int x in s) sum = unchecked(sum + x);
return sum;
}
interface IOps<V> where V : struct
{
static abstract int Count { get; }
static abstract V Load(ReadOnlySpan<int> s);
static abstract V Add(V a, V b);
static abstract int Sum(V v);
}
readonly struct Ops128 : IOps<Vector128<int>>
{
public static int Count => Vector128<int>.Count;
public static Vector128<int> Load(ReadOnlySpan<int> s) => Vector128.Create(s);
public static Vector128<int> Add(Vector128<int> a, Vector128<int> b) => a + b;
public static int Sum(Vector128<int> v) => Vector128.Sum(v);
}
readonly struct Ops256 : IOps<Vector256<int>>
{
public static int Count => Vector256<int>.Count;
public static Vector256<int> Load(ReadOnlySpan<int> s) => Vector256.Create(s);
public static Vector256<int> Add(Vector256<int> a, Vector256<int> b) => a + b;
public static int Sum(Vector256<int> v) => Vector256.Sum(v);
}
readonly struct Ops512 : IOps<Vector512<int>>
{
public static int Count => Vector512<int>.Count;
public static Vector512<int> Load(ReadOnlySpan<int> s) => Vector512.Create(s);
public static Vector512<int> Add(Vector512<int> a, Vector512<int> b) => a + b;
public static int Sum(Vector512<int> v) => Vector512.Sum(v);
}
Code-level advice for HTTP throughput benchmarks.
WebApplication.CreateSlimBuilder(args) (or CreateEmptyBuilder + builder.WebHost.UseKestrelCore()) and add only what's needed. Every middleware component (HTTPS redirection, auth, CORS, response compression, static files, developer exception page) costs something per request.appsettings.json that raises Microsoft.AspNetCore to Warning, the default level is Information and the host logs every request to the console - a massive bottleneck. Call builder.Logging.ClearProviders() or raise the level.builder.Services.ConfigureHttpJsonOptions(o => o.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default));. For hand-written responses, serialize with a reused Utf8JsonWriter (writer.Reset(response.BodyWriter)) instead of allocating one per request.[!WARNING] It is highly recommended to keep the default settings unless you have measured and understood the performance implications of changing them.
⭐ - known to have a visible impact on performance (typically, in synthetic benchmarks).
| Environment variable (CoreCLR Release, .NET 10.0+) | Default | Values to try | Purpose and trade-offs |
|---|---|---|---|
⭐DOTNET_SYSTEM_NET_SOCKETS_INLINE_COMPLETIONS | 0 | 1 | Run I/O continuations directly on poller threads instead of dispatching them to workers - beneficial for workloads with many small requests. ⚠️ Blocking or lengthy continuations can stall I/O ⚠️ |
⭐DOTNET_ThreadPool_UnfairSemaphoreSpinLimit | 70 in .NET 10.0; 1024 in current .NET 11.0 | 0, 0x400, 0xC00 | Trade worker wake latency against CPU spent spinning. 0 might be useful as a mitigation for high CPU usage (especially on arm64) at the cost of throughput. Large values might improve synthetic benchmark performance. |
⭐DOTNET_GCDynamicAdaptationMode | 1 | 0 | Disable DATAS for server GC. Can improve throughput at the expense of memory; compare against tuning DATAS (e.g. DOTNET_GCDGen0GrowthPercent) rather than disabling it. |
⭐DOTNET_HillClimbing_Disable | 0 | 1 | Disable ThreadPool hill climbing (improves time to steady state). |
DOTNET_SYSTEM_NET_SOCKETS_THREAD_COUNT | Automatic; processor count when inline completions are enabled | 1, 2, 4, 8, up to processor count | Tune I/O poller count. Fewer pollers can reduce scheduling and cache overhead; too few can serialize processing. Values are decimal. |
DOTNET_gcServer | varies by environment | 1 | Typically enabled by default for server workloads. Might improve the performance of desktop/console tools. |
DOTNET_GCDTargetTCP | Effective target 2% (configuration 0) | 1 | Lower DATAS's target throughput-cost percentage. Encourages more heaps when GC cost exceeds the target. Only relevant with DATAS enabled. |
DOTNET_GCDGen0GrowthPercent | Effective multiplier 100% (configuration 0) | 0xC8 | Scale DATAS's gen0 budget multiplier to 200% of its default. Trades memory for potentially fewer collections. Only relevant with DATAS enabled. |
DOTNET_TC_CallCountingDelayMs | 100 ms; longer on a single processor | 0 | Only for short-lived console tools where JIT warmup takes too long: remove the tiering delay while retaining the normal call-count threshold. Can increase compilation work early in execution. |
DOTNET_TieredCompilation | 1 | 0 | Alternative for short-lived console tools where JIT warmup takes too long: disable tiering and JIT optimized code on first use. Removes tiered warmup but disables dynamic PGO and can increase initial JIT cost. |
Recommended reading: Microsoft's Optimizing AOT deployments article, plus the runtime's CPU instruction-set tuning guide. The latter describes implementation-specific options that can change between SDK versions.
[!NOTE] These are publish-time MSBuild properties: republish for each experiment. NativeAOT has no JIT or tiered compilation, so the CoreCLR JIT/tiering environment variables above do not apply.
| MSBuild setting | Purpose and trade-offs |
|---|---|
-p:OptimizationPreference=Speed | Optimize for execution time (--Ot) instead of the default size/speed balance. A good throughput experiment, but can increase executable size and instruction-cache pressure. |
-p:OptimizationPreference=Size | Optimize for executable size (--Os). Useful when distribution size or memory footprint matters more than peak throughput; measure the speed trade-off. |
-p:IlcInstructionSet=native | Target instruction sets supported by the build machine's CPU. Useful for local benchmarks or homogeneous deployments; no cross-architecture compilation support, and the executable may not run on older CPUs. Use an explicit instruction-set baseline for heterogeneous deployments. |
-p:IlcInstructionSet=armv8.1-a | If IlcInstructionSet=native is not acceptable for you, consider explicitly raising the baseline from ARMv8.0-A to ARMv8.1-A to include LSE (crucial for atomics-heavy workloads). |
dotnet publish -c Release -r linux-arm64 -p:PublishAot=true -p:OptimizationPreference=Speed -p:IlcInstructionSet=native
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Route gh-aw workflow design/create/debug/upgrade requests to the right prompts.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Use a repo-root `.editorconfig` to configure free .NET analyzer and style rules. Use when a .NET repo needs rule severity, code-style options, section layout, or analyzer ownership made explicit. USE FOR: the repo needs a root .editorconfig; analyzer severity and style ownership are unclear; the team wants one source of truth for rule configuration. DO NOT USE FOR: choosing analyzers with no config change; formatting-only execution with no config ownership question. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Scans .NET code for ~50 performance anti-patterns across async, memory, strings, collections, LINQ, regex, serialization, and I/O with tiered severity classification. Use when analyzing .NET code for optimization opportunities, reviewing hot paths, or auditing allocation-heavy patterns.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Symbolicate the .NET runtime frames in an Android tombstone file. Extracts BuildIds and PC offsets from the native backtrace, downloads debug symbols from the Microsoft symbol server, and runs llvm-symbolizer to produce function names with source file and line numbers. USE FOR triaging a .NET MAUI or Mono Android app crash from a tombstone, resolving native backtrace frames in libmonosgen-2.0.so or libcoreclr.so to .NET runtime source code, or investigating SIGABRT, SIGSEGV, or other native signals originating from the .NET runtime on Android. DO NOT USE FOR pure Java/Kotlin crashes, managed .NET exceptions that are already captured in logcat, or iOS crash logs. INVOKES Symbolicate-Tombstone.ps1 script, llvm-symbolizer, Microsoft symbol server.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Symbolicate .NET runtime frames in Apple platform .ips crash logs (iOS, tvOS, Mac Catalyst, macOS). Extracts UUIDs and addresses from the native backtrace, locates dSYM debug symbols, and runs atos to produce function names with source file and line numbers. Automatically downloads .dwarf symbols from the Microsoft symbol server using Mach-O UUIDs. USE FOR triaging a .NET MAUI or Mono app crash from an .ips file on any Apple platform, resolving native backtrace frames in libcoreclr or libmonosgen-2.0 to .NET runtime source code, retrieving .ips crash logs from a connected iOS device or iPhone, or investigating EXC_CRASH, EXC_BAD_ACCESS, SIGABRT, or SIGSEGV originating from the .NET runtime. DO NOT USE FOR pure Swift/Objective-C crashes with no .NET components, or Android tombstone files. INVOKES Symbolicate-Crash.ps1 script, atos, dwarfdump, idevicecrashreport.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Design or review .NET solution architecture across modular monoliths, clean architecture, vertical slices, microservices, DDD, CQRS, and cloud-native boundaries without over-engineering. USE FOR: .NET architecture choices; layer and domain boundary review; service decomposition; clean architecture, vertical slice, DDD, CQRS, and modular monolith decisions. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.