C#のコンパイルエラーの対処例を書いてみる

# まえおき
C#布教活動しているなかで、初心者ごろしなのがコンパイルエラーな気がしてきたので<strike>、トラブルシューティング的なものを書いてみたい</strike>。
結果、思ってたのと違う記事が出来上がった。
というわけで、読み物としてお楽しみください。

# やってること
わざとコンパイルが通らないコードを書いて、コンパイルエラーを潰していく様を書いてみる。
※この記事全体でいえることですが、<b>別なコードを書いている場合は、同じコンパイルエラーでも、とるべき対処法は変わります。</b>

# 結局のところ
場当たり的にコンパイルエラー潰すより、<b>文法を学ぶなり、まっとうなサンプルコードを見るなり</b>したほうが、トータルでみると早くて、まともな設計ができるようになると思うので、<strike>この記事は意味がない。</strike><b>ちゃんと勉強しましょう。</b>


# 環境
Windows10 で、コマンドプロンプトで実行。
<b>この記事では、パスが通せていることを前提にしています。</b>

`where csc`の結果：
`C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe`


# 本題に入る前に - Visual Studio向けのソースコードをもってきたケース

`'InitializeComponent' は現在のコンテキスト内に存在しません`
というコンパイルエラーがでる場合。

`InitializeComponent`は、通常、Visual Studioを使っている前提のソースコードです。
(`Form`のコードなどがツールで自動生成されたものです。)
コマンドプロンプト環境でコンパイルするのはハードル高いです。
<b>この記事を見ても解決できません。</b>ごめんなさい。

# 本題 - Step.0 - べた書き

Hello Worldをやりたくて下記みたいなコードを書いたとします。
(ふつうはコンパイル通るコードをコピペしてHello Worldやると思いますが。)

```csharp:BadExample.cs

WriteLine(HelloWorld);
```

`csc BadExample.cs`と打ってコンパイルすると、下記のようにおこられます。

```
BadExample.cs(1,1): error CS0116: A namespace cannot directly contain members such as fields or methods

■日本語の場合：
badExample.cs(1,1): error CS0116: 名前空間にフィールドやメソッドのようなメンバーを直接含めることはできません。
```

C#で処理を書くときはクラスで囲めということです。
※「うっかり」以外でこのエラーが出るようなコーディングをしている場合は、いますぐコンパイル作業をやめて<b>C#の文法を学びましょう。</b>
(まずは、長くても数十行くらいの<b>コンパイルが通るサンプルコード</b>を持ってきて、なんとなくの書き方を知るのがよいかと。)

# Step.1 - コンパイルエラーが増えることもある

Step.0に対してクラスで囲んで対処。

```csharp:BadExample.cs

class {
    WriteLine(HelloWorld);
}
```

↓コンパイルエラー

```
BadExample.cs(1,7): error CS1001: Identifier expected
BadExample.cs(2,5): error CS1520: Method must have a return type
BadExample.cs(2,25): error CS1001: Identifier expected

■日本語の場合：
BadExample.cs(1,7): error CS1001: ID がありません。
BadExample.cs(2,5): error CS1520: メソッドは戻り値の型を持たなければなりません。
BadExample.cs(2,25): error CS1001: ID がありません。
`ファイル名.cs(行番号,文字位置): error CS1518: Expected class, delegate, enum, interface, or struct`
```

むしろコンパイルエラーが増えました。<b>１つ潰すとコンパイルエラーが増えることはよくあります。</b>

なお、慣れないうちはコンパイルエラーは<b>先頭から潰しましょう。</b>

エラーメッセージのフォーマットは、
`ファイル名(行番号,文字位置): error エラー番号: エラーの説明 ` です。
特に「行番号」は大きな手掛かりになります。

# Step.2

Step.1への対策として、ID(いわゆる識別子)を付ける。

```csharp:BadExample.cs

class BadExample {
    WriteLine(HelloWorld);
}
```

コンパイルしてみます。
すると、下記コンパイルエラーとなります。

```

BadExample.cs(2,5): error CS1520: Method must have a return type
BadExample.cs(2,25): error CS1001: Identifier expected

■日本語の場合：
BadExample.cs(2,5): error CS1520: メソッドは戻り値の型を持たなければなりません。
BadExample.cs(2,25): error CS1001: ID がありません。
```

# Step.3 - コンパイラの解釈とプログラマの意図がずれている

Step.2のコンパイルエラーは、
「このコードを書いた人はきっと『`BadExample`クラスが`WriteLine`メソッドを持っているコードを書きたい。』のだろう。」と解釈してエラーメッセージをだしています。

```csharp:コンパイラが解釈している、Step.2の正しいコード（あくまでイメージです）

class BadExample {
    戻り値の型 WriteLine(引数) {
    ：
    }
}
```

実際にやりたいことは、`WriteLine`を呼び出して「Hello World」を表示したい。（この記事ではそういう設定と思ってください。）
なので、下記のように修正してみます。

```csharp:BadExample.cs

class BadExample {
    void BadMethod() {
        WriteLine(HelloWorld);
    }
}
```

コンパイルしてみます。
すると、下記コンパイルエラーとなります。

```

BadExample.cs(3,9): error CS0103: The name 'WriteLine' does not exist in the current context
BadExample.cs(3,19): error CS0103: The name 'HelloWorld' does not exist in the current context

■日本語の場合：
BadExample.cs(3,9): error CS0103: 名前 'WriteLine' は現在のコンテキスト内に存在しません。
BadExample.cs(3,19): error CS0103: 名前 'HelloWorld' は現在のコンテキスト内に存在しません。
```

エラーが、「〇〇は現在のコンテキスト内に存在しません。」だけになりました。
ようやく構文エラーが消えました。
構文エラーの場合、<b>直接の原因でない箇所でもコンパイルエラーがでたりする</b>ので、まずは構文エラーを潰すのが定石だと思います。（括弧の閉じ忘れとか、識別子が１個抜けてるとか、セミコロンを忘れているとかとか。）
Visual Studio Codeなどの、<b>構文を</b>ある程度解釈して<b>色分け表示してくれるようなエディタや統合開発環境を選べば</b>、うっかり系の構文エラーはかなり減らせます。


# Step.4 - クラスを探す（Microsoft Docsがおすすめ）

Step.3では、コンパイラが`WriteLine`がないと言っているので、検索キーワード`writeline site:microsoft.com`とかでググるなりします。
(今回のように、クラスを探すだけなら`site:microsoft.com`を付けるのがおすすめです。)

「Console.WriteLine - Microsoft Docs」
https://docs.microsoft.com/ja-jp/dotnet/api/system.console.writeline?view=netframework-4.8
が見つかるはずなので、開きます。(`Console`を知っているというテイで、ご容赦ください。。)

> 名前空間:System 
> Assemblies:System.Console.dll, mscorlib.dll, netstandard.dll

とかかれているはずです。(別のクラスにも`WriteLine`があるので注意。)
というわけで、名前空間をつなげて`System.Console.WriteLine`としてあげると、コンパイラがこれを特定できます。
(なお、`using 名前空間;`と宣言しておくと、`名前空間.`は省略できます。
※`Console`はクラス名なので省略できません[^1]。)

[^1]: 「省略できません」はちょっと嘘ですが、この記事を読む段階では知らないほうがよい気がします。(`using static`使えば、`static`なクラスについては省略可能になる。`Math`クラスとか使いまくる場合はいりようになるかもしれない。)

```csharp:BadExample.cs

class BadExample {
    void BadMethod() {
        System.Console.WriteLine(HelloWorld);
    }
}
```


コンパイルしてみます。
すると、下記コンパイルエラーとなります。

```

BadExample.cs(3,34): error CS0103: 名前 'HelloWorld' は現在のコンテキスト内に存在しません。
```

# Step.5 - 文字列について（ちょっと脱線）

文字列は`""`で囲む。<strike>(記事を書いている途中で、このミスは「わざとらしすぎる」ので、最初から"HelloWorld"にしようかと思ったが、警告内容が変わってめんどくさいので、そのままにした。)</strike>

ちなみに`@"xxx"`とすると、エスケープ文字を処置しなくて済むので、ファイルパスとかを埋め込むときとか正規表現書くときはこっちのほうが楽。

ダメな例：`"c:\tmp.txt"` (※`\t`はタブ文字と解釈される。`\t`に限らず、基本的にエスケープをミスっててもコンパイルは通ってしまうケースが多い。)
OKな例：`"c:\\tmp.txt"` もしくは `@"c:\tmp.txt"`

エスケープ文字や`"`を含まない文字列については、`@`付きの表記のほうがミスしにくいのでお勧めしたい。

```csharp:BadExample.cs

class BadExample {
    void BadMethod() {
        System.Console.WriteLine(@"HelloWorld");
    }
}
```

コンパイルしてみます。
すると、下記コンパイルエラーとなります。

```

error CS5001: Program 'c:\(中略)\BadExample.exe' does not contain a static 'Main' method
 suitable for an entry point

■日本語の場合：
error CS5001: プログラム 'c:\(中略)\BadExample.exe' は、エントリ ポイントに適切な静的
 'Main' メソッドを含んでいません
```

# Step.6 - Mainメソッドがないとプログラムの入口がわからない

`Main`メソッドがないよ。
※C言語ユーザーは違和感あるかもしれませんが、mainではなく<b>M</b>ainです。

<strike>ググってC#のMainの書き方を調べたことにしてください。</strike>
`[STAThread]`をつけ忘れてハマると可哀想なので、つけておきます。

```csharp:BadExample.cs

class BadExample {
    [STAThread]
    static void Main() {
        System.Console.WriteLine(@"HelloWorld");
    }
}
```

<strike>これでいけるでしょ。完成！</strike>
↓コンパイルエラー

```

BadExample.cs(2,6): error CS0246: The type or namespace name 'STAThread' could not be found (are you missing a using
        directive or an assembly reference?)
BadExample.cs(2,6): error CS0246: The type or namespace name 'STAThreadAttribute' could not be found (are you missing a
        using directive or an assembly reference?)

■日本語の場合：
BadExample.cs(2,6): error CS0246: 型または名前空間名 'STAThread' が見つかりませんでした。using
        ディレクティブまたはアセンブリ参照が不足しています。
BadExample.cs(2,6): error CS0246: 型または名前空間名 'STAThreadAttribute' が見つかりませんでした。using
        ディレクティブまたはアセンブリ参照が不足しています。
```

# Step.7

`ディレクティブまたはアセンブリ参照が不足しています。`は、名前空間の指定が足りていなくて、コンパイラが見つけられていないケースが多いです。
※コンパイルオプションが必要な場合もあります。[^2]

[^2]: `csc`の`/r:DLLへのパス`オプションでアセンブリ参照を追加しないといけないケースがあります。(Excel使う場合とか。大体は`C:\Windows\assembly\`以下を`dir /s 名前空間名.dll`すればでてくると思います。参考: https://www.atmarkit.co.jp/fdotnet/dotnettips/846extratfromgac/extratfromgac.html  )

`site:microsoft.com` をつけてググります。

> STAThreadAttribute Class 
> 名前空間:System 
> Assemblies:System.Runtime.dll, mscorlib.dll, netstandard.dll

`System` に存在しているので、

```csharp:BadExample.cs

using System;

class BadExample {
    [STAThread]
    static void Main() {
        Console.WriteLine(@"HelloWorld");
    }
}
```

という風にすればコンパイル通ります。
下記でも問題ないですが、`STAThread`に`System`つけているのはあまり見ないスタイル(※個人の感想です)。

```csharp

class BadExample {
    [System.STAThread]
    static void Main() {
        System.Console.WriteLine(@"HelloWorld");
    }
}
```

`BadExample.exe`の実行結果：

```

HelloWorld
```

もう<b>Bad</b>Exampleじゃないけど、名前変えるとミスしそうなのでこのまま。

・・・見てくれた方はありがとうございます。お疲れさまでした！

# おまけ - cscの出力メッセージを英語にする。

コンパイラのメッセージを英語と日本語の両方を載せたいと思い、調べた。

コマンドプロンプトで `chcp 437` を実行するとcscの警告内容も英語に変えられる。日本語(Shift_JIS)は`chcp 932`。
参考: 
http://pocketstudio.jp/log3/2012/02/13/change_language_and_chcp/
https://www.ipentec.com/document/windows-codepage-list