Mark Ku's Blog
Podcast ConversationAI dialogue version of this article · Mandarin audio
Audio for this article is powered by VoAIVoAI

Introduction

As software projects continue to grow in scale, code quality often has a greater impact on project maintainability and team efficiency than the features themselves. Inconsistent naming, messy formatting, and a lack of coding standards can easily become hidden costs during future maintenance or handovers.

This article will focus on Visual Studio to outline a set of common and practical methods for managing code quality. We'll cover everything from setting up rules with the built-in Roslyn Analyzer and .editorconfig, to automated formatting with Code Cleanup, and finally, AI-assisted development and code review with GitHub Copilot.

By combining these tools and processes, development teams can:

Identify issues early on while writing code,

Maintain a consistent code style through automation,

And leverage AI to reduce repetitive work, allowing them to focus on core design and logic.

Prerequisites

  • Visual Studio 2022 or later
  • GitHub Copilot

1️⃣ Use Visual Studio's built-in rule engine, Roslyn Analyzers, and the .editorconfig rule set to enforce code style.

Roslyn Analyzer is the code analysis tool for the .NET world, similar in function to ESLint for JavaScript. It acts as a "rule engine" that automatically checks your code quality as you write and compile. .editorconfig, on the other hand, is the "rule configuration file" where you define how those rules should run—for example, whether to use camel case for naming or how many spaces to use for indentation.

In short:

Roslyn Analyzer = Does the checking

.editorconfig = Defines how to check

Integrating with .editorconfig settings

Create .editorconfig in the project's root directory:

root = true

[*]
charset = utf-8
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
max_line_length = 120

dotnet_style_qualification_for_field = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_qualification_for_method = false:error
dotnet_style_qualification_for_event = false:error

[*.cs]
csharp_style_var_for_built_in_types = false
dotnet_diagnostic.IDE0008.severity = error
csharp_style_namespace_declarations = file_scoped:error

You can also adjust .editorconfig settings through the Visual Studio UI

  • Open 工具 > 選項 > 文字編輯器 > C# > 程式碼樣式
  • Visual Studio 2022 has built-in support for EditorConfig, so you just need to reload the project for the changes to take effect. Visual Studio C Code Style options dialog with EditorConfig preferences

Modify the project file WebApplication1/WebApplication1.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>

    <!-- 啟用 .NET 分析器 -->
    <EnableNETAnalyzers>true</EnableNETAnalyzers>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  </PropertyGroup>
</Project>

Parameter Descriptions

  • EnableNETAnalyzers: Enables .NET code analyzers to check for code quality issues during the build.
  • EnforceCodeStyleInBuild: Treats code style violations as build errors, ensuring a consistent code style across the team. Now, when you build the project, rule violations will appear as errors. Visual Studio code editor displaying Roslyn analyzer errors and quick fix suggesVisual Studio IDE showing Roslyn analyzer code style errors and Copilot suggestiVisual Studio displaying Roslyn analyzer code style errors and Copilot

Fixing Errors

  • Error List window (檢視 > 錯誤清單)
  • Error squiggles in the editor
  • Quick Actions: Ctrl+. → Select a suggested fix Visual Studio Code quick action menu fixing 'index' method naming

Additional Note: You can also run rule validation via the command line (CLI) for easy CI/CD integration

# 建置並顯示警告
dotnet build --verbosity normal

# 僅執行程式碼分析
dotnet build --no-restore --verbosity normal

# 將警告視為錯誤
dotnet build /p:TreatWarningsAsErrors=true

P.S. For more complex logic, you can create a custom NamingConventionAnalyzer, which requires writing your own code.

2️⃣ Code Cleanup - Automatically format code style on save

Visual Studio Settings

  • Open 工具 > 選項 > 文字編輯器 > C#

  • Check the following:

    • 在儲存時自動格式化文件
    • 在儲存時移除並排序 using

Code Cleanup Settings

  • Open 工具 > 選項 > 文字編輯器 > 程式碼清除 > 設定程式碼清除 Visual Studio Code Code Cleanup settings with Execute on Save enabled

  • Select the rules to run and set a shortcut key Ctrl+K, Ctrl+E Visual Studio Code Cleanup settings window showing fix lists

The tricky part is that the team needs to sync these settings manually:

  • Windows: %USERPROFILE%\AppData\Local\Microsoft\VisualStudio\<version>\CodeCleanupProfiles.json
  • Mac: ~/Library/Preferences/VisualStudio/<version>/CodeCleanupProfiles.json

Example: Auto-formatting in action

Before formatting:

namespace WebApplication1
{
    public class TestFormatting
    {
        private string fieldName;
        private int fieldValue;

        public TestFormatting()
        {
            this.fieldName = "test";
            this.fieldValue = 42;
        }
    }
}

After saving, the auto-formatted result:

namespace WebApplication1;

public class TestFormatting
{
    private string fieldName;
    private int fieldValue;

    public TestFormatting()
    {
        fieldName = "test";
        fieldValue = 42;
    }
}

3️⃣ Automatically Generate Commit Messages with GitHub Copilot

If you want Copilot to help you write commit messages based on the content of your commits, you can refer to the article below. This article explains how to define custom instructions for generating Git commit messages, allowing you to add commit conventions (like Conventional Commits) or example structures to your workflow to help automatically generate more consistent messages. Visual Studio Code with C code and Copilot's suggested Git commit message


4️⃣ Generate Code According to Coding Rules (Copilot Custom Rules)

Using GitHub Copilot with Custom Rules

GitHub Copilot is a powerful AI coding assistant that can be configured with instructions to generate code that conforms to your team's standards.

Setting Copilot Instructions

In recent versions of Visual Studio, under Tools > Options > GitHub Copilot, the option to reference github/copilot-instructions for guidance and rules when generating code is enabled by default. Visual Studio options dialog with GitHub Copilot settings

So, all we need to do is create a .github/copilot-instructions.md file in the project's root directory:

程式註解一律使用繁體中文
變數、函數、類別名稱使用英文
函數名稱尾巴加上 123
程式碼最上方加註:由 GitHub Copilot 產生
變數名稱全部大寫並使用底線分隔

Now, whenever you open Copilot Chat, it will generate code based on these development guidelines. However, Copilot's autocompletion (IntelliSense) will not follow these rules.

But you can explicitly reference them in a comment, for example:

// 根據 Instructions.md 的規範

5️⃣ GitHub Copilot Code Review

GitHub Copilot not only helps generate code but can also perform code reviews. With AI assistance, you can quickly check code quality, identify potential issues, and receive suggestions for improvement.

⚠️ Note: This feature requires a GitHub Copilot Enterprise subscription. It is disabled by default and must be enabled to be used.


Further Reading

Appendix: Complete .editorconfig Example

Below is a complete, copy-pasteable example of an .editorconfig file:

# Editor configuration, see https://editorconfig.org
# 編輯器配置文件,詳見 https://editorconfig.org
root = true

# =============================================================================
# 通用排版規則 - 適用於所有文件類型
# =============================================================================
[*]
# 適用於所有文件的通用設置
charset = utf-8
# 字符編碼設置為 UTF-8
end_of_line = crlf
# 行尾符號使用 CRLF (Windows 風格)
insert_final_newline = true
# 文件結尾自動插入換行符
trim_trailing_whitespace = true
# 自動移除行尾空白字符
indent_style = space
# 縮進樣式使用空格而不是 Tab

### Indentation and spacing
### 縮進和間距設置
indent_size = 4
# 縮進大小為 4 個空格
tab_width = 4
# Tab 寬度為 4 個空格
max_line_length = 120
# 最大行長度為 120 字符

# =============================================================================
# 存檔時自動排版設置
# =============================================================================
# 啟用存檔時自動格式化
dotnet_style_qualification_for_field = false:error
# 字段不需要 this 限定符
dotnet_style_qualification_for_property = false:error
# 屬性不需要 this 限定符
dotnet_style_qualification_for_method = false:error
# 方法不需要 this 限定符
dotnet_style_qualification_for_event = false:error
# 事件不需要 this 限定符

# 強制執行一致的代碼格式
dotnet_diagnostic.IDE0055.severity = error # Remove unnecessary import
# 移除不必要的 import,違反時顯示錯誤
dotnet_diagnostic.IDE0100.severity = error # Remove unnecessary equality operator
# 移除不必要的相等運算子,違反時顯示錯誤
dotnet_diagnostic.IDE0055.severity = error # Fix formatting
# 修復格式化問題,顯示為錯誤

# =============================================================================
# 命名規則 - 根據命名規範精簡版
# =============================================================================
# 強制執行命名規則 (IDE1006) 作為建置錯誤
dotnet_diagnostic.IDE1006.severity = error
# 命名規則違反時顯示錯誤

# =============================================================================
# GitHub Copilot 設置
# =============================================================================
# GitHub Copilot Chat commit message generation instructions
# GitHub Copilot Chat 提交訊息生成指令
github.copilot.chat.commitMessageGeneration.instructions = |
  - 使用 Conventional Commits 格式:<type>(scope): <subject>
  - type 僅限 feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
  - 用中文撰寫 subject,使用祈使句,長度不超過 72 字符
  - 如需補充,加入英文 body 段落(可選),每行不超過 72 字符
  - 若適用,列出關鍵變更點作為項目符號
  - 若有重大變更,加入 BREAKING CHANGE: 說明
  - 僅根據實際 diff 與變更檔案撰寫,不要捏造內容

# =============================================================================
# C# 特定規則
# =============================================================================
[*.cs]
# 適用於所有 C# 文件的設置

# 縮進和括號設置
csharp_use_continuous_indent_inside_parens = true
# C# 在括號內使用連續縮進

# var 關鍵字使用規則
csharp_style_var_for_built_in_types = false
# 對於內建類型不使用 var 關鍵字
dotnet_diagnostic.IDE0008.severity = error # var preferences
# var 偏好設置,違反時顯示錯誤

# 命名空間規則
dotnet_style_namespace_match_folder = true
# 命名空間結構應與資料夾結構匹配
dotnet_diagnostic.IDE0130.severity = suggestion # namespace structure not match folder
# 命名空間結構不匹配資料夾時顯示建議
csharp_style_namespace_declarations = file_scoped:error
# 偏好使用檔案範圍的命名空間宣告
dotnet_diagnostic.IDE0161.severity = error # prefer file_scoped, otherwise show error
# 偏好檔案範圍命名空間,否則顯示錯誤

# using 語句規則
csharp_prefer_simple_using_statement = true
# 偏好使用簡單的 using 語句
dotnet_diagnostic.IDE0063.severity = error # prefer to use simple using statment
# 偏好使用簡單的 using 語句,違反時顯示錯誤

# 文檔註解設置
dotnet_diagnostic.CS1591.severity = none # ignore documentation missing before rosly fix #41640
# 忽略缺少文檔註解的警告(在 Roslyn 修復 #41640 之前)

# =============================================================================
# 命名規則 - 根據命名規範精簡版
# =============================================================================

# 強制執行命名規則 (IDE1006) 作為建置錯誤
dotnet_diagnostic.IDE1006.severity = error
# 命名規則違反時顯示錯誤

# 命名空間規則 - 使用 PascalCase
dotnet_style_namespace_match_folder = true
# 命名空間結構應與資料夾結構匹配

# 類型命名規則 - 使用 PascalCase
dotnet_naming_rule.types_should_be_pascal_case.severity = error
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case_style

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum, delegate
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_file_paths = **/*.cs
dotnet_naming_symbols.types.excluded_file_paths = **/Service/**, **/Services/**, **/Controller/**, **/Controllers/**, **/Dto/**, **/DTO/**, **/Dtos/**, **/DTOS/**, **/Query/**, **/Queries/**

dotnet_naming_style.pascal_case_style.required_prefix = 
dotnet_naming_style.pascal_case_style.required_suffix = 
dotnet_naming_style.pascal_case_style.word_separator = 
dotnet_naming_style.pascal_case_style.capitalization = pascal_case

# 介面命名規則 - 以 I 開頭
dotnet_naming_rule.interface_types_should_be_prefixed_with_i.severity = error
dotnet_naming_rule.interface_types_should_be_prefixed_with_i.symbols = interface_types
dotnet_naming_rule.interface_types_should_be_prefixed_with_i.style = interface_naming_style

dotnet_naming_symbols.interface_types.applicable_kinds = interface
dotnet_naming_symbols.interface_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected

dotnet_naming_style.interface_naming_style.required_prefix = I
dotnet_naming_style.interface_naming_style.required_suffix = 
dotnet_naming_style.interface_naming_style.word_separator = 
dotnet_naming_style.interface_naming_style.capitalization = pascal_case

# 私有欄位命名規則 - 以 _ 開頭
dotnet_naming_rule.private_fields_should_be_prefixed_with_underscore.severity = error
dotnet_naming_rule.private_fields_should_be_prefixed_with_underscore.symbols = private_fields
dotnet_naming_rule.private_fields_should_be_prefixed_with_underscore.style = private_field_naming_style

dotnet_naming_symbols.private_fields.applicable_kinds = field
dotnet_naming_symbols.private_fields.applicable_accessibilities = private

dotnet_naming_style.private_field_naming_style.required_prefix = _
dotnet_naming_style.private_field_naming_style.required_suffix = 
dotnet_naming_style.private_field_naming_style.word_separator = 
dotnet_naming_style.private_field_naming_style.capitalization = camel_case

# 常數命名規則 - 使用 PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = error
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = pascal_case_style

dotnet_naming_symbols.constants.applicable_kinds = field
dotnet_naming_symbols.constants.required_modifiers = const

# 枚舉命名規則 - 以 Enum 結尾
dotnet_naming_rule.enum_types_should_end_with_enum.severity = error
dotnet_naming_rule.enum_types_should_end_with_enum.symbols = enum_types
dotnet_naming_rule.enum_types_should_end_with_enum.style = enum_naming_style

dotnet_naming_symbols.enum_types.applicable_kinds = enum
dotnet_naming_symbols.enum_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected

dotnet_naming_style.enum_naming_style.required_prefix = 
dotnet_naming_style.enum_naming_style.required_suffix = Enum
dotnet_naming_style.enum_naming_style.word_separator = 
dotnet_naming_style.enum_naming_style.capitalization = pascal_case

# 方法參數和區域變數命名規則 - 使用 camelCase
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.severity = error
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.symbols = parameters_and_locals
dotnet_naming_rule.parameters_and_locals_should_be_camel_case.style = camel_case_style

dotnet_naming_symbols.parameters_and_locals.applicable_kinds = parameter, local
dotnet_naming_symbols.parameters_and_locals.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected

dotnet_naming_style.camel_case_style.required_prefix = 
dotnet_naming_style.camel_case_style.required_suffix = 
dotnet_naming_style.camel_case_style.word_separator = 
dotnet_naming_style.camel_case_style.capitalization = camel_case

# =============================================================================
# 特定資料夾的命名規則
# =============================================================================

# Service 資料夾下的類型命名規則 - 以 Service 結尾
dotnet_naming_rule.service_folder_types_should_end_with_service.severity = error
dotnet_naming_rule.service_folder_types_should_end_with_service.symbols = service_folder_types
dotnet_naming_rule.service_folder_types_should_end_with_service.style = service_naming_style

dotnet_naming_symbols.service_folder_types.applicable_kinds = class, struct, interface
dotnet_naming_symbols.service_folder_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.service_folder_types.required_file_extensions = cs
dotnet_naming_symbols.service_folder_types.required_file_paths = **/Service/**, **/Services/**

dotnet_naming_style.service_naming_style.required_prefix = 
dotnet_naming_style.service_naming_style.required_suffix = Service
dotnet_naming_style.service_naming_style.word_separator = 
dotnet_naming_style.service_naming_style.capitalization = pascal_case

# Controller 資料夾下的類型命名規則 - 以 Controller 結尾
dotnet_naming_rule.controller_folder_types_should_end_with_controller.severity = error
dotnet_naming_rule.controller_folder_types_should_end_with_controller.symbols = controller_folder_types
dotnet_naming_rule.controller_folder_types_should_end_with_controller.style = controller_naming_style

dotnet_naming_symbols.controller_folder_types.applicable_kinds = class, struct, interface
dotnet_naming_symbols.controller_folder_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.controller_folder_types.required_file_extensions = cs
dotnet_naming_symbols.controller_folder_types.required_file_paths = **/Controller/**, **/Controllers/**

dotnet_naming_style.controller_naming_style.required_prefix = 
dotnet_naming_style.controller_naming_style.required_suffix = Controller
dotnet_naming_style.controller_naming_style.word_separator = 
dotnet_naming_style.controller_naming_style.capitalization = pascal_case

# DTO 資料夾下的類型命名規則 - 以 Dto 結尾
dotnet_naming_rule.dto_folder_types_should_end_with_dto.severity = error
dotnet_naming_rule.dto_folder_types_should_end_with_dto.symbols = dto_folder_types
dotnet_naming_rule.dto_folder_types_should_end_with_dto.style = dto_naming_style

dotnet_naming_symbols.dto_folder_types.applicable_kinds = class, struct, interface
dotnet_naming_symbols.dto_folder_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.dto_folder_types.required_file_extensions = cs
dotnet_naming_symbols.dto_folder_types.required_file_paths = **/Dto/**, **/DTO/**, **/Dtos/**, **/DTOS/**

dotnet_naming_style.dto_naming_style.required_prefix = 
dotnet_naming_style.dto_naming_style.required_suffix = Dto
dotnet_naming_style.dto_naming_style.word_separator = 
dotnet_naming_style.dto_naming_style.capitalization = pascal_case

# Query 資料夾下的類型命名規則 - 以 Query 結尾
dotnet_naming_rule.query_folder_types_should_end_with_query.severity = error
dotnet_naming_rule.query_folder_types_should_end_with_query.symbols = query_folder_types
dotnet_naming_rule.query_folder_types_should_end_with_query.style = query_naming_style

dotnet_naming_symbols.query_folder_types.applicable_kinds = class, struct, interface
dotnet_naming_symbols.query_folder_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.query_folder_types.required_file_extensions = cs
dotnet_naming_symbols.query_folder_types.required_file_paths = **/Query/**, **/Queries/**

dotnet_naming_style.query_naming_style.required_prefix = 
dotnet_naming_style.query_naming_style.required_suffix = Query
dotnet_naming_style.query_naming_style.word_separator = 
dotnet_naming_style.query_naming_style.capitalization = pascal_case

# =============================================================================
# 通用命名規則 (適用於不在特定資料夾中的檔案)
# =============================================================================

# 類型命名規則 - 使用 PascalCase (不包含特定後綴)
dotnet_naming_rule.general_types_should_be_pascal_case.severity = error
dotnet_naming_rule.general_types_should_be_pascal_case.symbols = general_types
dotnet_naming_rule.general_types_should_be_pascal_case.style = general_pascal_case_style

dotnet_naming_symbols.general_types.applicable_kinds = class, struct, interface
dotnet_naming_symbols.general_types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.general_types.required_file_extensions = cs
dotnet_naming_symbols.general_types.required_file_paths = **/*.cs
dotnet_naming_symbols.general_types.excluded_file_paths = **/Service/**, **/Services/**, **/Controller/**, **/Controllers/**, **/Dto/**, **/DTO/**, **/Dtos/**, **/DTOS/**, **/Query/**, **/Queries/**

dotnet_naming_style.general_pascal_case_style.required_prefix = 
dotnet_naming_style.general_pascal_case_style.required_suffix = 
dotnet_naming_style.general_pascal_case_style.word_separator = 
dotnet_naming_style.general_pascal_case_style.capitalization = pascal_case

# =============================================================================
# 其他文件類型規則
# =============================================================================
[*.{json,js,ts,jsx,tsx}]
# JavaScript/TypeScript 文件規則
indent_size = 2
# JavaScript/TypeScript 使用 2 空格縮進

[*.{xml,html,cshtml}]
# XML/HTML 文件規則
indent_size = 2
# XML/HTML 使用 2 空格縮進

[*.{css,scss,less}]
# CSS 文件規則
indent_size = 2
# CSS 使用 2 空格縮進

[*.md]
# Markdown 文件規則
trim_trailing_whitespace = false
# Markdown 文件保留行尾空白(用於換行)
max_line_length = off
# Markdown 不限制行長度

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··492

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··334

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··268

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··218

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··217

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11