tech.guitarrapc.cóm

Technical updates

C#のコード設定を.editorconfigで統一する

.editorconfigは様々な言語でコードの設定を統一するための設定ファイルです。C#も.editorconfigを使うとコードフォーマットやアナライザーの設定できます。今回は私がこれまでC#で.editorconfigを使ってきておすすめ設定と避けたほうがいいと考えている設定を紹介します。

おすすめ設定

リポジトリルートに配置するベース設定としての.editorconfigを示します。charsetend_of_lineはOSや言語によって考慮が変わるため、その部分は適宜変更する必要があるため後段で補足説明します。

なお、MicrosoftのC#のコード規約に合わせたルール設定は含んでいませんが、これも.editorconfigで明示できますし個人のVS/Rider設定に依存しないのでオススメです。

root = true

# target to global files like .yaml, .json, .md, .gitignore and .gitattributes
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.cs]
indent_style = space
indent_size = 4

# Depends on your OS
end_of_line = unset # if you want to use git's default
# end_of_line = crlf # NOT RECOMEND, If you checkout on Windows
# end_of_line = lf # NOT RECOMEND, if you never checkout on Windows

# Depends on your language
charset = utf-8-bom # If you use Japanese, use utf-8-bom to avoid garbage character on git by shift-jis
# charset = utf-8 # If you use English only like OSS project

[*.{csproj,props}]
indent_style = space
indent_size = 2

[Dockerfile]
indent_style = space
indent_size = 4

[{appsettings.json,appsettings.*.json}]
indent_style = space
indent_size = 2

[*.cs]

# C# Standards
dotnet_diagnostic.CS1998.severity = none # This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

# Nullable Reference Types
dotnet_diagnostic.CS8618.severity = error # Non-nullable field is uninitialized. Consider declaring as nullable.
dotnet_diagnostic.CS8604.severity = error # Possible null reference argument.
dotnet_diagnostic.CS8629.severity = error # Nullable value type may be null.
dotnet_diagnostic.CS8600.severity = error # Converting null literal or possible null value to non-nullable type.
dotnet_diagnostic.CS8603.severity = error # Possible null reference return.
dotnet_diagnostic.CS8610.severity = error # Nullability of reference types in type of parameter doesn't match overridden member.
dotnet_diagnostic.CS8625.severity = error # Cannot convert null literal to non-nullable reference type.
dotnet_diagnostic.CS8606.severity = error # Possible null reference assignment to iteration variable
dotnet_diagnostic.CS8602.severity = error # Dereference of a possibly null reference.
dotnet_diagnostic.CS8601.severity = error # Possible null reference assignment.
dotnet_diagnostic.CS8614.severity = error # Nullability of reference types in type of parameter doesn't match implicitly implemented member.
dotnet_diagnostic.CS8765.severity = error # Nullability of type of parameter 'obj' doesn't match overridden member (possibly because of nullability attributes).
dotnet_diagnostic.CS8619.severity = error # Nullability of reference types in value of type 'GenericType<T?>' doesn't match target type 'GenericType<T>'.

# =================
# Analyzer Rulesets
# =================

# Microsoft.CodeAnalysis.BannedApiAnalyzers
dotnet_diagnostic.RS0030.severity = error # RS0030: Banned API

# Microsoft.VisualStudio.Threading.Analyzers
dotnet_diagnostic.VSTHRD100.severity = error # VSTHRD100: Avoid async void methods
dotnet_diagnostic.VSTHRD101.severity = error # VSTHRD101: Avoid unsupported async delegates
dotnet_diagnostic.VSTHRD110.severity = error # VSTHRD110: Observe result of async calls
dotnet_diagnostic.VSTHRD003.severity = none  # VSTHRD003 Avoid awaiting foreign Tasks

# =================
# Format Rulesets
# =================

# IDE0160: Convert to file-scoped namespace
csharp_style_namespace_declarations = file_scoped:warning

# Microsoft.Analyzers.ManagedCodeAnalysis
dotnet_diagnostic.CA2200.severity = error # Rethrow to preserve stack details

# =================
# Too Match Detail Rulesets
# =================

# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings?
dotnet_diagnostic.IDE0005.severity = warning # Remove unnecessary imports
dotnet_diagnostic.IDE0051.severity = warning # Remove unused private member
dotnet_diagnostic.IDE0052.severity = warning # Remove unread private member
dotnet_diagnostic.IDE0079.severity = none # Remove unnecessary suppression
dotnet_diagnostic.IDE0090.severity = none # Simplify new expression

考慮が必要な設定

対象のファイル

.editorconfigはファイル拡張子やファイル名をターゲットとして指定するため、感覚としては.gitattributesに似ています。ということは注意点も似ているということです。例えば、*.csはC#のソースコードを指定しますが、*.csはC#のプロジェクトファイルではありません。このため、*.csproj*.propsも指定する必要があります。

# C#のソースコードが対象
[*.cs]

# C#のプロジェクトファイルが対象
[*.{csproj,props}]

また、*は全ファイルを対象にする非常に強力な指定です。どのファイルでも原則とする設定を差し込んておいて、調整したいファイル拡張子に対して別途指定することでオーバーライドすると設定がスリムになります。例えば上記.editorconfigでは、原則改行コードはlf、charsetをutf-8としています。その後、yamlやjson、csファイルを対象にインデントやcharsetを調整しています。

# 全ファイルが対象
[*]
indent_style = space
indent_size = 2

OSを考慮したeol設定

残念ながら.editorconfigはOSの違いを判別する機能を持たないため、OSごとに設定を変えることはできません。1このため、OSを考慮すると「git configのautocrlfに任せて無指定かunset」がおすすめです。もし利用するOSを固定できるなら、WindowsかLinux/macOSでend_of_lineを選択するのもいいでしょうが、あまり指定するメリットはないと考えます。避けたい設定で、Windows環境でlfを指定するとおこる弊害を説明します。

[*.cs]
end_of_line = unset # gitのautocrlfに任せるのがおすすめ
OS end_of_line 備考
両OSで利用する場合 unset gitのリモート、ローカルEOLに任せる
Windows crlf eolで悩まないためにgit config core.autocrlf=trueを前提とする
Linux/macOS lf Windowsではないのでlfになることが期待される

言語を考慮したcharset設定

日本語を用いるプロジェクトと英語のみを用いるプロジェクトでcharsetの考慮が変わります。Visual Studioは日本語を含むとshift-jisでファイル保存しようとするのですがgitでshift-jisは文字化けを起こしトラブルの原因となります。このため、コード上で日本語を用いるプロジェクトではcharset=utf-8-bomを用いてGitでも日本語表示できるようにしつつ、ローカルでも日本語を文字化けさせないのがオススメです。

[*.cs]
charset = utf-8-bom # 日本語を含む場合、utf-8-bomにするとgitで文字化けを防ぎつつVisual Studioでも日本語を表示できる

一方、英語(ascii)のみを用いるプロジェクトではcharset=utf-8を用いて良いでしょう。ただ、Roslynチームはcharset=utf-8-bomを用いていることから、utf-8-bomが古臭い以外に使わない積極的な理由もないと認識しています。2

[*.cs]
charset = utf-8 # 英語だけならutf-8で十分
言語 charset 備考
日本語を用いるプロジェクト utf-8-bom Gitで日本語を表示し、ローカルでも日本語を文字化けさせない
英語のみを用いるプジェクト utf-8 一般的な設定

アナライザー設定

C#のアナライザー設定も.editorconfigで設定できます。アナライザーごとの警告レベルを調整できるので、プロジェクトで統一したアナライザー設定を展開するときに利用するといいでしょう。例えば、Nullable Reference Typesを導入したプロジェクトでは、dotnet_diagnostic.CS8618.severity = errorを設定することで、警告をエラーに昇格させることができます。nullableを必須にできるので、プロジェクトの初期から有効にするのがオススメです。設定可能な値はConfiguration options for code analysis | Mirosoft Learnで公開されています。

[*.cs]
dotnet_diagnostic.xxxxx.severity = error|warning|info|none

Microsoft LearnにEditorConfigをVisual Studioで利用する方法が紹介されています。ファイルの配置についても詳しく説明されているので、これまで知らなかった人は参考にしてください。

C#のコード規約

C#のコード規約も.editorconfigで設定できます。多くのプロジェクトで採用されているであろう「MicrosoftのC#コード規約」も.editorconfigで設定することで、プロジェクト全体でコード規約を統一できます。設定可能な値は、Code style rule options | Mirosoft Learnで公開されています。例えばdotnet_style_qualification_for_method = falseで「this.を省略させたい」意図がわかります。

[*.cs]
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false

Visual Studio以外にRiderも.editorconfigを尊重しますし、VS CodeもEditorConfig for VS Codeをインストールすることで.editorconfigを尊重します。.editorconfigを尊重するエディター/IDEを使っていると、プロジェクトにおけるコードフォーマットを統一できていいですね。

避けたい設定

ファイルごとの設定

可能であればファイルごとに.editorconfigでルールを調整するのは避けたほうがいいです。.gitattributesもそうですが、エディターによるファイル管理とこういった設定は連動しません。このため、ファイル指定での設定はファイルのパス変更やファイル名変更しても設定を変え忘れて効かなくなる可能性があります。

想像してください、何かしらの理由でディレクトリを移動させてgit commit、push、PRマージした後、ほかの人がpullしたときに.editorconfigが効かなくなって、原因が.editorconfigでファイル名やファイルパス指定したからだった時を。私はeditorconfigで設定したことに気づく自信はありませんし、気づくためにエラーを出す仕組みも.editorconfigは持ちません。

# 避ける
[foo/bar.json]
indent_size = 4
[piyo/buz.json]
indent_size = 2

# 可能なら拡張子でまとめる。設定がずれているなら併せられないかまず検討する
[*.json]
indent_size = 2

Windowsでend_of_lineをlfに指定する

過去数年lfを試してきたのですが、Windows環境のC#プロジェクトで改行コードをlfに統一するのは避けた方がいいです。git config core.autocrlf = trueが好ましいという背景込みですが、end_of_line=lfにしていると「Visual StudioのCRLFとLF混在時のダイアログ」がファイルを開くたびに発生してめんどくさいだけです。

image

end_of_lineを指定しないということは、C#のRaw String Literalなどファイルの改行コードが文字列改行コードになるもので環境依存が起こります。例えば、テストコードで改行コードを意識しないとWindowsで通るのにLinuxでこけた経験はないでしょうか。このようなケースはString.ReplaceLineEndings.Replace("\r\n", "\n")で文字列の改行コードをLFに統一できます。めんどくさい側面はありますが、利用者の環境でテスト結果が左右するよりコードで解決するほうがいいと考えます。

なお、.editorconfigでlfを設定していてもcrlfが混じる原因はいくつかあるようですが、個人的にはgit config core.autocrlf=trueが原因のことが多く感じます。とはいえ、core.autocrlf=trueはWindowsにおいてOS間の互換性を向上させるために有効な設定なので、これを有効にするのを前提にします。

なぜcore.autocrlfをtrueにするのか

蛇足なので、興味がある人だけ読んでください。

core.autocrlf=trueはWindows専用設定でOS間の互換性を向上させます。

具体的には、クローン/チェックアウト時は「リモートにlfで保存されているファイルをローカルではcrlfに変換」し、リモートプッシュ時は「ローカルのcrlfなファイルをlfに変換」するので、リポジトリ内部は常にlfで統一されます。特にUnityを使っている環境でEOLに伴うGit差分を考慮する必要がなくなるため、git config core.autocrlf=trueはWindowsだけの環境でも有効にしておくのがオススメです。Gitプッシュ時に、EOLによる差分が他の人に影響するかを気にしないで済むのはありがたいです。ただし.shなどLFが必須なファイルで困るので.gitattributesで個別設定を併用しましょう。

git config core.autocrlf=falseをオススメする記事やgistをよく見かけますが、falseが引き起こす問題は割と大きいです。例えば、Windowsで誤ってcrlfでプッシュしたファイルをLinuxやmacOSでチェックアウトすると、改行コードがcrlfのままになり、コードの差分が出てしまいます。また、チーム内でOSが混在していたり、人によってcore.autocrlf=trueが混じっていると、不要な差分やマージ時にコンフリクトが発生しえます。プロジェクトで最も大事なのは、リモートリポジトリの安定性なのを考えると、core.autocrlf=trueが最も安定していると考えます。

core.autocrlf=trueでもcore.autocrlf=falseのいずれを使うにしても、チーム内で統一しましょう。

Visual Studioで.editorconfigは調整しない

Visual Studioで.editorconfigファイルを開くと、GUIでdotnetアナライザー設定を調整できます。ただ、.editorconfigに存在しなかったアナライザ設定が差分検出されてしまいます。この差分が1行2行ならいいのですが存在しない設定全てを差分とするので、部分だけ設定している環境では100行近く差し込まれることでしょう。このため、Visual Studioの.editorconfigは閲覧専用にして、設定を変更するときはエディターで行うのがオススメです。

例えば先に示した.editorconfigをVSで開くと次のようなGUIが開き、ファイルの*から差分が発生したとわかります。

image

ファイルを保存すると大量の差分が生じています。ちょっとアグレッシブすぎますね。

image

幸いにしてアナライザーの設定はCode analysis | Microsoft Learnで公開されています。Code style rule optionsやCode quality rulesを見ると、どのような設定・値が可能かがわかります。

まとめ

C#のコード向けに.editorconfigを設定する背景情報をあまり見かけないので、これまで設定競合などを起こしながら着地したおすすめ設定と避けたほうがいい設定をまとめました。コード規約はMicrosoftのC#コード規則とするOSSやプロジェクトが多い印象です。多人数が関わるプロジェクトでは、editorconfigでコード規約を設定すると明解かつ個人のIDE/エディター設定に依存せず、IDEのフルサポートを受けられるのでオススメです。

あと、以前書いた記事のようにdotnet formatのフォーマットも制御できますしね。

参考


  1. 過去にautocrlfサポートが提案されていましたが投票の結果は却下でした。
  2. dotnet/runtimeの.editorconfigは英語で統一されているためかcharsetが指定されていないです。ASP.NETCoreではutf-8が指定されています。