AI's Promise vs. My Reality: The .NET Dev Stack I Actually Use
I'd heard the buzz about AI coding assistants replacing developers, and honestly, for a while, I bought into the hype. But what I’ve found after months of trying to integrate every shiny new AI into my .NET 9 projects is that the real game-changer isn't replacement, but intelligent augmentation. My personal journey has been less about delegating entire features to an LLM and more about finding that sweet spot where a few specific tools amplify my existing skills, especially when wrestling with legacy C# 13 codebases.
The Copilot I Never Expected
My initial foray into the world of dotnet ai tools started, like many, with GitHub Copilot. Honestly, I expected it to be a novelty, good for trivial boilerplate or maybe filling out a switch statement. What I ended up finding was that its real strength, for me, lies in its ability to act as a hyper-aware rubber duck. When I'm in Visual Studio 2026 or Rider 2026 and get stuck on a method signature or a common pattern, Copilot often has the right suggestion before I even finish typing.
Where it truly shines for me is in generating unit tests or simple CRUD operations, especially when there's an existing codebase to learn from. However, I learned the hard way that over-reliance can introduce subtle bugs, particularly with complex async/await patterns or intricate LINQ queries in C# 13. My rule now is: if it's more than a few lines, I treat Copilot's suggestion as a draft, not gospel.
// In a .NET 9 Web API controller, Copilot often nails the boilerplate
[HttpPost("products")]
public async Task<ActionResult<Product>> CreateProduct([FromBody] Product product)
{
// Copilot frequently suggests this entire block based on existing repository patterns
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
await _productRepository.AddAsync(product);
return CreatedAtAction(nameof(GetProductById), new { id = product.Id }, product);
}
Claude and Cursor: My Pair-Programming Power-Up
For deeper architectural questions, complex refactoring, or simply understanding a gnarly piece of legacy code, my developer ai stack leans heavily on Claude Opus 4.7, accessed primarily through Cursor 0.42+. I'm still figuring out the optimal way to prompt, but what I've found is that Claude excels at explaining why a particular piece of code might be problematic or suggesting more robust patterns.
One specific scenario where this combination saved me an embarrassing amount of time was debugging a tricky ConcurrentDictionary race condition. I'd tried to implement an async cache GetOrAdd pattern myself, only to hit subtle deadlocks. Feeding the problematic C# 13 snippet to Claude via Cursor, leveraging its Model Context Protocol (MCP) awareness, yielded a robust solution. It took a few iterations, but the detailed explanation of Lazy<Task<T>> was a game-changer.
// Prompt to Claude Opus 4.7 via Cursor 0.42+
Explain the potential pitfalls and thread-safety concerns of this .NET 9 C# 13 code snippet, particularly related to the concurrent modification of the `_cache` dictionary with an async factory. Suggest a safer pattern.
csharp
public class MyService
{
private readonly ConcurrentDictionary _cache = new();
public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> factory)
{
if (_cache.TryGetValue(key, out var cachedValue))
{
return (T)cachedValue;
}
var newValue = await factory(); // Potential for multiple threads to execute factory
_cache.TryAdd(key, newValue);
return newValue;
}
}
csharp
// Claude Opus 4.7 Response (abbreviated for brevity)
You've correctly identified a race condition. The primary issue with your proposed GetOrAddAsync is that ConcurrentDictionary.GetOrAdd expects a synchronous factory function. Directly passing an async lambda won't properly cache the result of the Task, only the Task object itself, leading to potential re-execution or incomplete caching.
To properly handle asynchronous factory functions and ensure single execution of the factory across multiple threads, I typically lean on a pattern involving Lazy<Task<T>> or an AsyncLazy<T> implementation. This ensures the asynchronous operation is performed once and its result is safely cached.
public class MyService
{
// Store Lazy<Task<object>> to ensure the async factory runs only once
private readonly ConcurrentDictionary<string, Lazy<Task<object>>> _cache = new();
public async Task<T> GetOrAddAsync<T>(string key, Func<Task<T>> factory)
{
var lazyTask = _cache.GetOrAdd(key, k => new Lazy<Task<object>>(async () =>
{
// The factory is executed inside the Lazy, ensuring single execution
var result = await factory();
return result;
}, LazyThreadSafetyMode.ExecutionAndPublication)); // Critical for thread safety
return (T)await lazyTask.Value; // Await the result of the Lazy<Task<object>>
}
}
Visual Studio + Copilot Edits: The IDE's New Brain
The biggest shift in my daily coding rhythm has been the tight integration of AI directly into my IDE. Visual Studio 2026, with its latest Copilot Edits and Copilot for Workspaces features, has become an indispensable part of my best ai for csharp toolkit. It's not just auto-completion anymore; it's proactive refactoring and code generation right where I need it.
For quick, localized transformations, Copilot Edits is fantastic. I can highlight a for loop and prompt it to "Refactor this to use LINQ," and it usually gets it right on the first try. It handles mundane tasks like extracting methods, renaming variables consistently, or even adding XML documentation comments based on context. I'm still figuring out how to make Copilot for Workspaces truly effective for larger, multi-file refactors across our sprawling monorepo, but for focused, in-file changes, it's a significant time-saver.
// Scenario: Using Copilot Edits in VS 2026
// Original code selected by me:
// var activeUsers = new List<User>();
// foreach (var user in allUsers)
// {
// if (user.IsActive && user.LastLogin > DateTime.UtcNow.AddDays(-30))
// {
// activeUsers.Add(user);
// }
// }
// return activeUsers.OrderBy(u => u.Username).ToList();
// My prompt to Copilot Edits: "Refactor this to use LINQ for filtering and ordering."
// Copilot Edits output:
return allUsers
.Where(user => user.IsActive && user.LastLogin > DateTime.UtcNow.AddDays(-30))
.OrderBy(user => user.Username)
.ToList();
So, where am I now? My current developer AI stack isn't about replacing me, but about making me a faster, more informed .NET developer. I'm still figuring out how to leverage Copilot for Workspaces effectively for cross-project context, especially with our monorepo setup. It feels like we're just scratching the surface of what's possible.
If you've managed to integrate dotnet ai tools into your daily workflow for complex refactoring or legacy codebase understanding, I'd genuinely love to hear about your specific wins and especially your failures. What broke for you, and how did you fix it?
Top comments (0)