1. 名前がかぶる問題:フィールド、プロパティ、パラメータ
初心者はよく、プロパティ名、コンストラクタのパラメータ名、プライベートフィールド名を同じにしちゃうことがある。コンパイラは怒らないけど、思った通りの動きにならないことが多いよ。
例
public class Student
{
private string name;
public Student(string name)
{
name = name; // 「一見正しそうだけど…」
}
public void PrintName()
{
Console.WriteLine(name);
}
}
どうなる?PrintName()メソッドは何も出力しない:プライベートフィールドnameはnullのまま!なぜなら、コンストラクタの中で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(); // 悲劇!
Nameはnullで、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だから、Name、name、NAMEは全部別物。
フィールドやプロパティの名前を変えたら、全部の場所でちゃんと変えたか確認しよう!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); // 「コリャ」と出る!
なぜ?s1とs2は同じオブジェクトを指してるから!
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が教えてくれることもある。死んだコードが少ないほど、アプリのメンテも楽になるよ!
GO TO FULL VERSION