---
# System prepended metadata

title: C# 學習筆記 | Class
tags: [學習筆記, C#]

---

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




