CodeGym /コース /C# SELF /クラスとオブジェクト宣言時によくあるミス

クラスとオブジェクト宣言時によくあるミス

C# SELF
レベル 25 , レッスン 0
使用可能

1. 名前がかぶる問題:フィールド、プロパティ、パラメータ

初心者はよく、プロパティ名、コンストラクタのパラメータ名、プライベートフィールド名を同じにしちゃうことがある。コンパイラは怒らないけど、思った通りの動きにならないことが多いよ。

public class Student
{
    private string name;

    public Student(string name)
    {
        name = name; // 「一見正しそうだけど…」
    }

    public void PrintName()
    {
        Console.WriteLine(name);
    }
}

どうなる?PrintName()メソッドは何も出力しない:プライベートフィールドnamenullのまま!なぜなら、コンストラクタの中でname = name;はローカルパラメータだけをいじってて、クラスのフィールドには触れてないから。

どうすればいい?

クラスのフィールドやプロパティにアクセスするにはthisを使おう:

public Student(string name)
{
    this.name = name; // これでOK!
}

メモ: Riderとか他のIDEはこういうミスを教えてくれるから、ヒントは無視しないでね!

2. デフォルトコンストラクタがない:必要なときは?

自分でコンストラクタを1つでも宣言したら、コンパイラはもう「空の」デフォルトコンストラクタを作ってくれない。これ、特にパラメータなしでオブジェクトを作ろうとしたときによくエラーになる。

public class Book
{
    public string Title { get; set; }

    // 明示的なコンストラクタ
    public Book(string title)
    {
        Title = title;
    }
}

var book = new Book(); // コンパイラ:「ビービー!パラメータなしのコンストラクタがないよ!」

パラメータあり・なし両方でインスタンスを作りたいなら、必要なコンストラクタをちゃんと追加しよう:

public Book() { }

public Book(string title)
{
    Title = title;
}

3. アクセス修飾子のミス:プライベートなパブリックピープル

初心者がよくやるのは、アクセス修飾子を考えずに書いちゃうこと。デフォルトでクラスのフィールドやメソッドはprivate、構造体のメンバーもprivate。明示しないと、外から使いたいプロパティやメソッドが使えなくなることも。

public class Animal
{
    string name; // デフォルトでprivate

    public void PrintName()
    {
        Console.WriteLine(name);
    }
}

var animal = new Animal();
animal.name = "バルシク"; // コンパイルエラー!

コンパイラはプライベートメンバーへのアクセスを許してくれない。修飾子はちゃんと明示しよう:

public string Name;

または

private string name;

もっとスマートにアクセスしたいならプロパティを使おう:

public string Name { get; set; }

4. フィールドの初期化忘れ:nullトラップ

めっちゃよくあるのが、クラスのフィールドを初期化し忘れること。特に、明示的な初期化前にメソッドで使っちゃうときは注意。

public class Cat
{
    public string Name;

    public void SayHello()
    {
        Console.WriteLine($"ニャー!名前の長さは{Name.Length}文字だよ!"); // NullReferenceExceptionの可能性あり
    }
}

var cat = new Cat();
cat.SayHello(); // 悲劇!

Namenullで、LengthにアクセスしようとするとNullReferenceExceptionになる。オブジェクト生成後の状態には特に気をつけて!

アドバイス: 重要なフィールドはコンストラクタで必ずセットしよう。新しいC#ならrequiredでプロパティを必須にできるよ。

5. オートプロパティの無限再帰:コピペミス

プロパティを手書きするとき、getterやsetterの中で自分自身を参照しちゃって無限ループになることがある。

public class Dog
{
    public int Age
    {
        get { return Age; } // あれ?
        set { Age = value; } // あれあれ?
    }
}

このコードはAgeを呼ぶたびに自分自身を呼び続けて…スタックが尽きるまで無限ループ(再帰地獄)。

正しい書き方

プライベートフィールドを使おう:

private int age;

public int Age
{
    get { return age; }
    set { age = value; }
}

またはオートプロパティを使おう:

public int Age { get; set; }

6. 名前の一貫性がない

プログラミングでは大文字小文字や名前の正確さが大事。C#はCase-Sensitiveだから、NamenameNAMEは全部別物。

フィールドやプロパティの名前を変えたら、全部の場所でちゃんと変えたか確認しよう!Riderはリファクタリングを助けてくれるけど、似た名前が多いとタイプミスは防げないこともあるよ。

7. 間違った修飾子の使用(static/instance)

インスタンスメソッドとstaticメソッドを混同しちゃうことがある。C#の文法で許されてない呼び方をするとエラーになるよ。

public class MathHelper
{
    public static int Add(int a, int b) => a + b;
    public int Multiply(int a, int b) => a * b;
}

var sum = MathHelper.Add(2, 3); // OK
var mul = MathHelper.Multiply(2, 3); // エラー!

Multiplyはstaticじゃないから、クラス名からは呼べない。オブジェクトを作ってから呼ぼう:

var helper = new MathHelper();
var mul = helper.Multiply(2, 3); // これでOK

8. コンストラクタ継承時のミス

クラスが他のクラスを継承してる場合、親クラスのコンストラクタにパラメータが必要なことを忘れないで。

public class Animal
{
    public string Name;

    public Animal(string name)
    {
        Name = name;
    }
}

public class Dog : Animal
{
    public Dog() { } // エラー:親クラスはnameが必要!
}

コンパイラはこういうコンストラクタを許してくれない。親クラスのコンストラクタを明示的に呼ぼう:

public class Dog : Animal
{
    public Dog(string name) : base(name) { }
}

9. インターフェースの全メンバーを実装し忘れ

クラスがインターフェースを実装してるのに、全部のメソッドを実装してなかったら、コンパイルエラーになる:「クラスがインターフェースを完全に実装していません」って言われるよ。

public interface IWalker
{
    void Walk();
    void Run();
}

public class Turtle : IWalker
{
    public void Walk()
    {
        Console.WriteLine("ゆっくり、でも確実に…");
    }
    // あれ?Runを忘れた!
}

IDEが怒って、Turtleは全メソッドを実装するまでコンパイルできないよ。

10. 初期化してないオブジェクトの使用

C#ではオブジェクトはnewで明示的に作る。変数だけ宣言しても、オブジェクトを代入しなければnull。フィールドやメソッドにアクセスしようとすると有名なNullReferenceExceptionが出る。

Student student;
student.PrintName(); // サプライズ!

必ずオブジェクトを初期化しよう:

Student student = new Student("バシリサ");
student.PrintName();

11. publicフィールドをプロパティの代わりに使うミス

昔からある「アンチパターン」:クラスのフィールドを全部publicにしちゃう。なぜダメ?フィールドが無防備、変更のコントロールができない、カプセル化が壊れる!

public class Car
{
    public int speed; // こうしないで!
}

プロパティを使おう:

public class Car
{
    public int Speed { get; set; }
}

プロパティならチェックやロジック、制限も追加できる:

private int speed;
public int Speed
{
    get { return speed; }
    set
    {
        if (value < 0) speed = 0;
        else speed = value;
    }
}

12. 「マジックナンバー」や文字列を直接書いちゃうミス

コードの中に42"イワン""SomeFile.txt"みたいな「マジック」な値が直接書いてあったら、それはミスの元。定数やフィールドを使おう。

public class ConfigManager
{
    public void Save()
    {
        File.WriteAllText("config.txt", "some data"); // マジック文字列!
    }
}

こうしよう:

public class ConfigManager
{
    private const string ConfigFileName = "config.txt";
    public void Save()
    {
        File.WriteAllText(ConfigFileName, "some data");
    }
}

13. 参照型変数のコピー:同じオブジェクトを指してるミス

C#のオブジェクト変数は参照型。変数を別の変数に代入すると、オブジェクト自体じゃなくて参照だけコピーされる。これで「いつの間にか」値が変わることがある。

Student s1 = new Student("バシャ");
Student s2 = s1;
s1.Name = "コリャ";
Console.WriteLine(s2.Name); // 「コリャ」と出る!

なぜ?s1s2は同じオブジェクトを指してるから!

14. エラー時のフィードバックがない

クラスが不正な値を許しちゃうのはダメ。例えば、マイナスの年齢とか。

public class Human
{
    public int Age { get; set; }
}

var h = new Human();
h.Age = -50; // 問題なし…

クラスのロジックで変な値を防ごう:

private int age;
public int Age
{
    get { return age; }
    set
    {
        if (value < 0)
            throw new ArgumentException("年齢はマイナスにできません。");
        age = value;
    }
}

15. スコープ(有効範囲)の混乱

同じ名前の変数をメソッド内とクラスのフィールドで宣言しちゃうことがある。

public class MyClass
{
    int value = 10;

    public void Foo()
    {
        int value = 99;
        Console.WriteLine(value); // 99が出る、10じゃない!
    }
}

クラスのフィールドに確実にアクセスしたいならthis.valueを使おう。

16. overrideなしでメソッドをオーバーライドしちゃう

親クラスの仮想メソッドをオーバーライドしたいときは、overrideを忘れずに!

public class Animal
{
    public virtual void Speak() => Console.WriteLine("動物が話す");
}

public class Cat : Animal
{
    public void Speak() => Console.WriteLine("ニャー!"); // overrideじゃない!
}

この場合、親クラスのメソッドはオーバーライドされてない:新しいメソッドは古いのを隠してるだけ(シャドウイング)。親クラスの参照で呼ぶと予想外の動きになるよ。

正しくは:

public override void Speak() => Console.WriteLine("ニャー!");

17. コンストラクタから仮想メソッドを呼ぶ

これはコンパイルエラーじゃないけど、バグの元。親クラスのコンストラクタで仮想メソッドを呼ぶと、子クラスでオーバーライドされてる場合、呼び出し時に子クラスのフィールドがまだ初期化されてないことがある。

public class Animal
{
    public Animal()
    {
        Speak();
    }

    public virtual void Speak()
    {
        Console.WriteLine("動物…");
    }
}

public class Cat : Animal
{
    public string Name { get; set; } = "バルシク";

    public override void Speak()
    {
        Console.WriteLine($"ニャー!私は{Name}");
    }
}

Cat cat = new Cat();
// Speak()が呼ばれる時、Catのコンストラクタはまだ終わってなくて、Nameはnull!

18. コンストラクタだけでオブジェクトを定義(プロパティなし)

クラスにコンストラクタしかなくて、作った後にオブジェクトを変更できない場合、不変オブジェクトにはいいけど、たいていは使いにくい。普通はプロパティで値を変えられるようにした方が便利。

19. 標準の命名規則(naming conventions)を無視

クラス名は大文字始まり、メソッドも大文字始まり、フィールドは小文字+アンダースコア(またはcamelCase)、定数は全部大文字。スタイルを統一すると読みやすくなるし、ミスも減るよ!

20. 使ってないフィールドやメソッド

フィールドを作ったのにどこでも使ってなかったら、それは「死んだコード」。IDEが教えてくれることもある。死んだコードが少ないほど、アプリのメンテも楽になるよ!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION