深入淺出 C# 4/e 第11章 物件之死 心得分享
分類
心得
此章一開始討論的是討物件的生命週期,關於物件的記憶體被回收的機制,人終將一死,程式也有死的時候,有時候你會看到比較大型的系統生命週期,尤其是在遊戲開發時,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());
參考
一杯咖啡的力量,勝過千言萬語的感謝。
支持我一杯咖啡,讓我繼續創作優質內容,與您分享更多知識與樂趣!