深入淺出 C# 4/e 第11章 物件之死 心得分享


分類

建立時間: 2022年12月16日 16:06
更新時間: 2022年12月19日 01:47

心得

此章一開始討論的是討物件的生命週期,關於物件的記憶體被回收的機制,人終將一死,程式也有死的時候,有時候你會看到比較大型的系統生命週期,尤其是在遊戲開發時,Start -> Update -> Destroy 這種經典的生命週期,隨然有點偏底層,但內容還是相當不錯,後面還會提到 struct 型態,以及方法中的參數的其他用法,例如 out, ref 修飾詞等等,還有很多寫程式的小技巧,建議讀者不要跳過這章。

物件完成項

我比較常聽到的是解構函式 destructor,一個是建構子,一個是解構子,這樣比較好記。

一般高階語言會自動幫我們處理釋放資源的工作,但如果你要是放非受控資源,或者有需要在物件被記憶體回收之前處理一些工作,就可以在解構子上執行。

EvilClone.cs

public class EvilClone
{
    public static int CloneCount;

    public int CloneID
    {
        get;
    } = ++CloneCount;

    public EvilClone() => Console.WriteLine(
        "Clone #{0} is wreaking havoc",
        CloneID);

    ~EvilClone()
    {
        Console.WriteLine("Clone #{0} destroyed", CloneID);
    }
}

~EvilClone() 就是解構子

Dispose

在上一章有提到釋放讀寫資源時,使用 using 陳述式或者是實作 IDisposable 介面,因為解構子在處理記憶體回收機制時,不知道哪個物件會先被回收,如果在解構子裡面用到其他的物件參考,有可能那個參考已經被回收了,就會發生未預期的狀況,所以 Dispose 還是有使用的需要。

struct

此章介紹新的型態 struct,它很像物件,但他不是物件,更不是類別,不能繼承其他類別,也不能被繼承,不過可以實作介面,它是值型態,所以將新變數指派給 struct 事實上是建立複本,而不是參考

Dog.cs

public struct Dog
{
    public string Breed
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public Dog(string name, string breed)
    {
        this.Name = name;
        this.Breed = breed;
    }

    public void Speak()
    {
        Console.WriteLine("My name is {0} and I'm a {1}.", Name, Breed);
    }
}

out

滾出去!,沒有啦。
out 修飾詞在一開始時就有用過,但書中在此章節才說明如何自己建立 out 函式,以下 ReturnThreeValues() 就是用 out 回傳三個值

Program.cs

static int ReturnThreeValues(int value, out double half, out int twice)
{
    half = value / 2f;
    twice = value * 2;

    return value + 1;
}

Console.Write("Enter a number: ");

if (int.TryParse(Console.ReadLine(), out int input))
{
    var output1 = ReturnThreeValues(input, out double output2, out int output3);

    Console.WriteLine("Outputs: plus one = {0}, half = {1:F}, twice = {2}",
        output1, output2, output3);
}

ref

這個就是方法的以參考傳遞,以前在 PHP 或 C 語言都曾見過,老實說自己有時候也會看到別人寫的程式有 ref 修飾詞,看了是霧茫茫,幸好此章書中有提到。

Guy.cs

namespace PassGuyByReference
{
    public class Guy
    {
        public string Name
        {
            get;
            set;
        }

        public int Age
        {
            get;
            set;
        }

        public override string ToString() => $"a {Age}-year-old named {Name}";
    }
}

Program.cs

using PassGuyByReference;

static void ModifyAnIntAndGuy(ref int valueRef, ref Guy guyRef)
{
    valueRef += 10;
    guyRef.Name = "Bob";
    guyRef.Age = 37;
}

var i = 1;
var guy = new Guy()
{
    Name = "Joe",
    Age = 26
};

Console.WriteLine("i is {0} and guy is {1}", i, guy);
ModifyAnIntAndGuy(ref i, ref guy);
Console.WriteLine("Now i is {0} and guy is {1}", i, guy);
輸出

i is 1 and guy is a 26-year-old named Joe
Now i is 11 and guy is a 37-year-old named Bob

參數預設值

這個比較常見,就是方法可以選擇性帶入,不帶入就用預設值。

具名引數

就是引數有指定參數名稱,這樣可以指定設定想要的預設值,而不用按照順序設定 CheckTemperature(96.2, tooLow: 95.5)

Program.cs

static void CheckTemperature(
    double temp,
    double tooHigh = 99.5,
    double tooLow = 96.5)
{
    if (temp < tooHigh && temp > tooLow)
    {
        Console.WriteLine("{0} degrees F - feeling good!", temp);

        return;
    }
    Console.WriteLine("Uh-oh {0} degrees F -- better see a doctor!", temp);
}

// Those values are fine for your average person
CheckTemperature(101.3);

// A dog's temperature should be between 100.5 and 102.5 Fahrenheit
CheckTemperature(101.3, 102.5, 100.5);

// Bob's temperature is always a little low, so set tooLow to 95.5
CheckTemperature(96.2, tooLow: 95.5);

null 警告

之前我在閱讀此書時就已經遇到多次變數可能為 null 警告了,此章書中給予了建議,此建議我之前也有分享過,我還是再記錄下來,再分享一遍

封裝

這個就是強迫賦值給屬性的意思,例如以下使用建構式來強迫賦值給 Name 屬性

Guy.cs

public class Guy
{
    public int Age
    {
        get;
        private set;
    }

    public string Name
    {
        get;
        private set;
    }

    public Guy(int age, string name)
    {
        this.Age = age;
        this.Name = name;
    }

    public override string ToString() => $"a {Age}-year-old named {Name}";
}

??

null 聯合運算子 ??,檢查如果值是 null 就回傳替代值

Program.cs

using (var stringReader = new StringReader(""))
{
    // 當 nextLine 是 null 時,指派空字串給它
    var nextLine = stringReader.ReadLine() ?? String.Empty;
    Console.WriteLine("Line length is: {0}", nextLine.Length);
}

??=

看起來有點類似 +=,但實際用途同上,當值是 null 時就指派值給變數、屬性或欄位

Program.cs

using (var stringReader = new StringReader(""))
{
    var nextLine = stringReader.ReadLine();
    // 當 nextLine 是 null 時,指派空字串給它
    nextLine ??= "";
    Console.WriteLine("Line length is: {0}", nextLine.Length);
}

擴充無法繼承的類別

當你遇到無法繼承的類別時,又想擴充方法時就可以用以下方法來實現,一般是第三方或者是 .NET Framework 才會有這個需求,沒事別這樣做。

做法是建立一個 static class,然後在裡面加入一個 static 方法,方法中的第一個參數使用 this 關鍵字加上要擴充的類別,然後再給它一個變數名稱,第二個參數之後就是擴充方法要使用的參數。

OrdinaryHuman.cs

namespace AmazeballsSerumProject
{
    sealed public class OrdinaryHuman
    {
        private int age;
        private int weight;

        public OrdinaryHuman(int weight)
        {
            this.weight = weight;
        }

        public void GoToWork()
        {
            /* code to go to work */
        }

        public void PayBills()
        {
            /* code to pay bills */
        }
    }
}

AmazeballsSerum.cs

namespace AmazeballsSerumProject
{
    static class AmazeballsSerum
    {
        public static string BreakWalls(
            this OrdinaryHuman h,
            double wallDensity)
        {
            return $"I broke through a wall of {wallDensity} density.";
        }
    }
}

Program.cs

using AmazeballsSerumProject;

OrdinaryHuman steve = new(185);
Console.WriteLine(steve.BreakWalls(89.2));

擴充基本型態 string

大家知道 string 裡面有一些方法,像是 Contains(),剛說明了擴充方法,現在是再舉例如何擴充 string 的方法,這也就是為什麼 using System.Linq; 後,你可以用 Enumerable.First() 方法的秘密。

ExtendAHuman.cs

namespace AmazingExtensions
{
    public static class ExtendAHuman
    {
        public static bool IsDistressCall(this string s)
        {
            if (s.Contains("Help!"))
            {
                return true;
            }

            return false;
        }
    }
}

Program.cs

using AmazingExtensions;

string message = "Evil clones are wreaking havoc. Help!";
Console.WriteLine(message.IsDistressCall());

參考

觀看次數: 1102
????=c#destructornulloutrefstruct擴充
按讚追蹤 Enjoy 軟體 Facebook 粉絲專頁
每週分享資訊技術

一杯咖啡的力量,勝過千言萬語的感謝。

支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!