オブジェクトへの参照

バグの例
まずバグの例を見てください。

1行分の文字列を保持する Data クラスを使用して、
ファイルの各 1 行を Data クラスのリストを使って処理をする
という簡単なプログラム例です。

Program.cs
プログラムのエントリポイントです。昔で言うところの Main 処理に該当します。

using Sample1;

SampleClass sampleClass = new();
sampleClass.SampleMethod();

Data.cs
各行の内容を保持する Data クラスです。

namespace Sample1
{
    internal class Data
    {
        public int Row { get; set; }
        public string Line { get; set; }

        public Data()
        {
            Row = 0;
            Line = string.Empty;
        }

        public Data(int row, string line)
        {
            Row = row;
            Line = line;
        }
    }
}

SampleClass.cs
実際の主処理を行う SampleClass です。

namespace Sample1
{
    internal class SampleClass
    {
        public void SampleMethod()
        {

            List<Data> list = new();
            Data data = new();

            int row = 0;
            foreach (string line in File.ReadLines(@"input.txt"))
            {
                row++;
                data.Row = row;
                data.Line = line;
                list.Add(data);
            }

            foreach (Data _data in list)
            {
                Console.WriteLine("{0}:{1}", _data.Row, _data.Line);
            }
        }
    }
}

input.txt
このプログラムへ入力する input.txt です。
Visual Studio を使用する場合は、プロジェクトにテキストファイルを追加して、
以下のように適当なデータを 3 行ぐらい用意して保存してください。
さらに、プロパティで [出力ディレクトリにコピー] を [常にコピーする] に設定してください。

あいうえお
かきくけこ
さしすせそ

このプログラムを実行するとコンソールに

3:さしすせそ
3:さしすせそ
3:さしすせそ

と最後の行を 3 回表示します。
これは期待していた出力内容ではありません。

バグの原因
では、なぜバグになってしまったのでしょうか。
それは、オブジェクトを保持する変数への代入は、「参照渡し」だからです。
例示したプログラムでは Data クラスはループ外で 1 度しか new しておらず、
ループ内で list に追加しています。プログラムが保持しているオブジェクトを図示するとこうです。

「参照渡し」なので list には同じ Data オブジェクトの参照が Add されます。
最後に Data オブジェクトに格納したのは、3 行目のデータですので、
3 行目のデータを毎回表示するわけです。

バグ修正
まずはバグ修正したものを図示しておきます。

このように各行を処理する度に Data オブジェクトを用意し、
そのオブジェクトへの参照を list に追加しなければいけません。

SampleClass.cs
修正した SampleClass です。

namespace Sample1
{
    internal class SampleClass
    {
        public void SampleMethod()
        {

            List<Data> list = new();

            int row = 0;
            foreach (string line in File.ReadLines(@"input.txt"))
            {
                row++;
                Data data = new();
                data.Row = row;
                data.Line = line;
                list.Add(data);
            }

            foreach (Data _data in list)
            {
                Console.WriteLine("{0}:{1}", _data.Row, _data.Line);
            }
        }
    }
}

このようにループ内で Data オブジェクトを作成します。
もう一度ビルドして実行すると、

1:あいうえお
2:かきくけこ
3:さしすせそ

今度は正しい結果が得られました。
では、今回のまとめです。

まとめ
オブジェクトを格納する変数は、オブジェクトへの参照を保持しているだけです。
オブジェクトの実データを保持しているわけではありません。

課題
例示したサンプルプログラムはまだ改良の余地を残しています。
どこをどんな風に直すのが適切なのか考えてみてください。

(Data クラスのコンストラクタにヒントがあります。)