├── .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 | ```
--------------------------------------------------------------------------------