###### 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);
}
}
```
## 記憶體存儲

### 棧 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();
```