C#入門系列【自定義類型】:從青銅到王者的進階之路
一、引言:為什么需要自定義類型?
在C#的世界里,系統自帶的類型(如int
、string
、bool
)就像是基礎武器,能解決一些簡單問題。但當你面對復雜的業務場景時,就需要像英雄聯盟里的英雄一樣,打造屬于自己的"終極武器"——自定義類型。
比如,你要開發一個游戲,需要描述一個角色:
- 只用系統類型:
string name = "蓋倫"; int level = 18; double health = 616.28; string[] skills = { "致命打擊", "勇氣", "審判", "德瑪西亞正義" };
- 用自定義類型:
var garen = new Hero { Name = "蓋倫", Level = 18, Health = 616.28,Skills = new[] { "致命打擊", "勇氣", "審判", "德瑪西亞正義" } };
哪個更簡潔明了?一目了然!
二、自定義類型的"三劍客"
1. 類(Class):最全能的戰士
類就像是游戲中的全能型英雄,既能扛傷害(封裝數據),又能打輸出(提供方法)。
public class Hero
{// 字段:英雄的屬性private string _name;// 屬性:英雄的公開特性public string Name { get => _name; set => _name = value ?? throw new ArgumentNullException("名字不能為空"); }public int Level { get; set; } = 1; // 默認1級public double Health { get; set; }public string[] Skills { get; set; }// 方法:英雄的技能public void Attack() => Console.WriteLine($"{Name}使用了普通攻擊!");public void UseSkill(int skillIndex){if (skillIndex < 0 || skillIndex >= Skills?.Length){Console.WriteLine($"{Name}沒有這個技能!");return;}Console.WriteLine($"{Name}使用了技能:{Skills[skillIndex]}!");}
}
使用示例:
var ezreal = new Hero
{Name = "探險家",Level = 15,Health = 520.36,Skills = new[] { "秘術射擊", "精華躍動", "奧術躍遷", "精準彈幕" }
};ezreal.UseSkill(3); // 輸出:探險家使用了技能:精準彈幕!
2. 結構(Struct):輕量級特種兵
結構就像是游戲中的特種兵,身材小巧(值類型),但行動迅速(無需堆內存分配)。
public struct Point
{public double X { get; set; }public double Y { get; set; }// 構造函數public Point(double x, double y) => (X, Y) = (x, y);// 方法:計算到原點的距離public double DistanceToOrigin() => Math.Sqrt(X * X + Y * Y);// 重寫ToString方法public override string ToString() => $"({X}, {Y})";
}
使用示例:
var point = new Point(3, 4);
Console.WriteLine($"點{point}到原點的距離是:{point.DistanceToOrigin()}");
// 輸出:點(3, 4)到原點的距離是:5
類 vs 結構:
特性 | 類(引用類型) | 結構(值類型) |
---|---|---|
存儲位置 | 堆(Heap) | 棧(Stack)或字段 |
拷貝方式 | 引用拷貝 | 值拷貝 |
默認初始值 | null | 各字段的默認值 |
適合場景 | 復雜對象、需要繼承 | 輕量級數據、頻繁創建銷毀 |
3. 枚舉(Enum):游戲中的技能欄
枚舉就像是游戲中的技能欄,把一組相關的值放在一起,方便選擇和使用。
public enum HeroType
{Warrior, // 戰士Mage, // 法師Assassin, // 刺客Tank, // 坦克Support, // 輔助Marksman // 射手
}// 為Hero類添加Type屬性
public class Hero
{// 其他成員保持不變...public HeroType Type { get; set; }
}
使用示例:
var yasuo = new Hero
{Name = "亞索",Type = HeroType.Assassin,Skills = new[] { "斬鋼閃", "風之障壁", "踏前斬", "狂風絕息斬" }
};Console.WriteLine($"{yasuo.Name}是一名{yasuo.Type}型英雄。");
// 輸出:亞索是一名Assassin型英雄。// 更友好的輸出
string GetTypeName(HeroType type) => type switch
{HeroType.Warrior => "戰士",HeroType.Mage => "法師",HeroType.Assassin => "刺客",HeroType.Tank => "坦克",HeroType.Support => "輔助",HeroType.Marksman => "射手",_ => type.ToString()
};Console.WriteLine($"{yasuo.Name}是一名{GetTypeName(yasuo.Type)}型英雄。");
// 輸出:亞索是一名刺客型英雄。
三、進階技巧:讓自定義類型更強大
1. 繼承與多態:英雄的"覺醒"
通過繼承,你可以讓英雄獲得更強大的能力。
// 基類:所有英雄的共同特性
public abstract class BaseHero
{public string Name { get; set; }public int Level { get; set; } = 1;// 抽象方法:必須由子類實現public abstract void UltimateSkill();// 虛方法:子類可以重寫public virtual void Attack() => Console.WriteLine($"{Name}進行了普通攻擊。");
}// 子類:戰士英雄
public class WarriorHero : BaseHero
{public override void UltimateSkill() => Console.WriteLine($"{Name}使用了終極技能:神羅天征!");public override void Attack() => Console.WriteLine($"{Name}使用了戰技:突刺!");
}// 子類:法師英雄
public class MageHero : BaseHero
{public override void UltimateSkill() => Console.WriteLine($"{Name}使用了終極技能:隕石天降!");public override void Attack() => Console.WriteLine($"{Name}使用了魔法:火球術!");
}
多態演示:
BaseHero[] heroes = new BaseHero[]
{new WarriorHero { Name = "趙云" },new MageHero { Name = "諸葛亮" }
};foreach (var hero in heroes)
{hero.Attack();hero.UltimateSkill();Console.WriteLine("----------------");
}// 輸出:
// 趙云使用了戰技:突刺!
// 趙云使用了終極技能:神羅天征!
// ----------------
// 諸葛亮使用了魔法:火球術!
// 諸葛亮使用了終極技能:隕石天降!
// ----------------
2. 接口:英雄的"裝備"
接口就像是游戲中的裝備,只要英雄"穿上"(實現),就能獲得相應的能力。
// 可遠程攻擊接口
public interface IRemoteAttack
{void RemoteAttack();
}// 可控制接口
public interface IControl
{void ControlEnemy();
}// 射手英雄:實現多個接口
public class MarksmanHero : BaseHero, IRemoteAttack, IControl
{public override void UltimateSkill() => Console.WriteLine($"{Name}使用了終極技能:萬箭齊發!");public void RemoteAttack() => Console.WriteLine($"{Name}進行了遠程攻擊!");public void ControlEnemy() => Console.WriteLine($"{Name}使用了控制技能:致盲!");
}
接口使用:
var jinx = new MarksmanHero { Name = "金克絲" };
jinx.RemoteAttack(); // 金克絲進行了遠程攻擊!
jinx.ControlEnemy(); // 金克絲使用了控制技能:致盲!
3. 泛型:英雄的"萬能鑰匙"
泛型就像是游戲中的萬能鑰匙,可以適配各種場景。
// 裝備槽:可以存放任何類型的裝備
public class EquipmentSlot<T>
{private T _equipment;public void Equip(T equipment){Console.WriteLine($"裝備了:{equipment}");_equipment = equipment;}public T Unequip(){Console.WriteLine($"卸下了:{_equipment}");var temp = _equipment;_equipment = default;return temp;}
}// 使用示例
var weaponSlot = new EquipmentSlot<string>();
weaponSlot.Equip("無盡之刃"); // 裝備了:無盡之刃
var weapon = weaponSlot.Unequip(); // 卸下了:無盡之刃
四、常見陷阱與避坑指南
1. 結構的"甜蜜陷阱"
結構是值類型,頻繁裝箱拆箱會影響性能。
錯誤示范:
var points = new List<Point>();
for (int i = 0; i < 1000000; i++)
{points.Add(new Point(i, i)); // 大量結構實例,可能導致性能問題
}
解決方案:
- 對于大數據量,優先使用類
- 使用
Span<T>
或Memory<T>
避免裝箱
2. 枚舉的"數字游戲"
枚舉默認是int
類型,可能導致意外賦值。
錯誤示范:
HeroType type = (HeroType)99; // 無效的枚舉值,但編譯通過
解決方案:
- 使用
[Flags]
特性創建位標志枚舉 - 在使用前驗證枚舉值有效性
if (!Enum.IsDefined(typeof(HeroType), type)) {Console.WriteLine("無效的英雄類型!"); }
3. 繼承的"深淵巨口"
過度繼承會導致代碼復雜度爆炸,就像游戲中技能點加錯了一樣。
錯誤示范:
public class TankWarriorMageHero : BaseHero // 多重職責,違反單一職責原則
{// ...
}
解決方案:
- 優先使用組合而非繼承
- 遵循SOLID設計原則
- 使用接口實現多角色能力
五、總結:自定義類型的"通關秘籍"
-
選擇合適的類型:
- 類:復雜對象,需要繼承和多態
- 結構:輕量級數據,頻繁創建銷毀
- 枚舉:固定值集合,提高代碼可讀性
-
善用高級特性:
- 繼承:擴展功能,實現多態
- 接口:定義契約,實現橫向擴展
- 泛型:提高代碼復用性
-
避開常見陷阱:
- 注意結構的性能問題
- 驗證枚舉值的有效性
- 避免過度繼承
掌握了這些,你就能在C#的世界里像職業選手一樣,靈活運用各種自定義類型,輕松應對各種復雜的業務場景,成為代碼界的"王者"!