CodeGym /課程 /C# SELF /宣告 class 和 object 時常見的錯誤

宣告 class 和 object 時常見的錯誤

C# SELF
等級 25 , 課堂 0
開放

1. 名稱衝突問題:欄位、屬性、參數

新手常常遇到 property、constructor 參數和 private 欄位名稱一樣的情況。這時候 compiler 不會報錯,但結果可能跟你想的不一樣。

範例

public class Student
{
    private string name;

    public Student(string name)
    {
        name = name; // "看起來沒問題..."
    }

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

會發生什麼事?PrintName() 這個方法啥都印不出來:private 欄位 name 會一直是 null!因為 constructor 裡的 name = name; 只是在操作 local 參數,不是 class 的欄位。

怎麼避免

this 關鍵字來存取 class 的欄位或 property:

public Student(string name)
{
    this.name = name; // 這樣就對了!
}

小提醒: Rider 或其他 IDE 會幫你抓這種 bug,別忽略他們的提示!

2. 沒有預設 constructor:什麼時候會需要?

只要你自己寫了一個 constructor,compiler 就不會自動幫你生一個「空的」預設 constructor。這很容易出錯,尤其是你想要不帶參數 new 一個 object 的時候。

範例

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

    // 明確宣告的 constructor
    public Book(string title)
    {
        Title = title;
    }
}

var book = new Book(); // Compiler: "嗶嗶!沒有無參數 constructor!"

如果你想要同時支援有參數和無參數的建立方式,記得要自己加上需要的 constructor:

public Book() { }

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

3. 存取修飾詞:public 人的 private 生活

新手常犯的錯誤之一就是沒想清楚存取修飾詞。class 的欄位和方法預設是 private,struct 的成員也是 private。沒明講或忘記寫,會導致 class 外面根本用不到你想用的 property 或 method。

範例

public class Animal
{
    string name; // 預設 private

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

var animal = new Animal();
animal.name = "Барсик"; // 編譯錯誤!

compiler 不會讓你存 private 成員。正確做法是明確寫出修飾詞:

public string Name;

或是

private string name;

更好的做法是用 property 來存取:

public string Name { get; set; }

4. 忘記初始化欄位:null 陷阱

超常見的陷阱——class 的欄位沒初始化。尤其是這個欄位會在還沒明確設定前就被 method 用到時。

範例

public class Cat
{
    public string Name;

    public void SayHello()
    {
        Console.WriteLine($"喵!名字長度 {Name.Length} 個字!"); // 可能會 NullReferenceException
    }
}

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

變數 Namenull,你一呼叫 Length 就會噴 NullReferenceException。object 剛 new 出來時的狀態一定要特別注意!

建議: 用 constructor 來強制設定重要欄位,property 也可以標記成必填(新版 C# 用 required)。

5. 自動屬性無限遞迴:複製貼上大災難

有時候手動寫 property,結果 getter 或 setter 不小心又呼叫自己(不是 private 欄位),就會進入死循環。

範例

public class Dog
{
    public int Age
    {
        get { return Age; } // 哎呀!
        set { Age = value; } // 更慘!
    }
}

這種 code 只要呼叫 Age,就會自己呼叫自己... 無限下去(直到 stack 爆掉——遞迴自殺)。

正確寫法

用一個 private 欄位來存值:

private int age;

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

或直接用自動 property:

public int Age { get; set; }

6. 名稱不一致

寫 code 要很小心大小寫和命名。C# 是區分大小寫的語言(Case-Sensitive),NamenameNAME 是三個不同的識別字。

還有,如果你改了欄位或 property 的名字,記得所有地方都要一起改!Rider 可以幫你 refactor,但還是要小心拼錯(尤其有很多相似名字時)。

7. 用錯修飾詞(static/instance)

新手有時會搞混 instance method 跟 static method。問題通常出在你想用不對的方式呼叫欄位或 method,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,不能直接用 class 名稱呼叫,必須先 new 一個 object:

var helper = new MathHelper();
var mul = helper.Multiply(2, 3); // 這樣才對

8. 繼承 constructor 時的錯誤

如果你的 class 有繼承別的 class,記得 base class 可能會要求 constructor 傳參數。

範例

public class Animal
{
    public string Name;

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

public class Dog : Animal
{
    public Dog() { } // 錯誤:base class 需要 name!
}

compiler 不會讓你這樣寫 constructor。要明確呼叫 base class 的 constructor:

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

9. 忘記實作 interface 的所有成員

如果你的 class 實作了一個 interface,卻沒把所有 method 都寫出來——compiler 會報錯:「class 沒有完整實作 interface」。

範例

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

public class Turtle : IWalker
{
    public void Walk()
    {
        Console.WriteLine("慢慢來,穩穩的...");
    }
    // 哎呀!Run 忘了!
}

IDE 會警告你,Turtle 這個 class 只要沒把所有 interface method 都寫出來就不能編譯。

10. 用到沒初始化的 object

在 C#,object 一定要用 new 明確建立。如果你只是宣告一個 reference 變數,沒給它 object——它就是 null。你一呼叫欄位或 method 就會遇到超有名的 NullReferenceException

範例

Student student;
student.PrintName(); // 驚喜!

一定要先初始化 object:

Student student = new Student("Василиса");
student.PrintName();

11. 錯誤:public 欄位而不是 property

老掉牙的「反模式」:把所有 class 欄位都宣告成 public。為什麼不好?欄位沒保護,沒辦法控管變更,封裝性全毀!

範例

public class Car
{
    public int speed; // 千萬別這樣!
}

最好用 property:

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

property 可以加檢查、邏輯、限制:

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

12. 不小心寫出「魔法數字」或「魔法字串」

如果你在 code 裡看到 42"Иван""SomeFile.txt" 這種「魔法」值直接寫在 method 裡——這很容易出錯。最好用常數或欄位來存。

範例

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. 複製錯誤:reference 變數指向同一個 object

在 C#,object 變數是 reference。你把一個變數指定給另一個,只是複製 reference,不是複製 object 本身。這常常會導致「突然」的變化。

範例

Student s1 = new Student("Вася");
Student s2 = s1;
s1.Name = "Коля";
Console.WriteLine(s2.Name); // 會印出 "Коля"!

為什麼?因為 s1s2 都是指向同一個 object!

14. 錯誤時沒有回饋

很糟的是 class 允許你設定不合理的值。比如說,負的年齡。

範例

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

var h = new Human();
h.Age = -50; // 沒問題...

class 的邏輯應該要保護不讓這種值出現:

private int age;
public int Age
{
    get { return age; }
    set
    {
        if (value < 0)
            throw new ArgumentException("年齡不能是負數。");
        age = value;
    }
}

15. 區域變數名稱混淆(scope)

有時候你在不同 scope 宣告同名變數——method 裡和 class 欄位一樣名。

範例

public class MyClass
{
    int value = 10;

    public void Foo()
    {
        int value = 99;
        Console.WriteLine(value); // 會印 99,不是 10!
    }
}

要明確存取 class 欄位,用 this.value

16. 沒有用 override 關鍵字覆寫 method

你想要覆寫 base class 的 virtual method,記得一定要加 override

範例

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

public class Cat : Animal
{
    public void Speak() => Console.WriteLine("喵!"); // 沒有 override!
}

這樣 base class 的 method 其實沒被覆寫:新 method 只是把舊的蓋掉(shadowing),用 base reference 會出現奇怪的行為。

正確寫法:

public override void Speak() => Console.WriteLine("喵!");

17. 在 constructor 裡呼叫 virtual method

這不會編譯錯,但很容易出現難 debug 的 bug。如果你在 base class 的 constructor 裡呼叫 virtual method,而這個 method 在子 class 有 override——呼叫時子 class 的欄位可能還沒初始化。

範例

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 的 constructor 還沒跑完,Name 還是 null!

18. 只用 constructor 描述 object(沒有 property)

如果 class 只有 constructor,object 建立後就不能改——這對 immutable object 很好,但大多數情況還是要讓使用者能改 property(或至少部分 property)。

19. 忽略標準命名規範(naming conventions)

class 名稱要大寫開頭,method 也是,欄位小寫加底線(或 camelCase),常數全部大寫。統一風格不只好讀,也能減少出錯機率!

20. 沒用到的欄位和方法

你宣告了欄位但沒用到——這就是「死 code」。IDE 會幫你標出來。死 code 越少,app 越好維護!

2
任務
C# SELF, 等級 25, 課堂 0
上鎖
修正名稱重複的錯誤並使用關鍵字 `this`
修正名稱重複的錯誤並使用關鍵字 `this`
2
任務
C# SELF, 等級 25, 課堂 0
上鎖
新增預設建構子
新增預設建構子
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION