AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Dotnet Performance Review

skill-tunahanaliozturk-secure-dotnet-skills-dotnet-performance-review · by tunahanaliozturk

Use when reviewing .NET code for performance — allocations and GC pressure, async/IO misuse, hot-path LINQ, repeated enumeration, string handling, caching, and serialization.

No reviews yet
0 installs
22 views
0.0% view→install

Install

$ agentstack add skill-tunahanaliozturk-secure-dotnet-skills-dotnet-performance-review

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-tunahanaliozturk-secure-dotnet-skills-dotnet-performance-review)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Dotnet Performance Review? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

.NET Performance Review

Directs the agent to walk .NET / ASP.NET Core code through concrete performance lenses — allocations, async/IO, enumeration, caching, and serialization — identifying high-impact issues and recommending measurement before any micro-optimization.

When to use

  • A request handler, background service, or library method is reported as slow or high-allocation under load.
  • A PR adds or changes LINQ queries, serialization, caching, or HTTP client usage in a hot path.
  • A profiler or BenchmarkDotNet run has flagged a method as a hotspot and the team needs a structured review.
  • Code is being hardened for production throughput before a load test or capacity review.

Process

  1. Identify hot paths. Find the methods called most often or under the most load — request handlers, inner loops, serialization boundaries, and background job loops. Scope the review to those paths first; premature optimization of cold code wastes time.
  2. Audit allocations and GC pressure. Walk each hot path for unnecessary heap allocations: string concatenation in loops, boxing of value types, closure capture, LINQ chains materializing intermediate collections, and params array creation.
  3. Audit async and I/O usage. Check for sync-over-async calls (.Result / .Wait()), async void methods outside event handlers, missed parallelism opportunities (Task.WhenAll), and large result sets that should stream via IAsyncEnumerable.
  4. Audit enumeration and LINQ. Look for multiple enumeration of the same IEnumerable, needless .ToList() / .ToArray() materializations used only to call .Count, and LINQ expressions that force client-side evaluation or generate N+1 queries.
  5. Audit caching and object reuse. Check whether expensive repeated computations (serializer options, compiled regexes, HTTP responses) are computed fresh on every request or properly reused via IMemoryCache, HybridCache, or static initialization.
  6. Prioritize by impact and recommend measuring before optimizing. Rank findings by likely throughput or latency impact. For any finding where the gain is uncertain, require a BenchmarkDotNet micro-benchmark or a profiler trace (dotnet-trace, PerfView, Visual Studio Diagnostic Tools) before investing in the fix. Avoid claiming wins without measurement.

.NET / Azure checks

  • String allocations in loops. string result += item; inside a loop creates a new string object on every iteration — O(n²) allocations. Replace with StringBuilder for imperative concatenation, or use interpolated-string handlers (C# 10+) with StringBuilder.AppendInterpolatedStringHandler for mixed format strings. In hot serialization paths, write to an IBufferWriter instead of a string intermediate.
  • Boxing of value types. Casting a struct, int, Guid, DateTime, or enum to object or a non-generic interface (IComparable, IFormattable) allocates a heap box. Common sources: adding value types to ArrayList, passing to string.Format's object[] params, using non-generic collections, or calling virtual methods on interfaces through a boxed value. Prefer generic collections and constrained generics.
  • Span / Memory / ArrayPool for buffers. Methods that slice, parse, or transform byte or char data should operate on Span (stack-only) or Memory (heap-friendly) rather than allocating sub-arrays. For temporary buffers (e.g., encode/decode scratch space), rent from ArrayPool.Shared and return in a finally block rather than allocating new byte[n] on every call.
  • Needless .ToList() / .ToArray() materialization. Calling .ToList() only to call .Count on the result, or .ToArray() to pass to a method that accepts IEnumerable, forces full materialization without benefit. Use .Count() directly on IQueryable to push the count to the database, or accept IEnumerable at the call site and avoid materializing until necessary.
  • Multiple enumeration of IEnumerable. Enumerating the same IEnumerable more than once (.Count() then foreach, or two separate Where / Any calls on a deferred query) executes the underlying query or iterator twice. If the sequence is deferred (a LINQ query, a yield-return, or an EF Core IQueryable), materialize it once with .ToList() or .ToArray() and reuse. For IQueryable, prefer projecting the count and results in a single round-trip where the ORM supports it.
  • Sync-over-async: .Result / .Wait(). Calling .Result or .Wait() on a Task in an environment with a synchronization context (ASP.NET Core on older hosting, or any code ultimately marshaled back to a captured context) can deadlock. Even where deadlock does not occur, it wastes a thread-pool thread blocking synchronously. Go async all the way: await task instead of task.Result. Note: GetAwaiter().GetResult() carries the same risk. The deadlock is context-dependent — ASP.NET Core's default context does not deadlock the way classic ASP.NET did — but blocking still wastes threads under load.
  • async void outside event handlers. async void methods swallow exceptions (they are raised on the thread-pool and crash the process or disappear silently). They also cannot be awaited by callers. Use async Task for all async methods except event handlers (+= subscriptions) where the delegate signature requires void.
  • IAsyncEnumerable for streaming results. Methods that produce large result sets (database cursors, external API pages, file streams) should return IAsyncEnumerable and be consumed with await foreach rather than buffering into a List and returning. This keeps peak memory bounded and reduces time-to-first-byte latency for the caller.
  • Task.WhenAll for independent I/O. Sequential await call1; await call2; await call3; where the calls are independent serializes I/O unnecessarily. Use Task.WhenAll(call1, call2, call3) (or Task.WhenAll over a projected sequence) to fan out and await all completions concurrently. Be aware of the shared DbContext constraint: a single DbContext is not thread-safe for concurrent operations.
  • IHttpClientFactory vs new HttpClient() per call. Constructing new HttpClient() per request (or per method call) exhausts socket connections — the underlying SocketsHttpHandler is not reused, so old connections linger in TIME_WAIT. Register typed or named clients via builder.Services.AddHttpClient() and inject IHttpClientFactory or the typed client; the factory manages handler lifetime and connection pooling.
  • IMemoryCache / HybridCache for expensive repeated work. Repeated calls to external APIs, database lookups, or CPU-heavy computations that return the same result within a time window should be cached. Use IMemoryCache (in-process) or HybridCache (.NET 9+, two-tier with distributed backing) rather than a static Dictionary or per-request recalculation. For high-concurrency scenarios, use GetOrCreateAsync with a factory to avoid cache-stampede (multiple threads computing the same value simultaneously).
  • Reuse JsonSerializerOptions and use System.Text.Json source generation. new JsonSerializerOptions { ... } on every serialize/deserialize call causes reflection-based metadata to be compiled on each construction — this is both slow and allocation-heavy. Create a single static readonly JsonSerializerOptions instance (or register it via AddJsonOptions in ASP.NET Core). For maximum throughput in hot paths, use System.Text.Json source generation ([JsonSerializable] + JsonSerializerContext) to eliminate runtime reflection entirely.

Red flags

| Signal | Why it matters | |--------|----------------| | .Result or .Wait() in a request handler or service | Blocks a thread-pool thread synchronously; can deadlock in contexts with a synchronization context; wastes throughput under load. Go async all the way. | | new HttpClient() constructed per request or per method call | No connection pooling — each instance opens new TCP connections that linger in TIME_WAIT, exhausting ephemeral ports under moderate traffic. Use IHttpClientFactory. | | string result += item inside a loop | Allocates a new string on every concatenation — O(n²) total allocation. Use StringBuilder or interpolated-string handlers. | | IEnumerable enumerated more than once (e.g., .Count() then foreach) | Executes the underlying query or iterator twice; for EF Core IQueryable this means two round-trips to the database. Materialize once. | | new JsonSerializerOptions(...) inside a method | Triggers reflection-based metadata compilation on every call. Use a static readonly instance or ASP.NET Core's registered options. | | async void on a method that is not a UI/event handler | Exceptions are unobserved and crash or silently disappear; the method cannot be awaited. Return Task instead. | | .ToList() called only to use .Count on the result | Materializes the full sequence needlessly. Call .Count() on IQueryable (pushes to the DB) or .Any() when only presence is needed. | | new byte[size] allocated on every call for a scratch buffer | Creates GC pressure proportional to request rate. Rent from ArrayPool.Shared and return in finally. | | Sequential await of independent I/O calls | Serializes work that could run in parallel. Replace with await Task.WhenAll(...) for independent tasks. | | Missing IMemoryCache / HybridCache for a known hot lookup | Every request recomputes or re-fetches data that does not change per call. Cache with a TTL and a stampede guard. |

Example

See [examples/dotnet-performance-review/](../../examples/dotnet-performance-review/).

Related skills

  • [ef-core-review](../ef-core-review/SKILL.md) — use for deep EF Core query performance (N+1, tracking, projections).
  • [resilience-review](../resilience-review/SKILL.md) — use to review timeouts and retry policies on downstream calls that affect throughput.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.