###### tags: `C#` `學習筆記` # C# 學習筆記 | Class ## namespace `namespace` 替類進行分類,如同文件夾一般 `internal class` 只能在同個 **namespace** 中使用相同的類別 如果要使用別的命名空間則有兩種方式 1. `using Mynamespace;` 2. `Mynamespace.Program p = new Mynamespace.Program();` ## 類的賦值 可以使用外部賦值 也可使用構造函數,類似Python`__init__` ```csharp internal class Customers { public string name; public string adress; public int age; public Customers() { Console.WriteLine("沒放入初始值使用這構造函數"); } //----- 構造函數 public Customers(string name, string adress, int age) { this.name = name; this.adress = adress; this.age = age; } } class Program { static void Main() { //----- 一般附值 Customers cus = new Customers(); cus.name = "Zeke"; cus.age = 24; //----- 構造函數 Customers c1 = new Customers("Zeke", "Taipei", 24); } } ``` ## 訪問權限 1. **public**,不管在哪都可使用 2. **internal**,在同一程式集才可以調用 3. **private**,只有在同一 class 中才可以使用 4. **protected**,繼承可以使用但外部不能使用 ## 屬性 可以對屬性進行操作 set 默認 value 為輸入的變數 ```csharp! internal class CustomersSetGet { private string name; private string adress; public string Name { set { name = value; } // 默認預設為value get { return name; } // 可以設置private } // 可以設置未定義變數,它會自動生成,但不推薦這麼做 public string Gender { set; get; // 簡寫 } } class Program { static void Main() { CustomersSetGet c1 = new CustomersSetGet(); c1.Name = "Zeke"; Console.WriteLine(c1.Name); } } ``` ## 記憶體存儲 ![](https://hackmd.io/_uploads/BJqHSL382.png) ### 棧 Stack 採用堆疊( Stack ),方式儲存,空間小速度快,後進先出,儲存值類型數據 值類型,`interger|bool|struct|char|float`,單獨內存,單獨定義放在stack 以下數據都儲存在棧區 1. 整數 2. 浮點數 3. 布林 4. 方法 ### 堆 Heap 空間大速度慢,以任意順序新增和移除,儲存了引用類型 引用類型,`stirng|array|class`,兩段內存 第一段放在stack指向heap或靜態儲存區的位置,表示地址 第二段放在heap或靜態儲存區的是數據本體 以下數據儲存在堆中 1. class 2. Array 3. Object ### 靜態儲存區 Static 詳細不太了解,不過字串類型 string 是被放在這裡的,而從棧或堆的字串變數都指向這邊 1. string ## struct and class 區別 一般的 class 被定義後會放在堆中,為引用類型,所以當兩個變數指向同個物件時,變化後會影響兩個 struct 不是引用類型,而是值類型,所以當A賦值給B,就算更改B,A也不會影響 適合簡單的類型 ```csharp! class Program { public static void Main() { StructStudent s1 = new StructStudent("Zeke", 20); StructStudent s2 = new StructStudent("Jack", 33); s2 = s1; s2.name = "Bob"; s2.age = 15; Console.WriteLine(s1.name); // Zeke } struct StructStudent { public string name; public int age; public StructStudent(string name, int age) { this.name = name; this.age = age; } } } ``` ## static 靜態 static 儲存在靜態儲存區,進行變動必須使用類名稱來使用 static class 無法實例化 static類只能使用static相關的變數和函數 ```csharp! static class Static { public static int mp; public static void StaticPrint() { Console.WriteLine($"staticMP: {mp}"); } } class Program { public static void Main() { Static.mp = 20; Console.WriteLine(Static.mp); } } ``` ## 繼承 當子類從父類繼承下來時,父類被稱為**基類**,子類又稱父類的**派生類** 不允許多重繼承,也就是一次繼承一個以上的父類 base and this 其實差距不大,只是比較好辨識而已 ```csharp! class Father { private string name; private int age; } class Son : Father { private string name; // private int age // 因為繼承父類,所以可以調用 public void Run() { base.name; // base and this 其實差距不大,只是比較好辨識而已 this.name; } } ``` ### virtual override 虛方法 當你需要重新建構繼承的函數時,可以使用的方法其一 :::success 和抽象類最大的不同是,他不強迫重寫函數。常用在對父類函數的擴充,而不是重寫 ::: 在父類替函數添加 virtual,且**父類可以含有函數體**,之後在子類利用 override 可以重新定義此函數 :::info 假設用父類 `Enemy e1;` 定義,用子類實例化 `e1 = new Boss();`,除了 **override** 的函數,其餘的都只能用父類的函數 ::: ```csharp! internal class Enemy { protected int hp; public virtual void Attack() { Console.WriteLine("發動攻擊"); } } internal class MegaBoss:Enemy { public override void Attack() // 虛方法,重寫Move() { Console.WriteLine("MegaBoss Attack"); } } internal class Program { public static void Main() { MegaBoss m1 = new MegaBoss(); m1.Attack() // MegaBoss Attack Enemy e1 = new MegaBoss(); MegaBoss.Attack(); // 發動攻擊 } } ``` ### new 隱藏方法 重構被繼承的函數其二 隱藏方法就只需在普通函數添加 **new** 關鍵字來重構函數 :::info 假設使用父類 `Enemy n1;` 來聲明,使用子類 `n1 = MegaBoss();` 來實例化,就只能訪問到父方法的函數了 ::: ```csharp! internal class Enemy { public void AI() { Console.WriteLine("預設的AI"); } } internal class MegaBoss:Enemy { public new void AI() // 隱藏方法 { Console.WriteLine("new AI"); } } internal class Program { public static void Main() { MegaBoss m1 = new MegaBoss(); m1.AI() // new AI Enemy e1 = new MegaBoss(); MegaBoss.AI(); // 預設的AI } } ``` ## sealed 密封 密封使用必須使用在**重寫的函數**上,當使用時代表這個函數已經被重寫 如果是放在類上代表該類不能被繼承 密封的好處是可以避免代碼混亂 ```csharp! internal class Enemy { protected int hp; public virtual void Attack() { Console.WriteLine("發動攻擊"); } } internal class MegaBoss:Enemy { public sealed override void Attack() // 密封該函數 { Console.WriteLine("MegaBoss Attack"); } } ``` ## abstact 抽象 抽象類只需要定義函數,不需要有函數體 `{}`,但是你不能實例化它 :::success 在繼承時也**必須**重寫他,這可以幫助我們在定義一個新的類的時候確保能百分百重寫這函數 ::: 抽象類就是個**模板**,可以讓人清楚知道該寫怎樣的函數 使用時要在**類**和該**函數體**加上 **abstract** ,重寫時必須使用 **override** ```csharp! abstract class Person // 要使用抽象函數必須使用抽象類 { private int hp; private int mp; public void Move() { Console.WriteLine("person who walk"); } public abstract void Attack(); // 使用 abstract 抽象化 } internal class Warrior:Person { public override void Attack() // 使用 override 重寫方法 { Console.WriteLine("戰士普通攻擊"); } } ``` ## 接口 Interface 接口和抽象類似,有函數聲明,但沒有函數體。常在開頭使用 `I` 來聲明接口,比如 : `IAnimal` 接口不允許實例化,也不能有構造函數,默認所有的函數為 `public` class 可以繼承一個以上的接口,接口也可以繼承其他接口 class 繼承後必須重寫所有的函數 ```csharp! internal interface IFly { public void Fly(); void Land(); } internal class Airplane : IFly, IMachine { public void Fly() { Console.WriteLine("飛機起飛了"); } public void Land() { Console.WriteLine("飛機降落了"); } } ``` ## Indexer 索引器 索引器(Indexer) 允許一個物件可以像陣列一樣使用索引值的方式來訪問。 使用索引器時必須使用 `this[index]` 來聲明索引器 ```csharp! internal class IndexerWeek { private string[] days = { "Mon", "Tues", "Wen", "Thurs", "Fri", "Sat" }; public int this[string day] { get { int i = 0; foreach (string item in days) { if (item == day) return i + 1; i++; } return -1; } set { } } } class Program { public static void Main() { IndexerWeek i = new IndexerWeek(); Console.WriteLine(i["Fri"]); // 5 } } ``` ## operator 運算符重載 運算符重載可以讓使用者自定義運算符的作用 通過關鍵字 `operator` 和運算符的符號来定義的。和其他函数一样,重載運算符有返回類型和参数列表 ```csharp! class Program { public static void Main() { Student s1 = new Student("Jojo"); Student s2 = new Student("Jojo"); // 原本是 false 因為數據引用的地址不一樣 // 如果有定義重載,則會按照我們的想法行動,讓它比對內部的變數是否相同 Console.WriteLine(s1 == s2); } } internal class Student { private string name; public Student(string name, int age, int id) { this.name = name; } public static bool operator == (Student a, Student b) { if(a.name == b.name) return true; return false; } public static bool operator != (Student a, Student b) { bool result = a == b; // == 已經重載了,所以使用後會使用上面的重載函數 return !result; } } ``` ## event 事件 事件(Event) 基本上是一个使用者操作,如點擊、滑鼠移動等等,或者是一些提示訊息,如系统的通知。應用程式需要在事件發生時響應事件。 且定義為事件後,只能在 `class` 內使用,如同 `private` 首先在類中聲明委託 ```csharp! delegate void SubscribeDelegate(); ``` 之後使用 `event` 關鍵字 ```csharp! public event SubscribeDelegate Subscribe = null; ``` 以訂閱和發部的設計模式為範例 ```csharp! public event SubscribeDelegate Subscribe = null; // 實例化發佈者 Publisher toolman = new Publisher("工具人"); // 實例化訂閱者 Subscriber man1 = new Subscriber("懶人一"); Subscriber man2 = new Subscriber("懶人二"); Subscriber man3 = new Subscriber("懶人三"); ``` 在外部調用只能進行**添加**和**刪除**,不能進行**賦值**和**執行** ```csharp! // 進行訂閱 toolman.Subscribe += man1.Mission1; toolman.Subscribe += man2.Mission2; toolman.Subscribe += man3.Mission1; //toolman.Subscribe = man3.Mission2; // 不能賦值 //toolman.Subscribe(); // 不能直接執行 // 執行發佈 toolman.ExecuteMission(); ```