{"slug": "best-practices-for-using-ai-to-generate-c-code", "title": "Best practices for using AI to generate C# code", "summary": "AI-powered tools like GitHub Copilot, Claude Code, and Cursor AI are increasingly used by .NET developers to generate C# code, but experts warn that generated code may contain logic errors, bugs, or security vulnerabilities. Best practices include precise prompt engineering, treating AI as a junior assistant, thorough code review and testing, and never compromising quality for speed.", "body_md": "AI-powered software development tools integrate with your IDE and codebase, helping you to write, refactor, and fix code faster. These tools also make it fast and easy to create and run unit tests and integration tests — tasks that take more time when done manually.\n\nToday, .NET developers often use [GitHub Copilot](https://www.infoworld.com/article/3609013/github-copilot-everything-you-need-to-know.html), [Claude Code](https://www.infoworld.com/article/4136718/claude-code-is-blowing-me-away.html), Cursor AI, and even AI chatbots like ChatGPT to generate code. In this article, we’ll cover some best practices you should follow when using AI to generate your C# code.\n\nWhile AI can write code for you, often the generated code does not work as intended. AI may generate code that contains logic errors, bugs, or security vulnerabilities, or code that doesn’t conform to your organization’s coding conventions or quality standards, or code that isn’t compatible with existing architecture. Further, AI may generate code that runs slowly or fails to run at all.\n\nThese are some of the key challenges organizations face when using AI-generated code in production:\n\nHere are some of the best practices you should follow when writing code using AI-powered tools:\n\nTo get the best use of AI-assisted coding tools, you should be proficient in prompt engineering. Your prompts should be specific, concise, and contain relevant code examples to enable your AI-powered tools to generate code that is functional, meets the requirements, and conforms to the standards and guidelines. Most importantly, you should plan precisely on the architecture and design, the exact solution you need, the structure of the codebase, and the coding and design guidelines to follow.\n\nYou should always treat AI as a peer programmer and your (junior) coding assistant. You should always review code the AI generates for you, run tests, and perform audits to validate correctness, conformance to guidelines and standards, performance and scalability bottlenecks, and security vulnerabilities. Based on the outcome of the audit, you should refactor your AI-generated code accordingly. And, repeat this cycle iteratively — audit followed by refactoring (if required) — until you are satisfied with the code.\n\nYour application source code should be performant, scalable, secure, extendable, and easy to comprehend and maintain. One of the biggest challenges of using AI-generated code is ensuring it meets requirements and conforms to the guidelines and standards of your organization without compromising on performance, scalability, and security.\n\nAI can generate code for you quite quickly, but the onus is on you to understand how the code works, investigate it for any flaws, test it thoroughly, and change it if and when it is needed. You must be sure to understand the code in its entirety. Unless you comprehend the code, you will never be able to improve or extend it when you need to.\n\nAnd you must never compromise quality for speed. If you use AI as a shortcut, your code may fail when deployed to the production environment — and that would be a disaster.\n\nThe code your AI-powered tool generates for you will be more useful to you if you’ve provided the right context. You should provide your AI coding tool with comprehensive, up-front information, such as architecture docs, coding standards, and relevant files, rather than just providing instructions using prompts. And you should add images or screenshots when specifying prompts to help your AI-powered tool better understand the context.\n\nYour AI-generated code must be testable for best results. It is always a good practice to specify tests at the time when your AI-enabled tool generates code, as tests can help AI understand the expected behavior and produce code that better aligns with your expectations. Additionally, you should specify the exact goal, the current and/or target technology stack, the relevant code boundaries, and the definition of “done”, i.e., the desired outcome.\n\nRemember, any AI-powered code generator is only as good as the input provided to it. This input is also known as the prompt. If the prompt you specify does not clearly state the objective, the generated code will not meet your requirements. Here is an example of a prompt that fails to consider performance and lacks clarity.\n\nGenerate code to create a Product DTO having fields Id, Name, and Price\n\nWhen I entered this prompt into the GitHub Copilot Chat window, the following piece of code was generated.\n\n```\npublic class Product\n{\n   public int Id { get; set; }\n   public string Name { get; set; } = string.Empty;\n   public decimal Price { get; set; }\n   public Product() { }\n   public Product(int id, string name, decimal price)\n   {\n       Id = id; Name = name; Price = price;\n   }\n}\n```\n\nTypically, a DTO (Data Transfer Object) should be created using records for improved performance instead of classes. Moreover, a DTO should be immutable by default, because its purpose is to store and pass data from the presentation layer to the business layer in an application. This not only guarantees thread safety but also prevents accidental changes to data and simplifies testability.\n\nNow, let’s change the prompt as shown below and try again.\n\nCreate an immutable Product DTO using C# that uses the record type, having fields Id, Name, and Price.\n\nWhen I entered the above prompt in GitHub Copilot Chat, a record type named ProductDto was created using a positional record as shown below.\n\n```\npublic sealed record ProductDto(int Id, string Name, decimal Price);\n```\n\nIn this next example, we’ll use GitHub Copilot within the Visual Studio IDE. With GitHub Copilot up and running in our IDE, you can specify the following prompt for creating a logging library using GitHub CoPilot within the Visual Studio IDE:\n\nCreate an asynchronous logger using .NET 10 and C# 14 that:\n\n- Stores logs asynchronously in a text file or a database\n- Uses a SQLite database for storing logs in a database\nThe log target should be configurable, i.e., the storage target of the generated log can be a file, a database, or etc.\n\nCreate a separate class for each log target, i.e., FileLogger for storing logs in a file and DbLogger for storing logs in the databas\n\nDave BerminghameIncorporate comprehensive error handling mechanism wherever applicable\n\nFigure 1 shows this prompt in GitHub Copilot (running in Visual Studio) and the files that Copilot generated for the project.\n\nFoundry\n\nOnce you have provided the prompt as input to Github Copilot, it will parse the input and generate several files in your project. Figure 2 shows the two projects in the Solution Explorer window — the console application project and the `AsyncLogger`\n\nclass library project.\n\nFoundry\n\nThe `AsyncLoggerService`\n\nclass uses the `System.Threading.Channel`\n\nstatic class to write logs of type `LogEntry`\n\nin the log target, which can be a text file or a database. The `System.Threading.Channel`\n\nclass contains two methods to create channels, the `CreateBounded`\n\nand the `CreateUnbounded`\n\nmethods.\n\nWhile `CreateBounded`\n\nis used to create a channel that holds a finite number of messages, `CreateUnbounded`\n\nis used to create a channel with unlimited capacity. You can learn more about working with `System.Threading.Channel`\n\nfrom my earlier article [here](https://www.infoworld.com/article/2263338/how-to-use-systemthreadingchannels-in-net-core.html).\n\nAlthough GitHub Copilot will generate the complete source code of the `AsyncLogger`\n\nlibrary for you, you should carefully examine — and thoroughly test — the generated code before you use it in production. For example, when I submitted the above prompt to Copilot, the code generated included three issues that needed to be addressed. Let’s take a look.\n\nIn the `AsyncLoggerService`\n\nclass, GitHub Copilot included the following code that uses an `Unbounded`\n\nchannel.\n\n```\n_channel = Channel.CreateUnbounded(\n    new UnboundedChannelOptions { SingleReader = true, SingleWriter = false });\n```\n\nThere is a major flaw in this approach. If this code were used in production, memory consumption could surge dramatically under burst traffic (say, 10k or more requests per second). This growth in memory usage could result in GC pressure and eventually a crash of the application.\n\nA better approach is to use a bounded channel with an explicit backpressure strategy, as shown in the code snippet given below.\n\n```\n_channel = Channel.CreateBounded(new BoundedChannelOptions(10_000)\n{\n    FullMode = BoundedChannelFullMode.DropWrite // or Wait\n});\n```\n\nIn the `LogAsync`\n\nmethod, GitHub Copilot included the following statement that contains a fire-and-forget call with no retries and no information if the write operation fails (i.e., if the channel is already closed).\n\n```\n_channel.Writer.TryWrite(entry);\n```\n\nA better approach is to include a fallback path as shown in the code snippet below.\n\n```\nif (!await _channel.Writer.WaitToWriteAsync())\n{\n    TryWriteFallback(\"Channel closed or unavailable\");\n    return;\n}\nawait _channel.Writer.WriteAsync(entry);\nprivate void TryWriteFallback(string text)\n{\n    try\n    {\n        var path = _config.FallbackFilePath ?? \"fallback-errors.log\";\n        var dir = Path.GetDirectoryName(path);\n        if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) \n            Directory.CreateDirectory(dir);\n        File.AppendAllText(path, $\"[{DateTime.UtcNow:o}] {text}{Environment.NewLine}\");\n    }\n    catch\n    {\n        // swallow - nothing else we can do\n    }\n}\n```\n\nThe `WaitToWriteAsync`\n\nmethod returns true if space is available to write an item, false otherwise. Hence, if no space is available, the `TryWriteFallback`\n\nmethod will be called and the log written to the fallback-errors.log file.\n\n**Using Sync over Async in a Constructor**\n\nFinally, GitHub Copilot included the following piece of code in the constructor of the `AsyncLoggerService`\n\nclass.\n\n```\n_target.InitializeAsync(_cts.Token).GetAwaiter().GetResult();\n```\n\nUsing sync over async in a constructor in C# is considered an anti-pattern. The reason is because constructors cannot be asynchronous, i.e., you cannot mark a constructor as asynchronous using the `async`\n\nkeyword. As a result, you will have to make blocking calls and wait for your asynchronous code to complete execution. And this could result in thread starvation and a deadlock.\n\nA better alternative will be to move the initialization code out of the constructor as shown below.\n\n```\npublic async Task InitializeAsync()\n{\n    await _target.InitializeAsync(_cts.Token);\n}\n```\n\nYou should verify each item of the following checklist before you integrate AI-generated code into your application.\n\nAI can help you create all of your boilerplate code, provide suggestions for best practices, and greatly speed up your exploration and research efforts. However, you should remember that AI is not a replacement for human intelligence, experience, and innovation. You should treat your AI-powered coding tool as your coworker or assistant and not your replacement.\n\nYou should take advantage of AI to do all of the tedious, monotonous work so that you can concentrate on the architecture, innovation, and other aspects of software architecture and development that require human involvement. You can take advantage of AI to generate your application’s architecture and design as well. However, the generated architecture and design should be for your reference only — it is entirely on you to decide how much of it you should use and what you need to replace.\n\nHere’s the final word: AI-powered coding tools will help you when you provide them with the correct context and clear instructions. Be sure to review the generated code carefully, and test thoroughly before deploying to production. Expect your AI coding tool to make mistakes, and be prepared to make changes (perhaps over many iterations) to get the performant, reliable, secure, and maintainable code that you need.", "url": "https://wpnews.pro/news/best-practices-for-using-ai-to-generate-c-code", "canonical_source": "https://www.infoworld.com/article/4161616/how-to-use-ai-to-write-good-c-sharp-code.html", "published_at": "2026-07-02 09:00:00+00:00", "updated_at": "2026-07-04 02:22:34.801699+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "developer-tools", "large-language-models", "generative-ai"], "entities": ["GitHub Copilot", "Claude Code", "Cursor AI", "ChatGPT", ".NET"], "alternates": {"html": "https://wpnews.pro/news/best-practices-for-using-ai-to-generate-c-code", "markdown": "https://wpnews.pro/news/best-practices-for-using-ai-to-generate-c-code.md", "text": "https://wpnews.pro/news/best-practices-for-using-ai-to-generate-c-code.txt", "jsonld": "https://wpnews.pro/news/best-practices-for-using-ai-to-generate-c-code.jsonld"}}