初心者向けよくあるエラーとその対処法(C#,VB.Net)

# 紹介
業務でVisual Studio2022を利用して、Webアプリ(VB.Net)を開発しています（歴3年）。また、C#での開発も勉強中です。あの時知っていれば、と思ったエラーと対処法の共有です。

これを知ることで、自分のコードでエラーが起きた際、正しく対処できるはずです。

# 前提 & 環境
Visual Studio 2022を利用して、C#とVB.Netのサンプルコードを書きました。
初学者向けに、コードを簡単に書いている部分も一部あります。
VB.Netでは、実行でエラーとなるが、C#では、コンパイラでエラーとなるコードもあるため、完全に一致していないサンプルコードもあります。
<br>
Windows 11 home
Visual Studio Community 2022 17.14.3
C# .netFramework 4.7.2
VB.Net Framework 4.7.2

# 内容
エラーを英語で書いていますが、日本語でも説明をするので、
安心してください。

- NullReferenceException
- IndexOutOfRangeException
- InvalidCastException
- FormatException
- まとめ




<details><summary>1.NullReferenceException</summary>


## 1.NullReferenceException
一言でいうと「中身がないものを使おうとして失敗したエラー」です。
サンプルコードで、確認をしてみましょう。

C#
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            
            string sampleString = null;

            int sampleLength = sampleString.Length;


        }
    }
}
```

VB.Net
```
Option Strict Off
Module Module1

    Sub Main()

        Dim sampleString As String

        Dim sampleLength As Integer = sampleString.Length

    End Sub

End Module
```
このコードを実行した際のメッセージを確認してみましょう。
C#
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/ab602bac-5b9d-4791-855d-e36cfd0e74ce.png)

VB.Net
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/cc132b85-07bc-4842-bccf-8cba01fcd41c.png)

同様のメッセージですよね(nullとNothingで表現は違いますが)。
どちらも「中身がないものを使おうとして失敗したエラー」という形になります。
プログラマーには、Nullが好きな方はいないと思います。
こんな簡単なミスはしないぞと思う方もいると思いますが、
コードを長く書いたり、複雑なものだったり、共同で作業を行ったりすると起こしてしまいます。
C#の方は、意図的に変数にNullを仕込みました。
また、VB.Netの方は構文ミスです。

対処法のプログラムを書いてみます。

C# 対処法1
Nullが入る可能性のある場合は、Nullでないかを判定する。
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {

            string sampleString = null;

            //nullでないか判定
            if (sampleString != null)
            {
                int sampleLength = sampleString.Length;

            }
                
            
            

        }
    }
}
```

C# 対処法2
Nullを扱わない。
Null→""とする。
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {

            string sampleString = "";

            int sampleLength = sampleString.Length;
            
            
            //int sampleInt;

            //sampleInt = sampleInt + 1;

        }
    }
}
```

上記の方法が挙げられます。
*付け加えると、C#8.0では、プロジェクト単位、ファイル単位でのNullの禁止が行えるようになっています。新規かつNullを使わないのであれば、そちらの設定を行うとよいかと思います。

VBの方も挙げます。

VB.Net Nullでないかを判定する。

説明　Option Strict On
暗黙の型変換を許さない。必ず、型変換を行うこと!
Nullが入る可能性のある場合

```
Option Strict On
Module Module1

    Sub Main()

        Dim sampleString As String

        'Nullでないか判定する
        If Not IsNothing(sampleString) Then
            Dim sampleLength As Integer = sampleString.Length
        End If

    End Sub

End Module
```

VB.Netの場合、初期値を入れる。
変数 As String = ""とする。
```
Option Strict On
Module Module1

    Sub Main()

        Dim sampleString As String = ""

        'Nullでないか判定する
        If Not IsNothing(sampleString) Then
            Dim sampleLength As Integer = sampleString.Length
        End If

    End Sub

End Module
```

VB.Netの頭につけているOption Strict Onについて
VB.Netは型変換が緩いため、つけておくべき規則です。勝手にStringをIntegerに入れようとすると、コンパイラがエラーを起こしてくれます。
「本番コードでは Option Strict On を推奨」します。
実行前に気づける点が利点です！

### コメント
変数にNullが、入るかもしれないことを想定しましょう！

</details>

<details><summary>2.IndexOutOfRangeException</summary>

次のエラーです。
一言でいうと、「存在しない番号にアクセスしたエラー」です。
どちらも配列を利用していますが、実行時にエラーとなります。
C#とVB.Netで異なるプログラムを書いています。

C#の場合
配列の範囲外の値を指定
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] oneDimArray = new int[3]{ 1, 2 ,3};
            
            int thirdValue = 0;

            thirdValue = oneDimArray[3];

            

        }
    }
}
```

VBの場合、
For文を使用
Lengthの値まで（0,1,2,3まで）増えていっています。
```
Option Strict Off
Module Module1

    Sub Main()

        '配列
        Dim oneDimArray() As Integer = {1, 2, 3}

        '合計
        Dim sum As Integer = 0

        For i = 0 To oneDimArray.Length

            sum = sum + oneDimArray(i)

        Next

    End Sub

End Module
```

C#もVB.Netも配列の要素は、0から始まっていきます。
なので、1から数えた値を入れたり、Length - 1を入れないと、
エラーが起きてしまいます。

実際に動かしてみるデバッグの時でないと、気づかなかったりするので注意が必要です。
実行時のエラー

C#の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/85b1b2ea-3b35-459f-b1cd-6ec68ffbc999.png)

VB.Netの場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/39dc5962-6f4a-4a3b-8cba-72ac2ca939e2.png)

それぞれのプログラムを直してみました。
最後の値を取り出したい場合は、要素数 - 1と書くことで、取り出せます。
C#の場合
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            int[] oneDimArray = new int[3] { 1, 2, 3 };

            int thirdValue = 0;

            thirdValue = oneDimArray[oneDimArray.Length - 1];



        }
    }
}
```
*下のVB.Netの例のようにFor Eachを利用することが、C#でも可能です。

VB.Netの場合
誤りの可能性があるFor文を使わずにFor Each文を利用して
配列の要素を1つずつ取り出すことで、対応しています。
```
Option Strict Off
Module Module1

    Sub Main()

        '配列
        Dim oneDimArray() As Integer = {1, 2, 3}

        '合計
        Dim sum As Integer = 0

        For Each value As Integer In oneDimArray

            sum = sum + value

        Next

    End Sub

End Module
```

### コメント2
配列といったまとまりを扱う場合は、範囲外の値を使わないように、気をつけましょう。

</details>

<details><summary>3.InvalidCastException</summary>



続いてのエラーは、
無効な型変換
「変えられない型に無理やり変えようとしたエラー」
例）文字列を数値に変えようとしたけど、できなかった。
といったケースです。


C#の場合
Object型の値を無理やり数値型に変更しようとしているため、エラーが起きます。
```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            object sampleObject = "123";


            int intSample = (int)sampleObject + 1;


        }
    }
}
```

VB.Netの場合です。
C#と同様に、型変換でエラーが起きています。
```
Option Strict On
Module Module1

    Sub Main()

        Dim value As Object = "123"
        Dim number As Integer = DirectCast(value, Integer) + 1


    End Sub

End Module
```

実際に実行してみましょう。
C#の場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/be583ee2-6fca-419b-be21-a47513d6cddd.png)

VB.Netの場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/e787b256-b437-40ea-a127-3f57dbb41980.png)

といった具合で、Object型から、いきなり変換しようとしてはいけません。
こういった場合、値が入っていない（Nullの場合）も考慮して、下記のように書きます。

C#の場合
!= を利用して、nullでないことを確認。
&& を利用。左から1つ目の条件式が成り立たない場合、2つ目以降の式は、実行されない。
int.TryParseを使用
（1つ目の式の値をint型に変換、その結果を引数に入れる）。
int.TryParseは、型変換に失敗した場合は、False。成功した場合は、Trueを返す。
ToStringを使用（Object型の値を文字列として変換する、nullに対して行うとエラーが起きる）。

```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            object sampleObject = "123";

            int intSample = 0;

            if (sampleObject != null && int.TryParse(sampleObject.ToString(), out intSample))
            {

                intSample = intSample + 1;
            }



        }
    }
    }
```

VB.Netの場合
IsNothingで、null(Nothing)出ないことを確認。
AndAlsoは、を利用。左から1つ目の条件式が成り立たない場合、2つ目以降の式は、実行されない。
Integer.TryParseを利用する。（1つ目の式の値をint型に変換、その結果を引数に入れる）。
型変換に失敗した場合は、False。成功した場合は、Trueを返す。
ToStringを使用（Object型の値を文字列として使用する。nullに対して行うと、エラーが起きる）。
```
Option Strict On
Module Module1

    Sub Main()


        Dim value As Object = "123"
        Dim number As Integer = 0

        If Not IsNothing(value) AndAlso Integer.TryParse(value.ToString(), number) Then

            number = number + 1

        End If


    End Sub

End Module
```

### コメント3
指定した変数の型で変換できない場合を想定しましょう。

</details>

<details><summary>4.FormatException</summary>


一言でいうと、指定外の書式エラー
先に挙げたInvalidCastExceptionと紛らわしいですが、
こちらは、値自体が変換できないものだった場合に起きます。
例）そもそも変換できない文字列を整数にしようとした」

C#の例です。
ABCという文字列を整数に変換しようとしています。

```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string hoge = "ABC";

            int hogeInt = int.Parse(hoge);
        }
    }
}

```

VB.Netの場合、
C#と同様に、ABCDEという文字列を整数に変換しようとしています。

```
Option Strict Off
Module Module1

    Sub Main()


        Dim hoge As String = "ABCDE"

        Dim hogeInt As Integer = Integer.Parse(hoge)


    End Sub

End Module
```

それぞれの実行結果を見てみます。

C#の場合、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/376e5808-ce14-4d7a-9657-507653f27f06.png)

VB.Netの場合、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537494/66f9eb07-c172-48c0-8d7c-7d23dfbb7c96.png)

これらのプログラムのエラー対策は、先に挙げたInvalidCastExceptionのプログラムと同様です。

修正例
C#の場合
InvalidCastExceptionとも一部重なりますが、
hoge != null はnull出ないことを確認
&& を利用。左から1つ目の条件式が成り立たない場合、2つ目以降の式は、実行されない。
TryParseで変換（1つ目の式で、string型の値をint型に変換。2つ目の式に結果の値を入れる）。
型変換に失敗した場合は、False。成功した場合は、Trueを返す。

文字列型 stringなので、ToString()は使う必要がないといった形です。

```
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ErrorProgram
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string hoge = "ABC";

            int hogeInt = 0;

            if (hoge != null && int.TryParse(hoge, out hogeInt))
            {
                //hogeIntへの処理
            }

        }
    }
}

```

VB.Netの場合
Not IsNothing(hoge)はNullでないことを確認
AndAlso を利用。左から1つ目の条件式が成り立たない場合、2つ目以降の式は、実行されない。
TryParseで変換（1つ目の式で、String型の値をint型に変換。2つ目の式に結果の値を入れる）。
型変換に失敗した場合は、False。成功した場合は、Trueを返す。

文字列型 Stringなので、ToString()は使う必要がないといった形です。

```
Option Strict On
Module Module1

    Sub Main()


        Dim hoge As String = "ABCDE"

        Dim hogeInt As Integer = 0

        If Not IsNothing(hoge) AndAlso Integer.TryParse(hoge, hogeInt) Then

            'hogeIntの処理

        End If

    End Sub

End Module

```

### コメント4
ユーザー入力は、変換に失敗する可能性あることも、想定しておきましょう！

といった具合です。
お疲れ様です。

</details>

<details><summary>5.まとめ</summary>

まとめ表
<table>
<thead>
<tr>
<th>例外(Exception)</th>
<th>原因</th>
<th>防ぎ方</th>
</tr>
</thead>
<tr>
<td>NullReference</td>
<td>Nullに触れた</td>
<td>Nullかをチェック or 非Null値を入れる</td>
</tr>
<tr>
<td>IndexOutOfRange</td>
<td>範囲外に出た</td>
<td>Length-1 or foreach</td>
</tr>
<tr>
<td>InvalidCast</td>
<td>無効な変換</td>
<td>TryParse,TryCast</td>
</tr>
<tr>
<td>Format</td>
<td>形式が問題あり</td>
<td>TryParse</td>
</tr>
</thead>
</table>


コードと見比べてみた結果は、どうでしょうか？
エラーは、プログラムの実行の失敗を伝えてくれる。
バグは、プログラマーが埋め込んでしまう失敗です。

業務を行う上では、バグを避けるため、慎重に扱いましょう。
例え、エラーだったとしても見逃してしまったら、
それはバグとなってしまいます。

VB.NetやC#を利用している方々の少しでも助けになればと思います。

</details>