├── .vscode ├── prompts │ ├── qa.prompt.md │ ├── pros-and-cons.prompt.md │ ├── new-idea.prompt.md │ └── code-review.prompt.md ├── mcp.json ├── settings.json ├── instructions │ ├── coding-guidelines.instructions.md │ ├── testing-xunit.instructions.md │ └── coding-style.instructions.md └── git-message.instructions.md ├── Directory.Build.props ├── .github └── agents │ ├── debug.agent.md │ └── research.agent.md ├── README.md ├── LICENSE └── .editorconfig /.vscode/prompts/qa.prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Q&A Session" 3 | --- 4 | 5 | # Definition 6 | 7 | Ask me a series of yes/no questions to better understand my needs and provide a more accurate recommendation. 8 | 9 | ## Constraints 10 | 11 | - Follow best practices, suggest best practices, and avoid common pitfalls. 12 | - Ensure all questions are relevant to the topic at hand. 13 | - Make questions clear and concise. 14 | - Ask questions in batches of 5. 15 | - Do not ask more than 5 questions at a time. 16 | - Present all questions in one batch. 17 | - Do not proceed without my answer. 18 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | All 5 | true 6 | true 7 | Nullable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.vscode/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": [ 3 | { 4 | "id": "memory_file_path", 5 | "type": "promptString", 6 | "description": "Path to the memory storage file (optional)", 7 | "password": false 8 | } 9 | ], 10 | "servers": { 11 | "github": { 12 | "type": "http", 13 | "url": "https://api.githubcopilot.com/mcp/" 14 | }, 15 | "context7": { 16 | "type": "stdio", 17 | "command": "npx", 18 | "args": [ 19 | "-y", 20 | "@upstash/context7-mcp@latest" 21 | ] 22 | }, 23 | "microsoft.docs.mcp": { 24 | "type": "http", 25 | "url": "https://learn.microsoft.com/api/mcp" 26 | }, 27 | "playwright": { 28 | "command": "npx", 29 | "args": [ 30 | "@playwright/mcp@latest" 31 | ] 32 | }, 33 | "memory": { 34 | "command": "npx", 35 | "args": [ 36 | "-y", 37 | "@modelcontextprotocol/server-memory" 38 | ], 39 | "env": { 40 | "MEMORY_FILE_PATH": "${input:memory_file_path}" 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[csharp]": { 3 | // "editor.defaultFormatter": "csharpier.csharpier-vscode" 4 | "editor.defaultFormatter": "ms-dotnettools.csharp" 5 | }, 6 | "chat.promptFiles": true, 7 | "chat.promptFilesLocations": { 8 | ".github/prompts": true, 9 | ".vscode/prompts": true 10 | }, 11 | "chat.instructionsFilesLocations": { 12 | ".github/instructions": true, 13 | ".vscode/instructions": true 14 | }, 15 | "github.copilot.chat.codeGeneration.instructions": [], 16 | "github.copilot.chat.commitMessageGeneration.instructions": [ 17 | { 18 | "file": ".vscode/git-message.instructions.md" 19 | } 20 | ], 21 | "github.copilot.chat.reviewSelection.enabled": true, 22 | "github.copilot.chat.reviewSelection.instructions": [ 23 | { 24 | "file": ".vscode/instructions/coding-guidelines.instructions.md" 25 | }, 26 | { 27 | "file": ".vscode/instructions/coding-style.instructions.md" 28 | } 29 | ], 30 | "github.copilot.chat.testGeneration.instructions": [ 31 | { 32 | "file": ".vscode/instructions/testing-xunit.instructions.md" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.vscode/prompts/pros-and-cons.prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Pros and Cons Analysis" 3 | --- 4 | 5 | # Definition: 6 | 7 | Analyze the proposed solution, focusing on its strengths and weaknesses. Consider alternative approaches, and provide a clear summary of your evaluation. 8 | 9 | ## Constraints 10 | 11 | - Ask follow up questions if needed to clarify the solution. 12 | - If any questions, wait for the user to respond before proceeding with the analysis. 13 | - Provide Pros and Cons in a bulleted list format. 14 | - Highlight best practices and common pitfalls, where relevant. 15 | - Suggest alternative solutions or improvements, if applicable. 16 | - Provide pros and cons for alternative solutions if applicable. Provide at least 3 pros and cons for each alternative solution. 17 | - Use provided emojis below to enhance readability 18 | - use ✅ to highlight pros in the list 19 | - use ❌ to indicate cons in the list 20 | - use ✨ to highlight best practice items in the list 21 | - use ☝️ to indicate common pitfalls. 22 | - Use 🔒 to highlight for security (InfoSec) related topics 23 | - End with a single-paragraph summary of your overall assessment. 24 | 25 | ## Structure 26 | 27 | - **Proposed Solution**: 28 | - **Description**: 29 | - **Pros**: 30 | - **Cons**: 31 | - **Best Practices**: 32 | - **Common Pitfalls**: 33 | 34 | - **Alternative Solution**: 35 | - **Description**: 36 | 37 | - **Pros**: 38 | - **Cons**: 39 | 40 | -------------------------------------------------------------------------------- /.vscode/prompts/new-idea.prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Create a proposal" 3 | --- 4 | 5 | I want to build an app. My idea was attached in the prompt I sent you. 6 | 7 | Let's join forces to make this idea into a simple, yet impactful proposal. 8 | 9 | FIRST: 10 | - Clarify any areas of my proposal that may need more details 11 | - Suggest new requirements based on the functionality provided 12 | - Consider edge cases that may not be included in my original proposal. 13 | - Organize requirements logically, and break them down into units that would make sense as user stories. 14 | - Raise any important high-level technical considerations, like platforms, languages, frameworks, and overall architecture details 15 | 16 | NEXT: 17 | - Iterate with me until I tell you I am satisfied. 18 | 19 | FINALLY: 20 | - Once I tell you I am ready, create a plan (or update an existing plan) in #file:../../docs/idea.md with the following structure as a Markdown file: 21 | 22 | ```markdown 23 | # Project Name 24 | ## 1. Product Overview 25 | -Core value proposition 26 | -Target audience 27 | ## 2. Functional Specifications 28 | -Break down the user's prompt into a series of functional specifications. 29 | -Extremely high level 30 | -Number these 2.1, 2.2, 2.3, etc. 31 | ## 3. Technical Specifications 32 | -Architecture for app at a high level, including language, frameworks, and platforms 33 | ## 4. MVP Scope 34 | -Provide a basic MVP scope that could be delivered quickly to validate this idea. 35 | ## 5. Business Model 36 | -Key differentiators in the market 37 | -Cover potential business model 38 | ## 6. Marketing Plan 39 | -Target audience 40 | -Overall marketing strategy 41 | -Marketing channels 42 | ``` -------------------------------------------------------------------------------- /.github/agents/debug.agent.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 'Debug your application to find and fix a bug' 3 | tools: ['edit/editNotebook', 'edit/newJupyterNotebook', 'runNotebooks', 'search', 'runCommands', 'runTasks', 'usages', 'problems', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runSubagent'] 4 | --- 5 | 6 | # Debug Mode Instructions 7 | 8 | You are in debug mode. Your primary objective is to systematically identify, analyze, and resolve bugs in the developer's application. Follow this structured debugging process: 9 | 10 | ## Phase 1: Problem Assessment 11 | 12 | 1. **Gather Context**: Understand the current issue by: 13 | - Reading error messages, stack traces, or failure reports 14 | - Examining the codebase structure and recent changes 15 | - Identifying the expected vs actual behavior 16 | - Reviewing relevant test files and their failures 17 | 18 | 2. **Reproduce the Bug**: Before making any changes: 19 | - Run the application or tests to confirm the issue 20 | - Document the exact steps to reproduce the problem 21 | - Capture error outputs, logs, or unexpected behaviors 22 | - Provide a clear bug report to the developer with: 23 | - Steps to reproduce 24 | - Expected behavior 25 | - Actual behavior 26 | - Error messages/stack traces 27 | - Environment details 28 | 29 | ## Phase 2: Investigation 30 | 31 | 3. **Root Cause Analysis**: 32 | - Trace the code execution path leading to the bug 33 | - Examine variable states, data flows, and control logic 34 | - Check for common issues: null references, off-by-one errors, race conditions, incorrect assumptions 35 | - Use search and usages tools to understand how affected components interact 36 | - Review git history for recent changes that might have introduced the bug 37 | 38 | 4. **Hypothesis Formation**: 39 | - Form specific hypotheses about what's causing the issue 40 | - Prioritize hypotheses based on likelihood and impact 41 | - Plan verification steps for each hypothesis 42 | 43 | ## Phase 3: Resolution 44 | 45 | 5. **Implement Fix**: 46 | - Make targeted, minimal changes to address the root cause 47 | - Ensure changes follow existing code patterns and conventions 48 | - Add defensive programming practices where appropriate 49 | - Consider edge cases and potential side effects 50 | 51 | 6. **Verification**: 52 | - Run tests to verify the fix resolves the issue 53 | - Execute the original reproduction steps to confirm resolution 54 | - Run broader test suites to ensure no regressions 55 | - Test edge cases related to the fix 56 | 57 | ## Phase 4: Quality Assurance 58 | 7. **Code Quality**: 59 | - Review the fix for code quality and maintainability 60 | - Add or update tests to prevent regression 61 | - Update documentation if necessary 62 | - Consider if similar bugs might exist elsewhere in the codebase 63 | 64 | 8. **Final Report**: 65 | - Summarize what was fixed and how 66 | - Explain the root cause 67 | - Document any preventive measures taken 68 | - Suggest improvements to prevent similar issues 69 | 70 | ## Debugging Guidelines 71 | - **Be Systematic**: Follow the phases methodically, don't jump to solutions 72 | - **Document Everything**: Keep detailed records of findings and attempts 73 | - **Think Incrementally**: Make small, testable changes rather than large refactors 74 | - **Consider Context**: Understand the broader system impact of changes 75 | - **Communicate Clearly**: Provide regular updates on progress and findings 76 | - **Stay Focused**: Address the specific bug without unnecessary changes 77 | - **Test Thoroughly**: Verify fixes work in various scenarios and environments 78 | 79 | Remember: Always reproduce and understand the bug before attempting to fix it. A well-understood problem is half solved. -------------------------------------------------------------------------------- /.vscode/instructions/coding-guidelines.instructions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "This file provides guidelines for writing clean, maintainable, and idiomatic C# code with a focus on functional patterns and proper abstraction." 3 | applyTo: "**/*.cs" 4 | --- 5 | # Role Definition: 6 | 7 | - C# Language Expert 8 | - Software Architect 9 | - Code Quality Specialist 10 | 11 | ## General: 12 | 13 | **Description:** 14 | C# code should be written to maximize readability, maintainability, and correctness while minimizing complexity and coupling. Prefer functional patterns and immutable data where appropriate, and keep abstractions simple and focused. 15 | 16 | **Requirements:** 17 | - Write clear, self-documenting code 18 | - Keep abstractions simple and focused 19 | - Minimize dependencies and coupling 20 | - Use modern C# features appropriately 21 | 22 | ## Code Organization: 23 | 24 | - Use meaningful names: 25 | ```csharp 26 | // Good: Clear intent 27 | public async Task> ProcessOrderAsync(OrderRequest request, CancellationToken cancellationToken) 28 | 29 | // Avoid: Unclear abbreviations 30 | public async Task> ProcAsync(ReqDto r, CancellationToken ct) 31 | ``` 32 | - Separate state from behavior: 33 | ```csharp 34 | // Good: Behavior separate from state 35 | public sealed record Order(OrderId Id, List Lines); 36 | 37 | public static class OrderOperations 38 | { 39 | public static decimal CalculateTotal(Order order) => 40 | order.Lines.Sum(line => line.Price * line.Quantity); 41 | } 42 | ``` 43 | - Prefer pure methods: 44 | ```csharp 45 | // Good: Pure function 46 | public static decimal CalculateTotalPrice( 47 | IEnumerable lines, 48 | decimal taxRate) => 49 | lines.Sum(line => line.Price * line.Quantity) * (1 + taxRate); 50 | 51 | // Avoid: Method with side effects 52 | public void CalculateAndUpdateTotalPrice() 53 | { 54 | this.Total = this.Lines.Sum(l => l.Price * l.Quantity); 55 | this.UpdateDatabase(); 56 | } 57 | ``` 58 | - Use extension methods appropriately: 59 | ```csharp 60 | // Good: Extension method for domain-specific operations 61 | public static class OrderExtensions 62 | { 63 | public static bool CanBeFulfilled(this Order order, Inventory inventory) => 64 | order.Lines.All(line => inventory.HasStock(line.ProductId, line.Quantity)); 65 | } 66 | ``` 67 | - Design for testability: 68 | ```csharp 69 | // Good: Easy to test pure functions 70 | public static class PriceCalculator 71 | { 72 | public static decimal CalculateDiscount( 73 | decimal price, 74 | int quantity, 75 | CustomerTier tier) => 76 | // Pure calculation 77 | } 78 | 79 | // Avoid: Hard to test due to hidden dependencies 80 | public decimal CalculateDiscount() 81 | { 82 | var user = _userService.GetCurrentUser(); // Hidden dependency 83 | var settings = _configService.GetSettings(); // Hidden dependency 84 | // Calculation 85 | } 86 | ``` 87 | 88 | ## Dependency Management: 89 | 90 | - Minimize constructor injection: 91 | ```csharp 92 | // Good: Minimal dependencies 93 | public sealed class OrderProcessor(IOrderRepository repository) 94 | { 95 | // Implementation 96 | } 97 | 98 | // Avoid: Too many dependencies 99 | // Too many dependencies indicates possible design issues 100 | public class OrderProcessor( 101 | IOrderRepository repository, 102 | ILogger logger, 103 | IEmailService emailService, 104 | IMetrics metrics, 105 | IValidator validator) 106 | { 107 | // Implementation 108 | } 109 | ``` 110 | - Prefer composition with interfaces: 111 | ```csharp 112 | // Good: Composition with interfaces 113 | public sealed class EnhancedLogger(ILogger baseLogger, IMetrics metrics) : ILogger 114 | { 115 | } 116 | ``` 117 | -------------------------------------------------------------------------------- /.vscode/git-message.instructions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "This file contains the instructions for the commit message format." 3 | --- 4 | 5 | ## Use Conentional Commits 6 | 7 | ## Commit Message Formats 8 | 9 | ### Default 10 | ``` 11 | (): 12 | 13 | 14 | 15 | 16 | ``` 17 | 18 | ### Types 19 | - `feat` Commits, that add or remove a new feature to the API or UI 20 | - `fix` Commits, that fix a API or UI bug of a preceded `feat` commit 21 | - `refactor` Commits, that rewrite/restructure your code, however do not change any API or UI behaviour 22 | - `perf` Commits are special `refactor` commits, that improve performance 23 | - `style` Commits, that do not affect the meaning (white-space, formatting, missing semi-colons, etc) 24 | - `test` Commits, that add missing tests or correcting existing tests 25 | - `docs` Commits, that affect documentation only 26 | - `build` Commits, that affect build components like build tool, ci pipeline, dependencies, project version, ... 27 | - `ops` Commits, that affect operational components like infrastructure, deployment, backup, recovery, ... 28 | - `chore` Miscellaneous commits e.g. modifying `.gitignore` 29 | 30 | ### Inital Commit 31 | ``` 32 | chore: init 33 | ``` 34 | 35 | ### Scopes 36 | The `scope` provides additional contextual information. 37 | * Is an **optional** part of the format 38 | * Allowed Scopes depends on the specific project 39 | * Don't use issue identifiers as scopes 40 | 41 | ### Breaking Changes Indicator 42 | Breaking changes should be indicated by an `!` before the `:` in the subject line e.g. `feat(api)!: remove status endpoint` 43 | - Is an **optional** part of the format 44 | 45 | ### Description 46 | The `description` contains a concise description of the change. 47 | - Is a **mandatory** part of the format 48 | - Use the imperative, present tense: "change" not "changed" nor "changes" 49 | - Think of `This commit will...` or `This commit should...` 50 | - Don't capitalize the first letter 51 | - No dot (`.`) at the end 52 | 53 | ### Body 54 | The `body` should include the motivation for the change and contrast this with previous behavior. 55 | - Is an **optional** part of the format 56 | - Use the imperative, present tense: "change" not "changed" nor "changes" 57 | - This is the place to mention issue identifiers and their relations 58 | 59 | ### Footer 60 | The `footer` should contain any information about **Breaking Changes** and is also the place to **reference Issues** that this commit refers to. 61 | - Is an **optional** part of the format 62 | - **optionally** reference an issue by its id. 63 | - **Breaking Changes** should start with the word `BREAKING CHANGES:` followed by space or two newlines. The rest of the commit message is then used for this. 64 | 65 | ### Versioning 66 | - **If** your next release contains commit with... 67 | - **breaking changes** incremented the **major version** 68 | - **API relevant changes** (`feat` or `fix`) incremented the **minor version** 69 | - **Else** increment the **patch version** 70 | 71 | 72 | ### Examples 73 | - ``` 74 | feat: add email notifications on new direct messages 75 | ``` 76 | - ``` 77 | feat(shopping cart): add the amazing button 78 | ``` 79 | - ``` 80 | feat!: remove ticket list endpoint 81 | 82 | refers to JIRA-1337 83 | 84 | BREAKING CHANGES: ticket enpoints no longer supports list all entites. 85 | ``` 86 | - ``` 87 | fix(shopping-cart): prevent order an empty shopping cart 88 | ``` 89 | - ``` 90 | fix(api): fix wrong calculation of request body checksum 91 | ``` 92 | - ``` 93 | fix: add missing parameter to service call 94 | 95 | The error occurred because of . 96 | ``` 97 | - ``` 98 | perf: decrease memory footprint for determine uniqe visitors by using HyperLogLog 99 | ``` 100 | - ``` 101 | build: update dependencies 102 | ``` 103 | - ``` 104 | build(release): bump version to 1.0.0 105 | ``` 106 | - ``` 107 | refactor: implement fibonacci number calculation as recursion 108 | ``` 109 | - ``` 110 | style: remove empty line 111 | ``` 112 | 113 | 114 | -------------------------------------------------------------------------------- /.github/agents/research.agent.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Researches topics in depth with comprehensive source analysis and synthesis 3 | argument-hint: What topic or question would you like researched? 4 | tools: ['search', 'new', 'Azure MCP/search', 'runSubagent', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'fetch', 'githubRepo'] 5 | handoffs: 6 | - label: Save Research 7 | agent: agent 8 | prompt: Save the research findings to a markdown file, as is 9 | --- 10 | 11 | You are a RESEARCH AGENT responsible for conducting comprehensive, in-depth research. 12 | 13 | You gather information from authoritative sources, recursively explore linked resources, analyze findings critically, and synthesize a well-cited response. Your iterative loops through research execution and result synthesis. 14 | 15 | Your SOLE responsibility is research, NEVER attempt to implement or execute solutions based on findings. 16 | 17 | 18 | STOP IMMEDIATELY if you consider implementing changes or taking action beyond gathering information. 19 | 20 | If you catch yourself proposing concrete implementations, STOP. Research findings inform future actions by other agents or the user. 21 | 22 | 23 | 24 | Comprehensive research and synthesis following : 25 | 26 | ## 1. Research execution: 27 | 28 | MANDATORY: Run #runSubagent tool, instructing the agent to work autonomously without pausing for user feedback, following to gather and synthesize research comprehensively. 29 | 30 | DO NOT do any other tool calls after #runSubagent returns! 31 | 32 | If #runSubagent tool is NOT available, run via tools yourself. 33 | 34 | ## 2. Present research findings to the user: 35 | 36 | 1. Follow and any additional instructions the user provided. 37 | 2. MANDATORY: Include all sources and citations clearly. 38 | 3. CRITICAL: Present findings for review, not for direct implementation. 39 | 40 | 41 | 42 | Conduct thorough research autonomously using the following steps: 43 | 44 | ## 1. Formulate Search Queries 45 | 46 | Break down the user's question into effective search queries that yield the most relevant and authoritative results. Consider: 47 | - Multiple query variations for comprehensive coverage 48 | - Technical vs. conceptual angles 49 | - Recent vs. foundational information 50 | - Official documentation, authoritative articles, and community resources 51 | 52 | ## 2. Perform Initial Searches 53 | 54 | Search across available sources: 55 | - Web search for latest information 56 | - Official documentation and technical references 57 | - GitHub repositories, issues, and code examples 58 | - Forums and Q&A sites for practical insights 59 | 60 | ## 3. Recursive Link Exploration 61 | 62 | For each search result: 63 | - Fetch and read the full content (not summaries or snippets) 64 | - Identify additional linked resources within the content 65 | - Recursively fetch and analyze these linked pages 66 | - Continue exploring until all key information is gathered 67 | - Do not stop at the first layer of results 68 | - Ensure comprehensive understanding through multi-layer exploration 69 | 70 | ## 4. Analyze and Synthesize Information 71 | 72 | - Critically evaluate credibility and relevance of each source 73 | - Cross-reference facts across sources 74 | - Discard outdated or conflicting information 75 | - Identify consensus and areas of disagreement 76 | - Synthesize findings into coherent narrative with clear citations 77 | 78 | ## 6. Cite All Sources 79 | 80 | - Clearly cite URLs and video links for all key information 81 | - Include timestamps for video references 82 | - Organize citations by source type and relevance 83 | - Provide URLs in clickable format when possible 84 | 85 | 86 | 87 | Present research findings in a clear, well-organized format. Use this template unless the user specifies otherwise: 88 | 89 | ```markdown 90 | ## Research: {Topic (210 words)} 91 | 92 | {Brief TL;DR of findings the key takeaways. (50150 words)} 93 | 94 | **Key Findings:** 95 | - {Finding 1 with source citation} 96 | - {Finding 2 with source citation} 97 | - {Finding 3 with source citation} 98 | 99 | **Sources:** 100 | - [{Source title}]({URL}) - {Brief description} 101 | - [{Video title}]({URL}) - {Description with timestamp if applicable} 102 | - {Additional sources as needed} 103 | 104 | **Analysis:** 105 | {Deeper analysis of findings, patterns, or consensus. (100200 words)} 106 | 107 | **Open Questions or Gaps:** 108 | {Any unanswered questions or areas needing further investigation.} 109 | ``` 110 | 111 | IMPORTANT: For research output, follow these rules even if they conflict with other guidelines: 112 | - ALWAYS cite sources with clickable links 113 | - INCLUDE full URLs from every source read 114 | - PROVIDE context for each finding 115 | - HIGHLIGHT consensus and disagreements 116 | - DO NOT propose implementations or solutions 117 | - ONLY present findings and synthesis 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🤖 .NET GitHub Copilot Rules 2 | 3 | A collection of [GitHub Copilot AI rules](https://code.visualstudio.com/docs/copilot/copilot-customization) for .NET development best practices. 4 | 5 | > [!TIP] 6 | > You can enhance Copilot's chat responses by providing it with contextual details about your team's workflow, tools, or project specifics. 7 | 8 | ## 📚 Customizations: Instructions, Prompts, MCPs, etc. 9 | 10 | This set of rules is a good starting point and should be customized to fit your specific needs. You can add or remove rules as necessary. 11 | 12 | Additionally, you can check [Awesome Copilot Repo](https://github.com/github/awesome-copilot) for more inspiration. 13 | 14 | ### 💻 .NET Development 15 | 16 | Rules for writing clean, maintainable C# code: 17 | - ✨ Modern C# coding patterns 18 | - 🧪 Testing best practices 19 | - 📁 Code organization 20 | - 🛡️ Error handling 21 | - 🔌 Dependency management 22 | 23 | * Code-generation instructions - [csharp/coding-guidelines.md](.vscode/instructions/coding-guidelines.instructions.md), [csharp/coding-style.md](.vscode/instructions/coding-style.instructions.md) 24 | * Test-generation instructions - [csharp/testing-xunit.md](.vscode/instructions/testing-xunit.instructions.md) 25 | 26 | ### ⏳ [Git](.vscode/git-message.instructions.md) 27 | 28 | * Commit message generation instructions - [git-message.md](.vscode/git-message.instructions.md) 29 | 30 | ### Tools 31 | 32 | Here is my top content extraction tools for GitHub Copilot: 33 | 34 | * `#fetch` - fetch web page 35 | * `#githubRepo` - fetch remote GitHub repository 36 | * `#context7` - fetch docs 37 | * `#codebase` - work with the code base 38 | 39 | ### MCP Servers 40 | 41 | More at 42 | 43 | ```bash 44 | npx @modelcontextprotocol/inspector --config .vscode/.mcp.json --server microsoft.docs.mcp 45 | ``` 46 | 47 | * https://code.visualstudio.com/mcp 48 | 49 | ## How to Use This Repository 50 | 51 | Copy the relevant instructions you want to use into your project's `.vscode/instructions` directory and configure `github.copilot.chat.codeGeneration.instructions` if needed. 52 | 53 | Here is an example of how to set up your `.vscode/settings.json` file: 54 | 55 | ```json 56 | { 57 | "chat.promptFiles": true, 58 | "chat.promptFilesLocations": { 59 | ".vscode/prompts": true 60 | }, 61 | "chat.instructionsFilesLocations": { 62 | ".vscode/instructions": true 63 | }, 64 | "github.copilot.chat.codeGeneration.instructions": [], 65 | "github.copilot.chat.commitMessageGeneration.instructions": [ 66 | { 67 | "file": ".vscode/git-message.instructions.md" 68 | } 69 | ], 70 | "github.copilot.chat.reviewSelection.enabled": true, 71 | "github.copilot.chat.reviewSelection.instructions": [ 72 | { 73 | "file": ".vscode/instructions/coding-guidelines.instructions.md" 74 | }, 75 | { 76 | "file": ".vscode/instructions/coding-style.instructions.md" 77 | } 78 | ], 79 | "github.copilot.chat.testGeneration.instructions": [ 80 | { 81 | "file": ".vscode/instructions/testing-xunit.instructions.md" 82 | } 83 | ] 84 | } 85 | ``` 86 | 87 | ## 🚀 Motivation 88 | 89 | ### Productivity 90 | 91 | You can create reusable instructions and prompts for your team to handle common tasks and GenAI scenarios effectively. 92 | 93 | ### Coding with Agents in Mind 94 | 95 | Shift your mindset from being a code typist to an AI operator when appropriate. Start with an instruction prompt and gather as much context as possible. Once you clearly understand the task, determine the level of AI assistance needed. 96 | 97 | 💡 To stay in the "Productivity Zone", gain experience with various AI models. 98 | 99 | ### Treat AI as a Teammate 100 | 101 | Building a successful, maintainable project requires clean, idiomatic, and consistent code. This is where *coding guidelines* are essential. They: 102 | - Promote consistency, readability, and maintainability. 103 | - Streamline collaboration and reduce technical debt. 104 | - Provide context for AI tools, ensuring generated code aligns with project standards. 105 | 106 | Without clear guidelines, AI contributions may introduce inconsistencies, increasing maintenance overhead. 107 | 108 | ### Documentation 109 | 110 | Document your coding guidelines so it can be consumed by AI tools. Not only can it be used for additional context for code generation, but also now you can chat with LLMs about your coding guidelines. It becomes integral part of your project. 111 | 112 | 💡 For example, you can ask Copilot to review your code, and it will refer to the guidelines you provided. 113 | 114 | ### Agent Mode 115 | 116 | With the introduction of [Agent Mode](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode), clear guidelines are crucial. They ensure consistency and independence in AI-generated contributions. 117 | 118 | ## Code Formatting and Linting 119 | 120 | Although, coding guidelines could hint AI to generate code in a specific way, it is not a replacement for code formatting and linting tools. Further more, AI could disrupt your code formatting so make sure to use formatters and analyzers to keep your code clean and consistent. 121 | 122 | ## Changelog: 123 | 124 | * New release introduces a better way to provide context to Copilot. 125 | * Custom Instructions (user/workspace) 126 | * Custom Prompts (user/workspace) 127 | 128 | ## Blogs 129 | 130 | * https://nikiforovall.blog/productivity/2025/03/08/github-copilot-instructions-for-dotnet.html 131 | * https://nikiforovall.blog/productivity/2025/04/19/github-copilot-prompt-engineering.html 132 | * https://nikiforovall.blog/productivity/2025/05/03/github-copilot-prompt-engineering-code-review.html 133 | 134 | ## 💳 Credits 135 | 136 | Inspired by 137 | -------------------------------------------------------------------------------- /.vscode/prompts/code-review.prompt.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "Perform a code review" 3 | --- 4 | 5 | ## Code Review Expert: Detailed Analysis and Best Practices 6 | 7 | As a senior software engineer with expertise in code quality, security, and performance optimization, perform a code review of the provided git diff. 8 | 9 | Focus on delivering actionable feedback in the following areas: 10 | 11 | Critical Issues: 12 | - Security vulnerabilities and potential exploits 13 | - Runtime errors and logic bugs 14 | - Performance bottlenecks and optimization opportunities 15 | - Memory management and resource utilization 16 | - Threading and concurrency issues 17 | - Input validation and error handling 18 | 19 | Code Quality: 20 | - Adherence to language-specific conventions and best practices 21 | - Design patterns and architectural considerations 22 | - Code organization and modularity 23 | - Naming conventions and code readability 24 | - Documentation completeness and clarity 25 | - Test coverage and testing approach 26 | 27 | Maintainability: 28 | - Code duplication and reusability 29 | - Complexity metrics (cyclomatic complexity, cognitive complexity) 30 | - Dependencies and coupling 31 | - Extensibility and future-proofing 32 | - Technical debt implications 33 | 34 | Provide specific recommendations with: 35 | - Code examples for suggested improvements 36 | - References to relevant documentation or standards 37 | - Rationale for suggested changes 38 | - Impact assessment of proposed modifications 39 | 40 | Format your review using clear sections and bullet points. Include inline code references where applicable. 41 | 42 | Note: This review should comply with the project's established coding standards and architectural guidelines. 43 | 44 | ## Constraints 45 | 46 | * **IMPORTANT**: Use `git --no-pager diff --no-prefix --unified=100000 --minimal $(git merge-base main --fork-point)...head` to get the diff for code review. 47 | * In the provided git diff, if the line start with `+` or `-`, it means that the line is added or removed. If the line starts with a space, it means that the line is unchanged. If the line starts with `@@`, it means that the line is a hunk header. 48 | 49 | * Avoid overwhelming the developer with too many suggestions at once. 50 | * Use clear and concise language to ensure understanding. 51 | 52 | * Assume suppressions are needed like `#pragma warning disable` and don't include them in the review. 53 | * If there are any TODO comments, make sure to address them in the review. 54 | 55 | * Use markdown for each suggestion, like 56 | ``` 57 | # Code Review for ${feature_description} 58 | 59 | Overview of the code changes, including the purpose of the feature, any relevant context, and the files involved. 60 | 61 | # Suggestions 62 | 63 | ## ${code_review_emoji} ${Summary of the suggestion, include necessary context to understand suggestion} 64 | * **Priority**: ${priority: (🔥/⚠️/🟡/🟢)} 65 | * **File**: ${relative/path/to/file} 66 | * **Details**: ... 67 | * **Example** (if applicable): ... 68 | * **Suggested Change** (if applicable): (code snippet...) 69 | 70 | ## (other suggestions...) 71 | ... 72 | 73 | # Summary 74 | ``` 75 | * Use the following emojis to indicate the priority of the suggestions: 76 | * 🔥 Critical 77 | * ⚠️ High 78 | * 🟡 Medium 79 | * 🟢 Low 80 | * Each suggestion should be prefixed with an emoji to indicate the type of suggestion: 81 | * 🔧 Change request 82 | * ❓ Question 83 | * ⛏️ Nitpick 84 | * ♻️ Refactor suggestion 85 | * 💭 Thought process or concern 86 | * 👍 Positive feedback 87 | * 📝 Explanatory note or fun fact 88 | * 🌱 Observation for future consideration 89 | * Always use file paths 90 | 91 | ### Use Code Review Emojis 92 | 93 | Use code review emojis. Give the reviewee added context and clarity to follow up on code review. For example, knowing whether something really requires action (🔧), highlighting nit-picky comments (⛏), flagging out of scope items for follow-up (📌) and clarifying items that don’t necessarily require action but are worth saying ( 👍, 📝, 🤔 ) 94 | 95 | #### Emoji Legend 96 | 97 | | | `:code:` | Meaning | 98 | | :---: | :-----------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 99 | | 🔧 | `:wrench:` | Use when this needs to be changed. This is a concern or suggested change/refactor that I feel is worth addressing. | 100 | | ❓ | `:question:` | Use when you have a question. This should be a fully formed question with sufficient information and context that requires a response. | 101 | | ⛏ | `:pick:` | This is a nitpick. This does not require any changes and is often better left unsaid. This may include stylistic, formatting, or organization suggestions and should likely be prevented/enforced by linting if they really matter | 102 | | ♻️ | `:recycle:` | Suggestion for refactoring. Should include enough context to be actionable and not be considered a nitpick. | 103 | | 💭 | `:thought_balloon:` | Express concern, suggest an alternative solution, or walk through the code in my own words to make sure I understand. | 104 | | 👍 | `:+1:` | Let the author know that you really liked something! This is a way to highlight positive parts of a code review, but use it only if it is really something well thought out. | 105 | | 📝 | `:memo:` | This is an explanatory note, fun fact, or relevant commentary that does not require any action. | 106 | | 🌱 | `:seedling:` | An observation or suggestion that is not a change request, but may have larger implications. Generally something to keep in mind for the future. | 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /.vscode/instructions/testing-xunit.instructions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "This file provides guidelines for writing effective, maintainable tests using xUnit and related tools" 3 | applyTo: "tests/**/*.cs" 4 | --- 5 | 6 | ## Role Definition: 7 | - Test Engineer 8 | - Quality Assurance Specialist 9 | 10 | ## General: 11 | 12 | **Description:** 13 | Tests should be reliable, maintainable, and provide meaningful coverage. Use xUnit as the primary testing framework, with proper isolation and clear patterns for test organization and execution. 14 | 15 | **Requirements:** 16 | - Use xUnit as the testing framework 17 | - Ensure test isolation 18 | - Follow consistent patterns 19 | - Maintain high code coverage 20 | 21 | ## Test Class Structure: 22 | 23 | - Use ITestOutputHelper for logging: 24 | ```csharp 25 | public class OrderProcessingTests(ITestOutputHelper output) 26 | { 27 | 28 | [Fact] 29 | public async Task ProcessOrder_ValidOrder_Succeeds() 30 | { 31 | output.WriteLine("Starting test with valid order"); 32 | // Test implementation 33 | } 34 | } 35 | ``` 36 | - Use fixtures for shared state: 37 | ```csharp 38 | public class DatabaseFixture : IAsyncLifetime 39 | { 40 | public DbConnection Connection { get; private set; } 41 | 42 | public async Task InitializeAsync() 43 | { 44 | Connection = new SqlConnection("connection-string"); 45 | await Connection.OpenAsync(); 46 | } 47 | 48 | public async Task DisposeAsync() 49 | { 50 | await Connection.DisposeAsync(); 51 | } 52 | } 53 | 54 | public class OrderTests : IClassFixture 55 | { 56 | private readonly DatabaseFixture _fixture; 57 | private readonly ITestOutputHelper _output; 58 | 59 | public OrderTests(DatabaseFixture fixture, ITestOutputHelper output) 60 | { 61 | _fixture = fixture; 62 | _output = output; 63 | } 64 | } 65 | ``` 66 | 67 | ## Test Methods: 68 | 69 | - Prefer Theory over multiple Facts: 70 | ```csharp 71 | public class DiscountCalculatorTests 72 | { 73 | public static TheoryData DiscountTestData => 74 | new() 75 | { 76 | { 100m, 1, 0m }, // No discount for single item 77 | { 100m, 5, 5m }, // 5% for 5 items 78 | { 100m, 10, 10m }, // 10% for 10 items 79 | }; 80 | 81 | [Theory] 82 | [MemberData(nameof(DiscountTestData))] 83 | public void CalculateDiscount_ReturnsCorrectAmount( 84 | decimal price, 85 | int quantity, 86 | decimal expectedDiscount) 87 | { 88 | // Arrange 89 | var calculator = new DiscountCalculator(); 90 | 91 | // Act 92 | var discount = calculator.Calculate(price, quantity); 93 | 94 | // Assert 95 | Assert.Equal(expectedDiscount, discount); 96 | } 97 | } 98 | ``` 99 | - Follow Arrange-Act-Assert pattern: 100 | ```csharp 101 | [Fact] 102 | public async Task ProcessOrder_ValidOrder_UpdatesInventory() 103 | { 104 | // Arrange 105 | var order = new Order( 106 | OrderId.New(), 107 | new[] { new OrderLine("SKU123", 5) }); 108 | var processor = new OrderProcessor(_mockRepository.Object); 109 | 110 | // Act 111 | var result = await processor.ProcessAsync(order); 112 | 113 | // Assert 114 | Assert.True(result.IsSuccess); 115 | _mockRepository.Verify( 116 | r => r.UpdateInventoryAsync( 117 | It.IsAny(), 118 | It.IsAny()), 119 | Times.Once); 120 | } 121 | ``` 122 | 123 | ## Test Isolation: 124 | 125 | - Use fresh data for each test: 126 | ```csharp 127 | public class OrderTests 128 | { 129 | private static Order CreateTestOrder() => 130 | new(OrderId.New(), TestData.CreateOrderLines()); 131 | 132 | [Fact] 133 | public async Task ProcessOrder_Success() 134 | { 135 | var order = CreateTestOrder(); 136 | // Test implementation 137 | } 138 | } 139 | ``` 140 | - Clean up resources: 141 | ```csharp 142 | public class IntegrationTests : IAsyncDisposable 143 | { 144 | private readonly TestServer _server; 145 | private readonly HttpClient _client; 146 | 147 | public IntegrationTests() 148 | { 149 | _server = new TestServer(CreateHostBuilder()); 150 | _client = _server.CreateClient(); 151 | } 152 | 153 | public async ValueTask DisposeAsync() 154 | { 155 | _client.Dispose(); 156 | await _server.DisposeAsync(); 157 | } 158 | } 159 | ``` 160 | 161 | ## Best Practices: 162 | 163 | - Name tests clearly: 164 | ```csharp 165 | // Good: Clear test names 166 | [Fact] 167 | public async Task ProcessOrder_WhenInventoryAvailable_UpdatesStockAndReturnsSuccess() 168 | 169 | // Avoid: Unclear names 170 | [Fact] 171 | public async Task TestProcessOrder() 172 | ``` 173 | - Use meaningful assertions: 174 | ```csharp 175 | // Good: Clear assertions 176 | Assert.Equal(expected, actual); 177 | Assert.Contains(expectedItem, collection); 178 | Assert.Throws(() => processor.Process(invalidOrder)); 179 | 180 | // Avoid: Multiple assertions without context 181 | Assert.NotNull(result); 182 | Assert.True(result.Success); 183 | Assert.Equal(0, result.Errors.Count); 184 | ``` 185 | - Handle async operations properly: 186 | ```csharp 187 | // Good: Async test method 188 | [Fact] 189 | public async Task ProcessOrder_ValidOrder_Succeeds() 190 | { 191 | await using var processor = new OrderProcessor(); 192 | var result = await processor.ProcessAsync(order); 193 | Assert.True(result.IsSuccess); 194 | } 195 | 196 | // Avoid: Sync over async 197 | [Fact] 198 | public void ProcessOrder_ValidOrder_Succeeds() 199 | { 200 | using var processor = new OrderProcessor(); 201 | var result = processor.ProcessAsync(order).Result; // Can deadlock 202 | Assert.True(result.IsSuccess); 203 | } 204 | ``` 205 | - Use `TestContext.Current.CancellationToken` for cancellation: 206 | ```csharp 207 | // Good: 208 | [Fact] 209 | public async Task ProcessOrder_CancellationRequested() 210 | { 211 | await using var processor = new OrderProcessor(); 212 | var result = await processor.ProcessAsync(order, TestContext.Current.CancellationToken); 213 | Assert.True(result.IsSuccess); 214 | } 215 | // Avoid: 216 | [Fact] 217 | public async Task ProcessOrder_CancellationRequested() 218 | { 219 | await using var processor = new OrderProcessor(); 220 | var result = await processor.ProcessAsync(order, CancellationToken.None); 221 | Assert.False(result.IsSuccess); 222 | } 223 | ``` 224 | 225 | ## Assertions: 226 | 227 | - Use xUnit's built-in assertions: 228 | ```csharp 229 | // Good: Using xUnit's built-in assertions 230 | public class OrderTests 231 | { 232 | [Fact] 233 | public void CalculateTotal_WithValidLines_ReturnsCorrectSum() 234 | { 235 | // Arrange 236 | var order = new Order( 237 | OrderId.New(), 238 | new[] 239 | { 240 | new OrderLine("SKU1", 2, 10.0m), 241 | new OrderLine("SKU2", 1, 20.0m) 242 | }); 243 | 244 | // Act 245 | var total = order.CalculateTotal(); 246 | 247 | // Assert 248 | Assert.Equal(40.0m, total); 249 | } 250 | 251 | [Fact] 252 | public void Order_WithInvalidLines_ThrowsException() 253 | { 254 | // Arrange 255 | var invalidLines = new OrderLine[] { }; 256 | 257 | // Act & Assert 258 | var ex = Assert.Throws(() => 259 | new Order(OrderId.New(), invalidLines)); 260 | Assert.Equal("Order must have at least one line", ex.Message); 261 | } 262 | 263 | [Fact] 264 | public void Order_WithValidData_HasExpectedProperties() 265 | { 266 | // Arrange 267 | var id = OrderId.New(); 268 | var lines = new[] { new OrderLine("SKU1", 1, 10.0m) }; 269 | 270 | // Act 271 | var order = new Order(id, lines); 272 | 273 | // Assert 274 | Assert.NotNull(order); 275 | Assert.Equal(id, order.Id); 276 | Assert.Single(order.Lines); 277 | Assert.Collection(order.Lines, 278 | line => 279 | { 280 | Assert.Equal("SKU1", line.Sku); 281 | Assert.Equal(1, line.Quantity); 282 | Assert.Equal(10.0m, line.Price); 283 | }); 284 | } 285 | } 286 | ``` 287 | 288 | - Avoid third-party assertion libraries: 289 | ```csharp 290 | // Avoid: Using FluentAssertions or similar libraries 291 | public class OrderTests 292 | { 293 | [Fact] 294 | public void CalculateTotal_WithValidLines_ReturnsCorrectSum() 295 | { 296 | var order = new Order( 297 | OrderId.New(), 298 | new[] 299 | { 300 | new OrderLine("SKU1", 2, 10.0m), 301 | new OrderLine("SKU2", 1, 20.0m) 302 | }); 303 | 304 | // Avoid: Using FluentAssertions 305 | order.CalculateTotal().Should().Be(40.0m); 306 | order.Lines.Should().HaveCount(2); 307 | order.Should().NotBeNull(); 308 | } 309 | } 310 | ``` 311 | 312 | - Use proper assertion types: 313 | ```csharp 314 | public class CustomerTests 315 | { 316 | [Fact] 317 | public void Customer_WithValidEmail_IsCreated() 318 | { 319 | // Boolean assertions 320 | Assert.True(customer.IsActive); 321 | Assert.False(customer.IsDeleted); 322 | 323 | // Equality assertions 324 | Assert.Equal("john@example.com", customer.Email); 325 | Assert.NotEqual(Guid.Empty, customer.Id); 326 | 327 | // Collection assertions 328 | Assert.Empty(customer.Orders); 329 | Assert.Contains("Admin", customer.Roles); 330 | Assert.DoesNotContain("Guest", customer.Roles); 331 | Assert.All(customer.Orders, o => Assert.NotNull(o.Id)); 332 | 333 | // Type assertions 334 | Assert.IsType(customer); 335 | Assert.IsAssignableFrom(customer); 336 | 337 | // String assertions 338 | Assert.StartsWith("CUST", customer.Reference); 339 | Assert.Contains("Premium", customer.Description); 340 | Assert.Matches("^CUST\\d{6}$", customer.Reference); 341 | 342 | // Range assertions 343 | Assert.InRange(customer.Age, 18, 100); 344 | 345 | // Reference assertions 346 | Assert.Same(expectedCustomer, actualCustomer); 347 | Assert.NotSame(differentCustomer, actualCustomer); 348 | } 349 | } 350 | ``` 351 | 352 | - Use Assert.Collection for complex collections: 353 | ```csharp 354 | [Fact] 355 | public void ProcessOrder_CreatesExpectedEvents() 356 | { 357 | // Arrange 358 | var processor = new OrderProcessor(); 359 | var order = CreateTestOrder(); 360 | 361 | // Act 362 | var events = processor.Process(order); 363 | 364 | // Assert 365 | Assert.Collection(events, 366 | evt => 367 | { 368 | Assert.IsType(evt); 369 | var received = Assert.IsType(evt); 370 | Assert.Equal(order.Id, received.OrderId); 371 | }, 372 | evt => 373 | { 374 | Assert.IsType(evt); 375 | var reserved = Assert.IsType(evt); 376 | Assert.Equal(order.Id, reserved.OrderId); 377 | Assert.NotEmpty(reserved.ReservedItems); 378 | }, 379 | evt => 380 | { 381 | Assert.IsType(evt); 382 | var confirmed = Assert.IsType(evt); 383 | Assert.Equal(order.Id, confirmed.OrderId); 384 | Assert.True(confirmed.IsSuccess); 385 | }); 386 | } 387 | ``` -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Don't use tabs for indentation. 7 | [*] 8 | indent_style = space 9 | # (Please don't specify an indent_size here; that has too many unintended consequences.) 10 | 11 | [*.{tf,tfvars,hcl}] 12 | indent_size = 2 13 | 14 | [*.{cs,csx,cake,vb,vbx}] 15 | 16 | # Code files 17 | [*.{cs,csx,vb,vbx}] 18 | indent_size = 4 19 | insert_final_newline = true 20 | charset = utf-8-bom 21 | 22 | # XML project files 23 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] 24 | indent_size = 2 25 | 26 | # XML config files 27 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] 28 | indent_size = 2 29 | 30 | # JSON files 31 | [*.json] 32 | indent_size = 2 33 | 34 | # Powershell files 35 | [*.ps1] 36 | indent_size = 2 37 | 38 | # Verify settings 39 | [*.{received,verified}.{json,txt,xml}] 40 | charset = "utf-8-bom" 41 | end_of_line = lf 42 | indent_size = unset 43 | indent_style = unset 44 | insert_final_newline = false 45 | tab_width = unset 46 | trim_trailing_whitespace = false 47 | 48 | # Dotnet code style settings: 49 | [*.{cs,vb}] 50 | dotnet_diagnostic.xUnit2018.severity = none # "do not compare an object's exact type to the abstract class" is a valid assert, but very noisy right now 51 | 52 | # Sort using and Import directives with System.* appearing first 53 | dotnet_sort_system_directives_first = true 54 | dotnet_separate_import_directive_groups = false 55 | # Avoid "this." and "Me." if not necessary 56 | dotnet_style_qualification_for_field = false:refactoring 57 | dotnet_style_qualification_for_property = false:refactoring 58 | dotnet_style_qualification_for_method = false:refactoring 59 | dotnet_style_qualification_for_event = false:refactoring 60 | 61 | # Use language keywords instead of framework type names for type references 62 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 63 | dotnet_style_predefined_type_for_member_access = true:suggestion 64 | 65 | # Suggest more modern language features when available 66 | dotnet_style_object_initializer = true:suggestion 67 | dotnet_style_collection_initializer = true:suggestion 68 | dotnet_style_coalesce_expression = true:suggestion 69 | dotnet_style_null_propagation = true:suggestion 70 | dotnet_style_explicit_tuple_names = true:suggestion 71 | 72 | # Whitespace options 73 | dotnet_style_allow_multiple_blank_lines_experimental = false 74 | 75 | # Non-private static fields are PascalCase 76 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion 77 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields 78 | dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style 79 | 80 | dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field 81 | dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected 82 | dotnet_naming_symbols.non_private_static_fields.required_modifiers = static 83 | 84 | dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case 85 | 86 | # Non-private readonly fields are PascalCase 87 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion 88 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields 89 | dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style 90 | 91 | dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field 92 | dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected 93 | dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly 94 | 95 | dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case 96 | 97 | # Constants are PascalCase 98 | dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion 99 | dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants 100 | dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style 101 | 102 | dotnet_naming_symbols.constants.applicable_kinds = field, local 103 | dotnet_naming_symbols.constants.required_modifiers = const 104 | 105 | dotnet_naming_style.constant_style.capitalization = pascal_case 106 | 107 | # Static fields are camelCase and start with s_ 108 | dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion 109 | dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields 110 | dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style 111 | 112 | dotnet_naming_symbols.static_fields.applicable_kinds = field 113 | dotnet_naming_symbols.static_fields.required_modifiers = static 114 | 115 | dotnet_naming_style.static_field_style.capitalization = camel_case 116 | dotnet_naming_style.static_field_style.required_prefix = s_ 117 | 118 | # Instance fields are camelCase and start with _ 119 | dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion 120 | dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields 121 | dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style 122 | 123 | dotnet_naming_symbols.instance_fields.applicable_kinds = field 124 | 125 | dotnet_naming_style.instance_field_style.capitalization = camel_case 126 | dotnet_naming_style.instance_field_style.required_prefix = _ 127 | 128 | # Locals and parameters are camelCase 129 | dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion 130 | dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters 131 | dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style 132 | 133 | dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local 134 | 135 | dotnet_naming_style.camel_case_style.capitalization = camel_case 136 | 137 | # Local functions are PascalCase 138 | dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion 139 | dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions 140 | dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style 141 | 142 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function 143 | 144 | dotnet_naming_style.local_function_style.capitalization = pascal_case 145 | 146 | # By default, name items with PascalCase 147 | dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion 148 | dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members 149 | dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style 150 | 151 | dotnet_naming_symbols.all_members.applicable_kinds = * 152 | 153 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 154 | 155 | # IDE0055: Fix formatting 156 | # Workaround for https://github.com/dotnet/roslyn/issues/70570 157 | dotnet_diagnostic.IDE0055.severity = warning 158 | 159 | # Namespace declaration preferences (IDE0160 and IDE0161) 160 | csharp_style_namespace_declarations = file_scoped:warning 161 | 162 | # CSharp code style settings: 163 | 164 | [*.cs] 165 | max_line_length=100 166 | # Newline settings 167 | csharp_new_line_before_open_brace = all 168 | csharp_new_line_before_else = true 169 | csharp_new_line_before_catch = true 170 | csharp_new_line_before_finally = true 171 | csharp_new_line_before_members_in_object_initializers = true 172 | csharp_new_line_before_members_in_anonymous_types = true 173 | csharp_new_line_between_query_expression_clauses = true 174 | 175 | # Indentation preferences 176 | csharp_indent_block_contents = true 177 | csharp_indent_braces = false 178 | csharp_indent_case_contents = true 179 | csharp_indent_case_contents_when_block = false 180 | csharp_indent_switch_labels = true 181 | csharp_indent_labels = flush_left 182 | 183 | # Whitespace options 184 | csharp_style_allow_embedded_statements_on_same_line_experimental = false 185 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false 186 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false 187 | csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false 188 | csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false 189 | 190 | # Prefer "var" everywhere 191 | csharp_style_var_for_built_in_types = true:suggestion 192 | csharp_style_var_when_type_is_apparent = true:suggestion 193 | csharp_style_var_elsewhere = true:suggestion 194 | 195 | # Prefer collection expressions 196 | dotnet_style_prefer_collection_expression = true:warning 197 | 198 | # Prefer method-like constructs to have a block body 199 | csharp_style_expression_bodied_methods = false:none 200 | csharp_style_expression_bodied_constructors = false:none 201 | csharp_style_expression_bodied_operators = false:none 202 | 203 | # Prefer property-like constructs to have an expression-body 204 | csharp_style_expression_bodied_properties = true:none 205 | csharp_style_expression_bodied_indexers = true:none 206 | csharp_style_expression_bodied_accessors = true:none 207 | 208 | csharp_style_expression_bodied_local_functions = when_on_single_line 209 | 210 | # Suggest more modern language features when available 211 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 212 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 213 | csharp_style_inlined_variable_declaration = true:suggestion 214 | csharp_style_throw_expression = true:suggestion 215 | csharp_style_conditional_delegate_call = true:suggestion 216 | csharp_style_prefer_extended_property_pattern = true:suggestion 217 | 218 | # Space preferences 219 | csharp_space_after_cast = false 220 | csharp_space_after_colon_in_inheritance_clause = true 221 | csharp_space_after_comma = true 222 | csharp_space_after_dot = false 223 | csharp_space_after_keywords_in_control_flow_statements = true 224 | csharp_space_after_semicolon_in_for_statement = true 225 | csharp_space_around_binary_operators = before_and_after 226 | csharp_space_around_declaration_statements = do_not_ignore 227 | csharp_space_before_colon_in_inheritance_clause = true 228 | csharp_space_before_comma = false 229 | csharp_space_before_dot = false 230 | csharp_space_before_open_square_brackets = false 231 | csharp_space_before_semicolon_in_for_statement = false 232 | csharp_space_between_empty_square_brackets = false 233 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 234 | csharp_space_between_method_call_name_and_opening_parenthesis = false 235 | csharp_space_between_method_call_parameter_list_parentheses = false 236 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 237 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 238 | csharp_space_between_method_declaration_parameter_list_parentheses = false 239 | csharp_space_between_parentheses = false 240 | csharp_space_between_square_brackets = false 241 | 242 | # Blocks are allowed 243 | csharp_prefer_braces = true:error 244 | csharp_preserve_single_line_blocks = true 245 | csharp_preserve_single_line_statements = false 246 | csharp_place_attribute_on_same_line = never 247 | csharp_max_initializer_elements_on_line = 1 248 | csharp_max_array_initializer_elements_on_line = 1 249 | 250 | # CS4014: Because this call is not awaited, execution of the current method continues before the call is completed 251 | dotnet_diagnostic.CS4014.severity = error 252 | 253 | # VSTHRD200: Use "Async" suffix for async methods 254 | dotnet_diagnostic.VSTHRD200.severity = error 255 | 256 | # VSTHRD011: Use AsyncLazy instead 257 | dotnet_diagnostic.VSTHRD011.severity = none 258 | 259 | # Consider calling Task.ConfigureAwait(Boolean) to signal your intention for continuation. 260 | dotnet_diagnostic.CA2007.severity = none 261 | 262 | # CA1308: Normalize strings to uppercase 263 | dotnet_diagnostic.CA1308.severity = none 264 | 265 | # CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. 266 | dotnet_diagnostic.CS8618.severity = none 267 | 268 | # CA1707: Identifiers should not contain underscores 269 | dotnet_diagnostic.CA1707.severity = none 270 | 271 | # CA1819: Properties should not return arrays 272 | dotnet_diagnostic.CA1819.severity = none 273 | 274 | # IDE1006: Naming Styles 275 | dotnet_diagnostic.IDE1006.severity = warning 276 | 277 | # IDE0011: Add braces 278 | dotnet_diagnostic.IDE0011.severity = warning 279 | 280 | # VSTHRD111: Use ConfigureAwait(bool) 281 | dotnet_diagnostic.VSTHRD111.severity = silent 282 | 283 | # CA1716: Conflicts with the reserved language keyword 284 | dotnet_diagnostic.CA1716.severity = silent 285 | 286 | # IDE0051: Remove unused private members 287 | dotnet_diagnostic.IDE0051.severity = error 288 | 289 | # Default severity for analyzer diagnostics with category 'CodeQuality' 290 | dotnet_analyzer_diagnostic.category-CodeQuality.severity = warning 291 | 292 | # CS1591: Missing XML comment for publicly visible type or member 293 | dotnet_diagnostic.CS1591.severity = silent 294 | 295 | # IDE0005: Using directive is unnecessary. 296 | dotnet_diagnostic.IDE0005.severity = error 297 | 298 | # IDE0160: Convert to block scoped namespace 299 | csharp_style_namespace_declarations = file_scoped 300 | 301 | # IDE2006: Blank line not allowed after arrow expression clause token 302 | # conflicts with csharpier 303 | dotnet_diagnostic.IDE2006.severity = silent 304 | 305 | # IDE0058: Expression value is never used 306 | dotnet_diagnostic.IDE0058.severity = suggestion 307 | 308 | # IDE0045: Convert to conditional expression 309 | dotnet_diagnostic.IDE0045.severity = suggestion 310 | 311 | # CA1515: Consider making public types internal 312 | dotnet_diagnostic.CA1515.severity = none 313 | 314 | -------------------------------------------------------------------------------- /.vscode/instructions/coding-style.instructions.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: "This file provides guidelines for writing clean, maintainable, and idiomatic C# code with a focus on functional patterns and proper abstraction." 3 | applyTo: "**/*.cs" 4 | --- 5 | 6 | ## Type Definitions: 7 | 8 | - Prefer records for data types: 9 | ```csharp 10 | // Good: Immutable data type with value semantics 11 | public sealed record CustomerDto(string Name, Email Email); 12 | 13 | // Avoid: Class with mutable properties 14 | public class Customer 15 | { 16 | public string Name { get; set; } 17 | public string Email { get; set; } 18 | } 19 | ``` 20 | - Make classes sealed by default: 21 | ```csharp 22 | // Good: Make classes sealed by default 23 | public sealed class OrderProcessor 24 | { 25 | // Implementation 26 | } 27 | 28 | // Only unsealed when inheritance is specifically designed for 29 | public abstract class Repository 30 | { 31 | // Base implementation 32 | } 33 | ``` 34 | 35 | ## Variable Declarations: 36 | 37 | - Use var where possible: 38 | ```csharp 39 | // Good: Using var for type inference 40 | var fruit = "Apple"; 41 | var number = 42; 42 | var order = new Order(fruit, number); 43 | ``` 44 | 45 | ## Control Flow: 46 | 47 | - Prefer range indexers over LINQ: 48 | ```csharp 49 | // Good: Using range indexers with clear comments 50 | var lastItem = items[^1]; // ^1 means "1 from the end" 51 | var firstThree = items[..3]; // ..3 means "take first 3 items" 52 | var slice = items[2..5]; // take items from index 2 to 4 (5 exclusive) 53 | 54 | // Avoid: Using LINQ when range indexers are clearer 55 | var lastItem = items.LastOrDefault(); 56 | var firstThree = items.Take(3).ToList(); 57 | var slice = items.Skip(2).Take(3).ToList(); 58 | ``` 59 | - Prefer collection initializers: 60 | ```csharp 61 | // Good: Using collection initializers 62 | string[] fruits = ["Apple", "Banana", "Cherry"]; 63 | 64 | // Avoid: Using explicit initialization when type is clear 65 | var fruits = new List() { 66 | "Apple", 67 | "Banana", 68 | "Cherry" 69 | }; 70 | ``` 71 | - Use pattern matching effectively: 72 | ```csharp 73 | // Good: Clear pattern matching 74 | public decimal CalculateDiscount(Customer customer) => 75 | customer switch 76 | { 77 | { Tier: CustomerTier.Premium } => 0.2m, 78 | { OrderCount: > 10 } => 0.1m, 79 | _ => 0m 80 | }; 81 | 82 | // Avoid: Nested if statements 83 | public decimal CalculateDiscount(Customer customer) 84 | { 85 | if (customer.Tier == CustomerTier.Premium) 86 | return 0.2m; 87 | if (customer.OrderCount > 10) 88 | return 0.1m; 89 | return 0m; 90 | } 91 | ``` 92 | 93 | ## Nullability: 94 | 95 | - Mark nullable fields explicitly: 96 | ```csharp 97 | // Good: Explicit nullability 98 | public class OrderProcessor 99 | { 100 | private readonly ILogger? _logger; 101 | private string? _lastError; 102 | 103 | public OrderProcessor(ILogger? logger = null) 104 | { 105 | _logger = logger; 106 | } 107 | } 108 | 109 | // Avoid: Implicit nullability 110 | public class OrderProcessor 111 | { 112 | private readonly ILogger _logger; // Warning: Could be null 113 | private string _lastError; // Warning: Could be null 114 | } 115 | ``` 116 | - Use null checks only when necessary for reference types and public methods: 117 | ```csharp 118 | // Good: Proper null checking 119 | public void ProcessOrder(Order order) 120 | { 121 | ArgumentNullException.ThrowIfNull(order); // Appropriate for reference types 122 | 123 | _logger?.LogInformation("Processing order {Id}", order.Id); 124 | } 125 | 126 | // Good: Using pattern matching for null checks 127 | public decimal CalculateTotal(Order? order) => 128 | order switch 129 | { 130 | null => throw new ArgumentNullException(nameof(order)), 131 | { Lines: null } => throw new ArgumentException("Order lines cannot be null", nameof(order)), 132 | _ => order.Lines.Sum(l => l.Total) 133 | }; 134 | // BAD: Avoid null checks for value types 135 | public void ProcessOrder(int orderId) 136 | { 137 | ArgumentNullException.ThrowIfNull(order); // DON'T USE Null checks are unnecessary for value types 138 | } 139 | 140 | // Avoid: null checks for non-public methods 141 | private void ProcessOrder(Order order) 142 | { 143 | ArgumentNullException.ThrowIfNull(order); // DON'T USE, ProcessOrder is private 144 | } 145 | ``` 146 | - Use null-forgiving operator when appropriate: 147 | ```csharp 148 | public class OrderValidator 149 | { 150 | private readonly IValidator _validator; 151 | 152 | public OrderValidator(IValidator validator) 153 | { 154 | _validator = validator ?? throw new ArgumentNullException(nameof(validator)); 155 | } 156 | 157 | public ValidationResult Validate(Order order) 158 | { 159 | // We know _validator can't be null due to constructor check 160 | return _validator!.Validate(order); 161 | } 162 | } 163 | ``` 164 | - Use nullability attributes: 165 | ```csharp 166 | public class StringUtilities 167 | { 168 | // Output is non-null if input is non-null 169 | [return: NotNullIfNotNull(nameof(input))] 170 | public static string? ToUpperCase(string? input) => 171 | input?.ToUpperInvariant(); 172 | 173 | // Method never returns null 174 | [return: NotNull] 175 | public static string EnsureNotNull(string? input) => 176 | input ?? string.Empty; 177 | 178 | // Parameter must not be null when method returns true 179 | public static bool TryParse(string? input, [NotNullWhen(true)] out string? result) 180 | { 181 | result = null; 182 | if (string.IsNullOrEmpty(input)) 183 | return false; 184 | 185 | result = input; 186 | return true; 187 | } 188 | } 189 | ``` 190 | - Use init-only properties with non-null validation: 191 | ```csharp 192 | // Good: Non-null validation in constructor 193 | public sealed record Order 194 | { 195 | public required OrderId Id { get; init; } 196 | public required ImmutableList Lines { get; init; } 197 | 198 | public Order() 199 | { 200 | Id = null!; // Will be set by required property 201 | Lines = null!; // Will be set by required property 202 | } 203 | 204 | private Order(OrderId id, ImmutableList lines) 205 | { 206 | Id = id; 207 | Lines = lines; 208 | } 209 | 210 | public static Order Create(OrderId id, IEnumerable lines) => 211 | new(id, lines.ToImmutableList()); 212 | } 213 | ``` 214 | - Document nullability in interfaces: 215 | ```csharp 216 | public interface IOrderRepository 217 | { 218 | // Explicitly shows that null is a valid return value 219 | Task FindByIdAsync(OrderId id, CancellationToken ct = default); 220 | 221 | // Method will never return null 222 | [return: NotNull] 223 | Task> GetAllAsync(CancellationToken ct = default); 224 | 225 | // Parameter cannot be null 226 | Task SaveAsync([NotNull] Order order, CancellationToken ct = default); 227 | } 228 | ``` 229 | 230 | ## Safe Operations: 231 | 232 | - Use Try methods for safer operations: 233 | ```csharp 234 | // Good: Using TryGetValue for dictionary access 235 | if (dictionary.TryGetValue(key, out var value)) 236 | { 237 | // Use value safely here 238 | } 239 | else 240 | { 241 | // Handle missing key case 242 | } 243 | ``` 244 | ```csharp 245 | // Avoid: Direct indexing which can throw 246 | var value = dictionary[key]; // Throws if key doesn't exist 247 | 248 | // Good: Using Uri.TryCreate for URL parsing 249 | if (Uri.TryCreate(urlString, UriKind.Absolute, out var uri)) 250 | { 251 | // Use uri safely here 252 | } 253 | else 254 | { 255 | // Handle invalid URL case 256 | } 257 | ``` 258 | ```csharp 259 | // Avoid: Direct Uri creation which can throw 260 | var uri = new Uri(urlString); // Throws on invalid URL 261 | 262 | // Good: Using int.TryParse for number parsing 263 | if (int.TryParse(input, out var number)) 264 | { 265 | // Use number safely here 266 | } 267 | else 268 | { 269 | // Handle invalid number case 270 | } 271 | ``` 272 | ```csharp 273 | // Good: Combining Try methods with null coalescing 274 | var value = dictionary.TryGetValue(key, out var result) 275 | ? result 276 | : defaultValue; 277 | 278 | // Good: Using Try methods in LINQ with pattern matching 279 | var validNumbers = strings 280 | .Select(s => (Success: int.TryParse(s, out var num), Value: num)) 281 | .Where(x => x.Success) 282 | .Select(x => x.Value); 283 | ``` 284 | 285 | - Prefer Try methods over exception handling: 286 | ```csharp 287 | // Good: Using Try method 288 | if (decimal.TryParse(priceString, out var price)) 289 | { 290 | // Process price 291 | } 292 | 293 | // Avoid: Exception handling for expected cases 294 | try 295 | { 296 | var price = decimal.Parse(priceString); 297 | // Process price 298 | } 299 | catch (FormatException) 300 | { 301 | // Handle invalid format 302 | } 303 | ``` 304 | 305 | ## Asynchronous Programming: 306 | 307 | - Use Task.FromResult for pre-computed values: 308 | ```csharp 309 | // Good: Return pre-computed value 310 | public Task GetDefaultQuantityAsync() => 311 | Task.FromResult(1); 312 | 313 | // Better: Use ValueTask for zero allocations 314 | public ValueTask GetDefaultQuantityAsync() => 315 | new ValueTask(1); 316 | 317 | // Avoid: Unnecessary thread pool usage 318 | public Task GetDefaultQuantityAsync() => 319 | Task.Run(() => 1); 320 | ``` 321 | - Always flow CancellationToken: 322 | ```csharp 323 | // Good: Propagate cancellation 324 | public async Task ProcessOrderAsync( 325 | OrderRequest request, 326 | CancellationToken cancellationToken) 327 | { 328 | var order = await _repository.GetAsync( 329 | request.OrderId, 330 | cancellationToken); 331 | 332 | await _processor.ProcessAsync( 333 | order, 334 | cancellationToken); 335 | 336 | return order; 337 | } 338 | ``` 339 | - Prefer await: 340 | ```csharp 341 | // Good: Using await 342 | public async Task ProcessOrderAsync(OrderId id) 343 | { 344 | var order = await _repository.GetAsync(id); 345 | await _validator.ValidateAsync(order); 346 | return order; 347 | } 348 | ``` 349 | - Never use Task.Result or Task.Wait: 350 | ```csharp 351 | // Good: Async all the way 352 | public async Task GetOrderAsync(OrderId id) 353 | { 354 | return await _repository.GetAsync(id); 355 | } 356 | 357 | // Avoid: Blocking on async code 358 | public Order GetOrder(OrderId id) 359 | { 360 | return _repository.GetAsync(id).Result; // Can deadlock 361 | } 362 | ``` 363 | - Use TaskCompletionSource correctly: 364 | ```csharp 365 | // Good: Using RunContinuationsAsynchronously 366 | private readonly TaskCompletionSource _tcs = 367 | new(TaskCreationOptions.RunContinuationsAsynchronously); 368 | 369 | // Avoid: Default TaskCompletionSource can cause deadlocks 370 | private readonly TaskCompletionSource _tcs = new(); 371 | ``` 372 | - Always dispose CancellationTokenSources: 373 | ```csharp 374 | // Good: Proper disposal of CancellationTokenSource 375 | public async Task GetOrderWithTimeout(OrderId id) 376 | { 377 | using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); 378 | return await _repository.GetAsync(id, cts.Token); 379 | } 380 | ``` 381 | - Prefer async/await over direct Task return: 382 | ```csharp 383 | // Good: Using async/await 384 | public async Task ProcessOrderAsync(OrderRequest request) 385 | { 386 | await _validator.ValidateAsync(request); 387 | var order = await _factory.CreateAsync(request); 388 | return order; 389 | } 390 | 391 | // Avoid: Manual task composition 392 | public Task ProcessOrderAsync(OrderRequest request) 393 | { 394 | return _validator.ValidateAsync(request) 395 | .ContinueWith(t => _factory.CreateAsync(request)) 396 | .Unwrap(); 397 | } 398 | ``` 399 | 400 | ## Symbol References: 401 | 402 | - Always use nameof operator: 403 | ```csharp 404 | // Good: Using nameof in attributes 405 | public class OrderProcessor 406 | { 407 | [Required(ErrorMessage = "The {0} field is required")] 408 | [Display(Name = nameof(OrderId))] 409 | public string OrderId { get; init; } 410 | 411 | [MemberNotNull(nameof(_repository))] 412 | private void InitializeRepository() 413 | { 414 | _repository = new OrderRepository(); 415 | } 416 | 417 | [NotifyPropertyChangedFor(nameof(FullName))] 418 | public string FirstName 419 | { 420 | get => _firstName; 421 | set => SetProperty(ref _firstName, value); 422 | } 423 | } 424 | ``` 425 | - Use nameof with exceptions: 426 | ```csharp 427 | public class OrderService 428 | { 429 | public async Task GetOrderAsync(OrderId id, CancellationToken ct) 430 | { 431 | var order = await _repository.FindAsync(id, ct); 432 | 433 | if (order is null) 434 | throw new OrderNotFoundException( 435 | $"Order with {nameof(id)} '{id}' not found"); 436 | 437 | if (!order.Lines.Any()) 438 | throw new InvalidOperationException( 439 | $"{nameof(order.Lines)} cannot be empty"); 440 | 441 | return order; 442 | } 443 | 444 | public void ValidateOrder(Order order) 445 | { 446 | if (order.Lines.Count == 0) 447 | throw new ArgumentException( 448 | "Order must have at least one line", 449 | nameof(order)); 450 | } 451 | } 452 | ``` 453 | - Use nameof in logging: 454 | ```csharp 455 | public class OrderProcessor 456 | { 457 | private readonly ILogger _logger; 458 | 459 | public async Task ProcessAsync(Order order) 460 | { 461 | _logger.LogInformation( 462 | "Starting {Method} for order {OrderId}", 463 | nameof(ProcessAsync), 464 | order.Id); 465 | 466 | try 467 | { 468 | await ProcessInternalAsync(order); 469 | } 470 | catch (Exception ex) 471 | { 472 | _logger.LogError( 473 | ex, 474 | "Error in {Method} for {Property} {Value}", 475 | nameof(ProcessAsync), 476 | nameof(order.Id), 477 | order.Id); 478 | throw; 479 | } 480 | } 481 | } 482 | ``` 483 | 484 | ### Usings and Namespaces: 485 | 486 | - Use implicit usings: 487 | ```csharp 488 | // Good: Implicit 489 | namespace MyNamespace 490 | { 491 | public class MyClass 492 | { 493 | // Implementation 494 | } 495 | } 496 | // Avoid: 497 | using System; // DON'T USE 498 | using System.Collections.Generic; // DON'T USE 499 | using System.IO; // DON'T USE 500 | using System.Linq; // DON'T USE 501 | using System.Net.Http; // DON'T USE 502 | using System.Threading; // DON'T USE 503 | using System.Threading.Tasks;// DON'T USE 504 | using System.Net.Http.Json; // DON'T USE 505 | using Microsoft.AspNetCore.Builder; // DON'T USE 506 | using Microsoft.AspNetCore.Hosting; // DON'T USE 507 | using Microsoft.AspNetCore.Http; // DON'T USE 508 | using Microsoft.AspNetCore.Routing; // DON'T USE 509 | using Microsoft.Extensions.Configuration; // DON'T USE 510 | using Microsoft.Extensions.DependencyInjection; // DON'T USE 511 | using Microsoft.Extensions.Hosting; // DON'T USE 512 | using Microsoft.Extensions.Logging; // DON'T USE 513 | using Good: Explicit usings; // DON'T USE 514 | 515 | namespace MyNamespace 516 | { 517 | public class MyClass 518 | { 519 | // Implementation 520 | } 521 | } 522 | ``` 523 | - Use file-scoped namespaces: 524 | ```csharp 525 | // Good: File-scoped namespace 526 | namespace MyNamespace; 527 | 528 | public class MyClass 529 | { 530 | // Implementation 531 | } 532 | 533 | // Avoid: Block-scoped namespace 534 | namespace MyNamespace 535 | { 536 | public class MyClass 537 | { 538 | // Implementation 539 | } 540 | } 541 | ``` --------------------------------------------------------------------------------