文章

C# 開發全攻略:最佳實踐與技巧分享

引言

在現代軟體開發領域中,選擇合適的程式語言至關重要。C# 作為一款由微軟開發並不斷優化的現代化程式語言,憑藉其強大的功能和靈活性,已經成為眾多開發者的首選。

為什麼選擇 C#

以下是幾個關鍵原因:

  • 強大且多樣的功能1
  • 優秀的工具支持2
  • 跨平台能力3
  • 活躍的社群與豐富的資源4

本文目的與內容概要

  • 降低程式開發者進入一門語言的難度
  • 本文涵蓋從基礎到進階的各種技術與最佳實踐
  • 幫助開發者提升程式設計能力與程式品質
  • 以有價值的資訊與實用的技巧為主

C# 基礎知識

變數與資料型別

C# 中的變數和資料型別是理解程式語言的基礎。變數是儲存數據的容器,而資料型別則定義了變數可以儲存的數據種類。C# 支持多種資料型別,包括基本型別和參考型別。

基本型別又稱實值型別

  • 整數型別:int, long, short, byte
  • 浮點型別:float, double, decimal
  • 字符型別:char
  • 布爾型別:bool
  • 結構型別:struct

實值型別變數儲存的是數據本身,而不是對數據的引用。

引用型別又稱參考型別

  • 字串型別:string
  • 物件型別:object
  • 陣列型別:Array
  • 列表型別:List<T>
  • 其他參考型別:類別 class、介面 interface、委派 delegate,以及記錄型別 record

參考型別變數儲存的是對記憶體中物件的參考,而不是物件本身。

範例1:

1
2
3
4
5
6
int age = 30;
float height = 5.9f;
char initial = 'A';
bool isStudent = true;
string name = "John Smith";
List<string> names = new List<string> { "Alice", "Bob", "Charlie" };

範例2:以下案例 point1point2 互不影響

1
2
3
4
5
6
7
8
9
10
// 實值型別中的結構 (struct) 
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

Point point1 = new Point { X = 1, Y = 2 };
Point point2 = point1; // point2 是一個新結構,複製了 point1 的值
point2.X = 3; // 修改 point2 不會影響 point1

範例3:以下案例若修改 otherNumbersperson2 則會影響原來的變數

1
2
3
4
5
6
7
8
// 參考型別中的陣列 (Array) 
int[] numbers = { 1, 2, 3, 4, 5 };
int[] otherNumbers = numbers; // otherNumbers 參考同一個陣列

// 參考型別中的記錄型別 (Record) 
public record Person(string Name, int Age);
Person person1 = new Person("Alice", 30);
Person person2 = person1; // person2 參考同一個物件

在微軟的官方學習文件中有實值型別參考型別的詳細解說,可點選文字超連結至微軟的學習官網查閱。

常數與隱式類型變數

  • 常數 const:用於定義在程式執行過程中不可更改的值。例如物理常數、應用程式設定、網路配置、文件路徑、時間或錯誤代碼等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 數學常數
public class MathConstants
{
    public const double Pi = 3.14159;
    public const double EulerNumber = 2.71828;
}

// 應用程式設定
public class AppSettings
{
    public const string ApplicationName = "MyApp";        
    public const string Version = "1.0.0";
}

// 程式進入點
public class Program
{
    public static void Main()
    {
        // 計算圓周長
        double circleCircumference = 2 * MathConstants.Pi * 10;
        Console.WriteLine($"圓周長: {circleCircumference}");

        // 輸出應用程式設定
        Console.WriteLine($"Welcome to {AppSettings.ApplicationName}");
        Console.WriteLine($"Version: {AppSettings.Version}");

    }
}

使用常數宣告可以提高程式的可讀性、一致性和可維護性
常數使得程式更清晰明了,並有助於避免魔法數字5和硬編碼6

  • 隱式類型變數 var:用於編譯時自動推斷變數的類型
1
2
const int MaxValue = 100;
var count = 10; // 編譯器將推斷 count 為 int 類型

枚舉型別 (enum):

枚舉型別用於定義一組具名的整數常數,增加程式的可讀性和可維護性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum DaysOfWeek
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

public enum StatusCode
{
    Success = 200,
    NotFound = 404,
    InternalServerError = 500
}

基本語法與結構

C# 的基本語法和結構是撰寫程式的核心。了解如何撰寫有效的 C# 程式碼對於開發者來說至關重要。

  • 命名空間 namespace
    C# 使用命名空間來組織程式架構,避免名稱衝突,換言之命名空間是唯一的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    namespace MyApplication
    {
      class Program
      {
          static void Main(string[] args)
          {
              // 程式進入點
              Console.WriteLine("Hello, World!");
          }
      }
    }
    
  • 類別 class 和方法 method
    C# 是一個物件導向的語言,而類別 (class) 是其核心結構。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public class Person
    {
      // 成員變數
      private string name;
    
      // 建構函數
      public Person(string name)
      {
          this.name = name;
      }
    
      // 方法
      public void Greet()
      {
          Console.WriteLine("Hello, my name is " + name);
      }
    }
    

控制結構 (條件語句、迴圈等)

控制結構允許我們控制程式的流程,包括條件語句和迴圈。

以下介紹條件語句 (if-elseswitch) 和迴圈 (forwhiledo-while) 的基本用法。

  • 條件語句:根據特定條件執行不同的程式區塊。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    int number = 10;
    if (number > 0)
    {
      Console.WriteLine("Number is positive");
    }
    else if (number < 0)
    {
      Console.WriteLine("Number is negative");
    }
    else
    {
      Console.WriteLine("Number is zero");
    }
    
  • 迴圈:允許重複執行某段程式,直到特定條件為真。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
      // for 迴圈
      for (int i = 0; i < 10; i++)
      {
          Console.WriteLine(i);
      }
    
      // while 迴圈
      int j = 0;
      while (j < 10)
      {
          Console.WriteLine(j);
          j++;
      }
    
      // do-while 迴圈
      int k = 0;
      do
      {
          Console.WriteLine(k);
          k++;
      } while (k < 10);
    

物件導向程式設計

物件導向程式設計 (OOP) 是 C# 的核心理念之一。它提供了一種組織和結構化程式碼的方式,使得程式更加模組化、可重用和易於維護。本章將介紹 OOP 的三大基石:類別與物件、繼承與多型、介面與抽象。

類別與物件

  1. 類別是物件的藍圖或模板,定義了物件的屬性和行為。
  2. 物件則是類別的實例,代表實際存在的實體。
  • 定義類別:類別由關鍵字 class 定義,內部可以包含成員、屬性、方法和事件等成員。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    public class Person
    {
      // 成員
      private string name;
      private int age;
    
      // 屬性
      public string Name
      {
          get { return name; }
          set { name = value; }
      }
    
      public int Age
      {
          get { return age; }
          set { age = value; }
      }
    
      // 方法
      public void Greet()
      {
          Console.WriteLine($"Hello, my name is {name} and I am {age} years old.");
      }
    }
    
  • 創建物件:使用 new 關鍵字來創建類別的實例 (物件) 。
    1
    2
    3
    4
    
    Person person = new Person();
    person.Name = "Alice";
    person.Age = 30;
    person.Greet(); // 問候方法
    

繼承與多型

繼承允許我們從現有類別創建新類別,從而重用、擴展和修改父類別的行為。多型允許我們以統一的方式處理不同類別的物件。

  • 繼承:使用 : 符號來繼承基類。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public class Employee : Person
    {
      public int EmployeeID { get; set; }
    
      public void Work()
      {
          Console.WriteLine($"{Name} is working.");
      }
    }
    
  • 多型:通常通過基類引用來引用子類物件,並在運行時決定調用哪個方法實作。
    1
    2
    3
    4
    5
    6
    
    Person employee = new Employee();
    employee.Name = "Bob";
    employee.Age = 25;
    ((Employee)employee).EmployeeID = 123;
    employee.Greet();
    ((Employee)employee).Work();
    

介面與抽象類別

介面和抽象類別都是用來定義物件行為的工具,但它們有不同的用法和特性。

  • 介面:只定義方法、屬性、事件或索引器的簽名,而不包含任何實作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      public interface IWorkable
      {
          void Work();
      }
    
      public class Manager : Person, IWorkable
      {
          public void Work()
          {
              Console.WriteLine($"{Name} is managing the team.");
          }
      }
    

    類別或結構可以實作一個或多個介面。

  • 抽象類別:可以包含抽象方法 (沒有實作的方法) 和具體方法。抽象類別不能實例化,必須被繼承。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Animal
{
    public abstract void Speak();
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

public class Program
{
    public static void Main()
    {
        Animal myDog = new Dog();
        myDog.Speak();
    }
}

小節

  1. 物件導向程式設計提供了一種強大且靈活的方法來組織和管理程式。
  2. 通過理解和應用類別與物件、繼承與多型、介面與抽象類別的概念,開發者可以創建更高效、更可維護的程式碼。

進階語法特性

C# 提供了許多強大且靈活的語法特性,使得程式設計更加高效和便捷。本章將介紹三個重要的進階語法特性:委派與事件、屬性與索引器、泛型。

委派與事件

  1. 委派是 C# 中的一種型別安全的函數指標,允許將方法作為參數傳遞。
  2. 事件則是基於委派的,主要用於實作事件驅動程式設計。
  • 委派:用來定義可指向具有特定簽名和返回型別的方法的類型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public delegate void Notify(string message);

public class Process
{
    public Notify ProcessCompleted;
    
    public void StartProcess()
    {
        Console.WriteLine("Process Started.");
        // 模擬一些處理工作
        OnProcessCompleted("Process Completed Successfully.");
    }

    protected virtual void OnProcessCompleted(string message)
    {
        ProcessCompleted?.Invoke(message);
    }
}

public class Program
{
    public static void Main()
    {
        Process process = new Process();
        process.ProcessCompleted = Message;
        process.StartProcess();
    }

    public static void Message(string message)
    {
        Console.WriteLine(message);
    }
}
  • 事件:是基於委派的封裝,用於安全地發佈和訂閱消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Process
{
    public event EventHandler ProcessCompleted;

    public void StartProcess()
    {
        Console.WriteLine("Process Started.");
        // 模擬一些處理工作
        OnProcessCompleted(EventArgs.Empty);
    }

    protected virtual void OnProcessCompleted(EventArgs e)
    {
        ProcessCompleted?.Invoke(this, e);
    }
}

public class Program
{
    public static void Main()
    {
        Process process = new Process();
        process.ProcessCompleted += Process_ProcessCompleted;
        process.StartProcess();
    }

    private static void Process_ProcessCompleted(object sender, EventArgs e)
    {
        Console.WriteLine("Process Completed Successfully.");
    }
}

屬性與索引器

屬性提供了控制類別成員訪問的方法,而索引器允許對類似陣列的物件進行索引。

  • 屬性:用於封裝成員的存取,並提供額外的邏輯。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person
{
    private string name;
    private int age;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int Age
    {
        get { return age; }
        set 
        { 
            if (value < 0)
                throw new ArgumentException("Age cannot be negative");
            age = value; 
        }
    }
}
  • 索引器:允許對類似陣列的物件進行索引操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SampleCollection<T>
{
    private T[] arr = new T[100];
    public T this[int i]
    {
        get { return arr[i]; }
        set { arr[i] = value; }
    }
}

public class Program
{
    public static void Main()
    {
        var stringCollection = new SampleCollection<string>();
        stringCollection[0] = "Hello, World!";
        Console.WriteLine(stringCollection[0]);
    }
}

泛型

泛型允許你定義一個類別、結構、介面或方法,並在其中使用型別參數。這使得程式更加通用和型別安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class GenericList<T>
{
    private T[] elements;
    private int count;

    public GenericList(int capacity)
    {
        elements = new T[capacity];
        count = 0;
    }

    public void Add(T element)
    {
        if (count < elements.Length)
        {
            elements[count] = element;
            count++;
        }
        else
        {
            throw new InvalidOperationException("List is full");
        }
    }

    public T Get(int index)
    {
        if (index < 0 || index >= count)
        {
            throw new ArgumentOutOfRangeException(nameof(index));
        }
        return elements[index];
    }
}

public class Program
{
    public static void Main()
    {
        var intList = new GenericList<int>(5);
        intList.Add(1);
        intList.Add(2);
        Console.WriteLine(intList.Get(0)); // Output: 1
        Console.WriteLine(intList.Get(1)); // Output: 2

        var stringList = new GenericList<string>(5);
        stringList.Add("Hello");
        stringList.Add("World");
        Console.WriteLine(stringList.Get(0)); // Output: Hello
        Console.WriteLine(stringList.Get(1)); // Output: World
    }
}

小節

  1. 進階語法特性使得 C# 成為一個強大且靈活的程式設計語言。
  2. 通過理解和應用委派與事件、屬性與索引器、泛型等特性,開發者可以創建更加高效和可維護的程式。

異步程式設計

異步程式設計在現代應用程式開發中扮演著越來越重要的角色。通過異步程式設計,可以提高應用程式的響應速度和資源利用率。本章將介紹異步與多執行續、asyncawait 關鍵字、以及 Task 和多執行續並發處理的相關知識和最佳實踐。

異步與多執行續

  • 異步 (Asynchronous Programming) :允許程式在等待某些操作 (如 I/O 操作) 完成時,不阻塞主執行緒,從而提高應用程式的響應速度。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      public async Task DownloadFileAsync(string url)
      {
          using (HttpClient client = new HttpClient())
          {
              string content = await client.GetStringAsync(url);
              Console.WriteLine(content);
          }
      }
    
      public async Task ProcessAsync()
      {
          await DownloadFileAsync("https://example.com/file.txt");
          Console.WriteLine("Download completed.");
      }
    
  • 多執行續:允許在多核處理器上同時執行多個任務,從而提高計算效率。
    1
    2
    3
    4
    5
    6
    7
    
      public void ParallelProcessing()
      {
          Parallel.For(0, 10, i =>
          {
              Console.WriteLine($"Processing {i}");
          });
      }
    

async 和 await 關鍵字

asyncawait 是 C# 中實作異步程式設計的核心關鍵字。async 用於標記一個方法為異步方法,await 用於等待異步操作的完成。

  • async 關鍵字:用於修飾方法,表示該方法包含異步操作。
1
2
3
4
5
public async Task DoWorkAsync()
{
    await Task.Delay(1000); // 模擬異步操作
    Console.WriteLine("Work completed.");
}

Task 和多執行續並行處理

Task 是 .NET 中表示異步操作的類別。通過使用 Task,可以方便地實作並行處理和異步操作。

  • Task 基本用法:Task 可以表示一個異步操作,並且可以使用 Task.Run 來啟動新任務。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public async Task RunTaskAsync()
{
    Task task = Task.Run(() =>
    {
        // 模擬一些計算操作
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Task running: {i}");
            Thread.Sleep(1000);
        }
    });

    await task;
    Console.WriteLine("Task completed.");
}
  • Task 並行處理:可以使用 Task.WhenAll 方法來等待多個任務完成,從而實作並行處理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public async Task RunMultipleTasksAsync()
{
    Task task1 = Task.Run(() => 
    {
        Thread.Sleep(1000);
        Console.WriteLine("Task 1 completed.");
    });

    Task task2 = Task.Run(() => 
    {
        Thread.Sleep(2000);
        Console.WriteLine("Task 2 completed.");
    });

    await Task.WhenAll(task1, task2);
    Console.WriteLine("All tasks completed.");
}

小節

  1. 異步程式設計是現代應用開發中的一個重要技術,它可以顯著提高應用的響應速度和資源利用率。
  2. 通過理解和掌握異步與多執行續、asyncawait 關鍵字、以及 Task 和並行處理的用法,開發者可以撰寫出更加高效和可靠的應用程式。

LINQ (語言整合查詢)

LINQ (Language Integrated Query) 是 C# 中的一個強大功能,允許開發者使用查詢語法來訪問和操作數據。LINQ 可以應用於多種數據源,如集合、數組、SQL 資料庫和 XML 文檔。本章將介紹 LINQ 的基礎概念、LINQ to Objects、LINQ to SQL 以及 LINQ to XML。

LINQ 基礎

LINQ 提供了一套一致的語法來查詢不同類型的數據源,並且可以與 C# 語言無縫整合。LINQ 查詢由三個主要部分組成:數據來源、查詢語句和查詢執行。

  • 基本語法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
      // 數據來源
      int[] numbers = { 2, 4, 6, 8, 10 };
    
      // 查詢語句
      var evenNumbers = from num in numbers
                        where num % 2 == 0
                        select num;
    
      // 查詢執行
      foreach (var num in evenNumbers)
      {
          Console.WriteLine(num);
      }
    
  • Lambda 表達式:LINQ 也可以使用 Lambda 表達式來簡化查詢語法。
    1
    2
    3
    4
    5
    6
    
      var evenNumbers = numbers.Where(num => num % 2 == 0);
    
      foreach (var num in evenNumbers)
      {
          Console.WriteLine(num);
      }
    

LINQ to Objects

LINQ to Objects 允許對集合和數組等記憶體中的物件進行查詢操作。

  • 查詢集合
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
      List<string> names = new List<string> { "Alice", "Bob", "Charlie", "David" };
    
      // 查詢語句
      var query = from name in names
                  where name.StartsWith("A")
                  select name;
    
      // 查詢執行
      foreach (var name in query)
      {
          Console.WriteLine(name);
      }
    
  • Lambda 語法
    1
    2
    3
    4
    5
    6
    
      var query = names.Where(name => name.StartsWith("A"));
    
      foreach (var name in query)
      {
          Console.WriteLine(name);
      }
    

LINQ to SQL 與 LINQ to XML

  • LINQ to SQL:允許開發者使用 LINQ 查詢 SQL 資料庫,並且將結果映射到 C# 物件上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      using (var db = new DataContext(connectionString))
      {
          var query = from customer in db.GetTable<Customer>()
                      where customer.City == "Seattle"
                      select customer;
    
          foreach (var customer in query)
          {
              Console.WriteLine(customer.Name);
          }
      }
    
  • LINQ to XML:提供了對 XML 文檔進行查詢和操作的功能。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
      XDocument doc = XDocument.Load("books.xml");
    
      var query = from book in doc.Descendants("book")
                  where (int)book.Element("price") > 20
                  select new
                  {
                      Title = book.Element("title").Value,
                      Price = book.Element("price").Value
                  };
    
      foreach (var book in query)
      {
          Console.WriteLine($"Title: {book.Title}, Price: {book.Price}");
      }
    

小結

  1. LINQ 是一個強大且靈活的工具,可以簡化數據查詢和操作過程。
  2. 通過理解 LINQ 的基本語法、LINQ to Objects、LINQ to SQL 以及 LINQ to XML 的用法,開發者可以更高效地處理各種數據源。

資料操作

資料操作是任何應用程式開發中的關鍵部分。在 C# 中,有多種方法可以與資料庫進行互動,包括使用 ADO.NET 和 Entity Framework Core。本章將介紹如何使用 ADO.NET 進行資料庫操作、Entity Framework Core 的使用以及基本的 CRUD (創建、讀取、更新、刪除) 操作。

使用 ADO.NET 進行資料庫操作

ADO.NET 是 .NET 提供的一套強大的資料訪問技術,允許開發者直接與資料庫進行互動。

  1. 連接資料庫:首先,需要建立與資料庫的連接。這通常使用 SqlConnection 類來實作。
    1
    2
    3
    4
    5
    6
    7
    
     string connectionString = "your_connection_string_here";
    
     using (SqlConnection connection = new SqlConnection(connectionString))
     {
         connection.Open();
         Console.WriteLine("Database connected.");
     }
    
  2. 執行查詢:可以使用 SqlCommand 類來執行 SQL 查詢。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     string query = "SELECT * FROM Customers";
    
     using (SqlCommand command = new SqlCommand(query, connection))
     {
         using (SqlDataReader reader = command.ExecuteReader())
         {
             while (reader.Read())
             {
                 Console.WriteLine($"{reader["CustomerID"]}, {reader["Name"]}");
             }
         }
     }
    
  3. 新增資料
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    string insertQuery = "INSERT INTO Customers (Name, City) VALUES (@Name, @City)";
    
     using (SqlCommand command = new SqlCommand(insertQuery, connection))
     {
         command.Parameters.AddWithValue("@Name", "John Doe");
         command.Parameters.AddWithValue("@City", "New York");
         int rowsAffected = command.ExecuteNonQuery();
         Console.WriteLine($"{rowsAffected} row(s) inserted.");
     }
    

Entity Framework Core

Entity Framework Core (EF Core) 是一個開源的 ORM 框架,讓開發者可以使用 .NET 物件來操作資料庫,從而減少直接撰寫 SQL 語句的需要。

  1. 配置與連接:首先,需要配置 EF Core 並建立與資料庫的連接。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     public class AppDbContext : DbContext
     {
         public DbSet<Customer> Customers { get; set; }
    
         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
         {
             optionsBuilder.UseSqlServer("your_connection_string_here");
         }
     }
    
  2. 定義實體 (Entity) 類別
    1
    2
    3
    4
    5
    6
    
     public class Customer
     {
         public int CustomerID { get; set; }
         public string Name { get; set; }
         public string City { get; set; }
     }
    

基本 CRUD 操作

CRUD 操作是資料庫應用中最常見的操作,分別對應創建、讀取、更新和刪除資料。

  • 創建 (Create)
    1
    2
    3
    4
    5
    6
    7
    
      using (var context = new AppDbContext())
      {
          var customer = new Customer { Name = "Jane Doe", City = "Los Angeles" };
          context.Customers.Add(customer);
          context.SaveChanges();
          Console.WriteLine("Customer added.");
      }
    
  • 讀取 (Read)
    1
    2
    3
    4
    5
    6
    7
    8
    
      using (var context = new AppDbContext())
      {
          var customers = context.Customers.ToList();
          foreach (var customer in customers)
          {
              Console.WriteLine($"{customer.CustomerID}, {customer.Name}, {customer.City}");
          }
      }
    
  • 更新 (Update)
    1
    2
    3
    4
    5
    6
    7
    
      using (var context = new AppDbContext())
      {
          var customer = context.Customers.First();
          customer.City = "San Francisco";
          context.SaveChanges();
          Console.WriteLine("Customer updated.");
      }
    
  • 刪除 (Delete)
    1
    2
    3
    4
    5
    6
    7
    
      using (var context = new AppDbContext())
      {
          var customer = context.Customers.First();
          context.Customers.Remove(customer);
          context.SaveChanges();
          Console.WriteLine("Customer deleted.");
      }
    

小結

  1. 資料操作是應用程式開發的核心部分,掌握如何有效地進行資料庫操作對於開發者來說至關重要。
  2. 使用 ADO.NET 可以進行細粒度的資料庫控制,而 Entity Framework Core 則提供了一種更高層次的抽象,讓資料操作更加簡單和高效。

例外處理與除錯

在開發過程中,處理和除錯錯誤是確保軟體穩定性和可靠性的關鍵步驟。本章將介紹錯誤處理的基礎概念、如何使用 try-catch 語句以及一些實用的除錯技巧與工具。

錯誤處理基礎

錯誤處理是軟體開發中不可避免的一部分。在 C# 中,錯誤處理主要通過例外 (Exception) 機制來實作。例外是用來表示程式執行過程中發生的錯誤或異常情況的物件。

一些常見的例外類型包括:

  • ArgumentNullException:當一個必需的參數為 null 時拋出。
  • ArgumentOutOfRangeException:當一個參數的值超出有效範圍時拋出。
  • InvalidOperationException:當方法調用的狀態不正確時拋出。
  • IOException:當發生 I/O 操作錯誤時拋出。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    public void ReadFile(string filePath)
    {
      // 守衛語句
      if (string.IsNullOrEmpty(filePath))
      {
          // 擲出空值例外
          throw new ArgumentNullException(nameof(filePath), 
              "File path cannot be null or empty");
      }
    
      // DoSomething 讀取文件邏輯
    }
    

使用 try-catch 語句

try-catch 語句用於捕獲和處理例外,從而避免程式崩潰並提供適當的錯誤處理機制。

基本語法

1
2
3
4
5
6
7
8
9
10
11
12
13
    public void Divide(int numerator, int denominator)
    {
        try
        {
            int result = numerator / denominator;
            Console.WriteLine($"Result: {result}");
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine("Error: Cannot divide by zero.");
            Console.WriteLine(ex.Message);
        }
    }

多個 catch 區塊

可以使用多個 catch 區塊來捕獲不同類型的例外。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void ProcessFile(string filePath)
{
    try
    {
        string content = File.ReadAllText(filePath);
        Console.WriteLine(content);
    }
    catch (FileNotFoundException ex)
    {
        Console.WriteLine("Error: File not found.");
        Console.WriteLine(ex.Message);
    }
    catch (UnauthorizedAccessException ex)
    {
        Console.WriteLine("Error: Access denied.");
        Console.WriteLine(ex.Message);
    }
    catch (Exception ex)
    {
        Console.WriteLine("An unexpected error occurred.");
        Console.WriteLine(ex.Message);
    }
}

finally 區塊

finally 區塊中的程式無論是否發生例外都會執行,通常用於釋放資源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void OpenFile(string filePath)
{
    FileStream fileStream = null;

    try
    {
        fileStream = File.Open(filePath, FileMode.Open);
        // 讀取文件
    }
    catch (Exception ex)
    {
        Console.WriteLine("An error occurred while opening the file.");
        Console.WriteLine(ex.Message);
    }
    finally
    {
        fileStream?.Dispose();
    }
}

除錯技巧與工具

除錯是開發過程中的重要環節,通過除錯可以找出並修正程式中的問題。Visual Studio 提供了強大的除錯工具來幫助開發者進行程式除錯。

  • 中斷點:是除錯過程中的核心工具,用於暫停程式執行,讓開發者檢查變數值和程式狀態。
    1
    2
    3
    4
    5
    
      public void Calculate(int value)
      {
          int result = value * 10; // 在這行設置中斷點 F8
          Console.WriteLine($"Result: {result}");
      }
    
  • 區域變數視窗:用於顯示當前作用域內的變數及其值。
  • 自動變數視窗:則顯示當前和前後幾行代碼中使用的變數及其值。
  • 監看式視窗:允許開發者在除錯過程中觀察和評估變數值。

  • 即時運算視窗 (Immediate Window) :允許在除錯過程中執行程式片段,檢查和修改變數值。
    1
    2
    3
    4
    5
    6
    7
    
    public void TestImmediateWindow()
    {
      int a = 5;
      int b = 10;
      int sum = a + b; // 在這行設置斷點
      Console.WriteLine(sum); // 在即時視窗中可以評估和修改變數值
    }
    
  • 日誌記錄:可以幫助追蹤應用程式運行時的行為,特別是在生產環境中。
    1
    2
    3
    4
    5
    6
    7
    
      private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
    
      public void LogExample()
      {
          Logger.Info("This is an informational message.");
          Logger.Error("This is an error message.");
      }
    

小結

  1. 有效的錯誤處理和除錯技術對於開發穩定可靠的軟體至關重要。
  2. 通過理解和應用錯誤處理的基礎概念、try-catch 語句以及除錯技巧與工具,開發者可以更好地管理和排查程式中的問題。

記憶體管理與垃圾回收

記憶體管理是任何程式設計語言的關鍵部分。在 C# 中,記憶體管理主要通過自動化的垃圾回收機制來處理。本章將介紹 C# 的記憶體管理方式、垃圾回收機制以及如何正確使用 IDisposable 介面來釋放非托管資源。

C# 的記憶體管理

C# 使用託管堆和堆疊來管理記憶體。變數可以儲存在堆疊或堆上,具體取決於它們的類型和作用範圍。

  • 堆疊 (Stack):是一種後進先出 (LIFO) 的資料結構,用於儲存方法的區域變數和方法調用資訊。
    1
    2
    3
    4
    5
    6
    7
    
      public void StackExample()
      {
          int a = 10; // 值型別儲存在堆疊上
          int b = 20;
          int sum = a + b;
          Console.WriteLine(sum);
      }
    

    實值型別 (如整數和結構體) 通常儲存在堆疊上。

  • 堆 (Heap):是一塊全域可訪問的記憶體,用於儲存動態分配的物件和類型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
      public void HeapExample()
      {
          Person person = new Person(); // 參考型別儲存在堆上
          person.Name = "Alice";
          Console.WriteLine(person.Name);
      }
    
      public class Person
      {
          public string Name { get; set; }
      }
    

    參考型別 (如類和陣列) 通常儲存在堆上。

垃圾回收機制

C# 的垃圾回收 (Garbage Collection, GC) 機制自動管理記憶體分配和釋放,幫助開發者避免記憶體洩漏和其他相關問題。GC 通過標記-清除算法來識別和釋放不再使用的物件。

工作原理

  1. 標記階段:垃圾回收器標記所有可達的物件。
  2. 清除階段:垃圾回收器釋放所有未標記的物件所佔用的記憶體。
  3. 壓縮階段 (可選) :壓縮記憶體,以便分配新的物件。
  • 強制垃圾回收 可以通過調用 GC.Collect 方法來強制執行垃圾回收,但一般情況下不建議這樣做,因為 GC 會自動優化垃圾回收過程。
    1
    2
    
    GC.Collect();
    GC.WaitForPendingFinalizers();
    

使用 IDisposable 介面

IDisposable 介面提供了一種顯式釋放非托管資源的方法。當使用包含非托管資源的物件時,應該實作 IDisposable 介面並在 Dispose 方法中釋放這些資源。

實作 IDisposable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class ResourceHolder : IDisposable
{
    private bool disposed = false;

    // 非托管資源
    private IntPtr unmanagedResource;

    // 託管資源
    private StreamReader managedResource;

    public ResourceHolder()
    {
        unmanagedResource = // 分配非托管資源
        managedResource = new StreamReader("file.txt");
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // 釋放託管資源
                if (managedResource != null)
                {
                    managedResource.Dispose();
                    managedResource = null;
                }
            }

            // 釋放非托管資源
            if (unmanagedResource != IntPtr.Zero)
            {
                // 釋放非托管資源的程式
                unmanagedResource = IntPtr.Zero;
            }

            disposed = true;
        }
    }

    ~ResourceHolder()
    {
        Dispose(false);
    }
}
  • 使用範圍 using 語句來自動調用 Dispose 方法,確保資源在使用後得到正確釋放。
    1
    2
    3
    4
    5
    6
    7
    
    public void UseResource()
    {
      using (var resourceHolder = new ResourceHolder())
      {
          // 使用資源
      } // 退出 using 範圍時自動調用 Dispose 方法
    }
    

小節

  1. 有效的記憶體管理和資源釋放是撰寫高效穩定程式的關鍵。
  2. 在 C# 中,記憶體管理主要由自動垃圾回收機制處理,但對於非托管資源,需要開發者顯式地釋放。
  3. 通過理解和應用這些概念,開發者可以確保程式在運行過程中高效且無記憶體洩漏。

設計模式

設計模式是經過驗證的解決方案,用於解決軟體開發過程中常見的設計問題。瞭解和應用設計模式可以幫助開發者撰寫更靈活、可維護的程式。本章將介紹一些常見的設計模式,如單例模式、工廠模式和觀察者模式,並提供在 C# 中的實作示例和最佳實踐。

常見設計模式 (如單例、工廠、觀察者)

  • 單例模式 (Singleton) 確保一個類只有一個實例,並提供全域訪問點。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    public class Singleton
    {
      private static Singleton instance;
      private static readonly object lockObj = new object();
    
      private Singleton() { }
    
      public static Singleton Instance
      {
          get
          {
              lock (lockObj)
              {
                  if (instance == null)
                  {
                      instance = new Singleton();
                  }
              }
              return instance;
          }
      }
    
      public void DoSomething()
      {
          Console.WriteLine("Singleton instance method called.");
      }
    }
    
  • 工廠模式 (Factory) 定義了一個用於創建物件的介面,但讓子類決定實例化哪個類。工廠模式使得一個類的實例化延遲到子類。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    
      public interface IProduct
      {
          void DoWork();
      }
    
      public class ConcreteProductA : IProduct
      {
          public void DoWork()
          {
              Console.WriteLine("Product A work.");
          }
      }
    
      public class ConcreteProductB : IProduct
      {
          public void DoWork()
          {
              Console.WriteLine("Product B work.");
          }
      }
    
      public abstract class Creator
      {
          public abstract IProduct FactoryMethod();
    
          public void AnOperation()
          {
              IProduct product = FactoryMethod();
              product.DoWork();
          }
      }
    
      public class ConcreteCreatorA : Creator
      {
          public override IProduct FactoryMethod()
          {
              return new ConcreteProductA();
          }
      }
    
      public class ConcreteCreatorB : Creator
      {
          public override IProduct FactoryMethod()
          {
              return new ConcreteProductB();
          }
      }
    
  • 觀察者模式 (Observer) 定義了物件間的一對多依賴關係,使得當一個物件改變狀態時,所有依賴於它的物件都會得到通知並自動更新。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
      public interface IObserver
      {
          void Update(string message);
      }
    
      public class ConcreteObserver : IObserver
      {
          private string name;
    
          public ConcreteObserver(string name)
          {
              this.name = name;
          }
    
          public void Update(string message)
          {
              Console.WriteLine($"{name} received message: {message}");
          }
      }
    
      public class Subject
      {
          private List<IObserver> observers = new List<IObserver>();
    
          public void Attach(IObserver observer)
          {
              observers.Add(observer);
          }
    
          public void Detach(IObserver observer)
          {
              observers.Remove(observer);
          }
    
          public void Notify(string message)
          {
              foreach (var observer in observers)
              {
                  observer.Update(message);
              }
          }
      }
    

在 C# 中的實作示例

  • 單例模式實作示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    public class Program
    {
      public static void Main()
      {
          Singleton singleton1 = Singleton.Instance;
          Singleton singleton2 = Singleton.Instance;
    
          if (singleton1 == singleton2)
          {
              Console.WriteLine("Both instances are the same.");
          }
    
          singleton1.DoSomething();
      }
    }
    
  • 工廠模式實作示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    public class Program
    {
      public static void Main()
      {
          Creator creatorA = new ConcreteCreatorA();
          creatorA.AnOperation();
    
          Creator creatorB = new ConcreteCreatorB();
          creatorB.AnOperation();
      }
    }
    
  • 觀察者模式實作示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    public class Program
    {
      public static void Main()
      {
          Subject subject = new Subject();
    
          IObserver observer1 = new ConcreteObserver("Observer 1");
          IObserver observer2 = new ConcreteObserver("Observer 2");
    
          subject.Attach(observer1);
          subject.Attach(observer2);
    
          subject.Notify("State changed!");
    
          subject.Detach(observer1);
          subject.Notify("Another state change!");
      }
    }
    

設計模式的最佳實踐

  1. 選擇合適的模式:並不是所有問題都需要設計模式,選擇合適的模式來解決具體問題是關鍵。
  2. 遵循 SOLID 原則:設計模式應與 SOLID 原則結合使用,以提高程式的可維護性和可擴展性。
  3. 避免過度設計:過度使用設計模式會使程式變得複雜且難以維護,應該根據實際需求進行設計。
  4. 理解設計模式的意圖:在應用設計模式前,確保理解其意圖和適用場景,以便正確實作。
  5. 重構現有程式:當發現程式出現重複或難以維護時,可以考慮重構並引入適當的設計模式。

小結

  1. 設計模式是解決常見軟體設計問題的有效工具。
  2. 通過理解和應用常見設計模式如單例、工廠和觀察者模式,開發者可以提高程式的靈活性和可維護性。

其他有關於 C# 設計模式 (Design Pattern) 會在後續的文章中詳細介紹。

測試與除錯

測試與除錯是保證軟體品質和穩定性的重要環節。通過有效的測試和除錯,可以發現和修復程式中的問題,確保軟體按預期運行。本章將介紹單元測試與測試框架 (如 NUnit、MSTest) 、測試驅動開發 (TDD) 以及測試的最佳實踐。

單元測試與測試框架 (如 NUnit、MSTest)

單元測試是測試單個功能單元 (如方法或類) 是否按預期運行的一種技術。在 C# 中,常用的單元測試框架包括 NUnit 和 MSTest。

  • NUnit 是一個流行的單元測試框架,提供了簡單易用的測試功能。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
      using NUnit.Framework;
    
      [TestFixture]
      public class CalculatorTests
      {
          [Test]
          public void Add_WhenCalled_ReturnsSum()
          {
              // Arrange
              var calculator = new Calculator();
    
              // Act
              int result = calculator.Add(1, 2);
    
              // Assert
              Assert.AreEqual(3, result);
          }
      }
    
      public class Calculator
      {
          public int Add(int a, int b)
          {
              return a + b;
          }
      }
    
  • MSTest 是微軟提供的測試框架,與 Visual Studio 集成度高。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
      using Microsoft.VisualStudio.TestTools.UnitTesting;
    
      [TestClass]
      public class CalculatorTests
      {
          [TestMethod]
          public void Add_WhenCalled_ReturnsSum()
          {
              // Arrange
              var calculator = new Calculator();
    
              // Act
              int result = calculator.Add(1, 2);
    
              // Assert
              Assert.AreEqual(3, result);
          }
      }
    
      public class Calculator
      {
          public int Add(int a, int b)
          {
              return a + b;
          }
      }
    

測試驅動開發 (TDD)

測試驅動開發 (TDD) 是一種軟體開發方法,強調先撰寫測試案例,然後撰寫能夠通過測試的程式,最後重構程式。TDD 可以幫助開發者撰寫更加可靠和可維護的程式。

TDD 的基本步驟

  1. 撰寫測試:根據需求撰寫一個失敗的測試。
  2. 實作功能:撰寫最簡單的程式使測試通過。
  3. 重構程式:優化程式結構,同時保持測試通過。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
     // 步驟 1:撰寫測試
     [Test]
     public void Multiply_WhenCalled_ReturnsProduct()
     {
         var calculator = new Calculator();
         int result = calculator.Multiply(2, 3);
         Assert.AreEqual(6, result);
     }
    
     // 步驟 2:實作功能
     public class Calculator
     {
         public int Multiply(int a, int b)
         {
             return a * b;
         }
     }
    
     // 步驟 3:重構程式
     public class Calculator
     {
         public int Multiply(int a, int b) => a * b;
     }
    

測試的最佳實踐

  1. 保持測試獨立:每個測試應該獨立於其他測試,避免相互依賴。
  2. 撰寫清晰的測試名稱:測試名稱應該清晰描述測試的意圖和預期行為。
  3. 覆蓋多種情況:測試應涵蓋正常情況、邊界情況和錯誤情況。
  4. 快速執行:測試應該能夠快速執行,避免過長的測試時間影響開發效率。
  5. 自動化測試:使用持續集成工具自動運行測試,確保每次程式變更都能被及時測試。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
     [Test]
     public void Add_WhenCalledWithNegativeNumbers_ReturnsCorrectSum()
     {
         var calculator = new Calculator();
         int result = calculator.Add(-1, -2);
         Assert.AreEqual(-3, result);
     }
    
     [Test]
     public void Divide_WhenCalledWithZero_ThrowsDivideByZeroException()
     {
         var calculator = new Calculator();
         Assert.Throws<DivideByZeroException>(() => calculator.Divide(1, 0));
     }
    

小結

  1. 測試與除錯是軟體開發過程中的重要組成部分。
  2. 通過有效地進行單元測試和應用測試驅動開發,開發者可以提高程式的品質和可靠性。
  3. 理解和應用測試的最佳實踐,將有助於您在日常開發中更好地應對挑戰,確保軟體的穩定性和可維護性。

效能優化

效能優化是提升應用程式響應速度和資源利用率的關鍵。通過有效的效能分析和程式優化,可以確保應用程式在各種環境下高效運行。本章將介紹效能分析工具、程式優化技巧以及常見效能問題及其解決方案。

效能分析工具

效能分析工具可以幫助開發者識別和診斷應用程式中的效能瓶頸。以下是幾種常用的效能分析工具:

  • Visual Studio Profiler 提供了一個內置的效能分析工具,允許開發者分析應用程式的 CPU 和記憶體使用情況。
    1. 打開 Visual Studio,載入你的項目。
    2. 選擇 Debug 菜單,然後點擊 Performance Profiler
    3. 選擇需要分析的工具,例如 CPU UsageMemory Usage
    4. 點擊 Start 開始效能分析。
  • JetBrains dotTrace 是一款強大的效能分析工具,提供詳細的效能報告和診斷功能。
    1. 下載並安裝 JetBrains dotTrace。
    2. 選擇需要分析的應用程式。
    3. 開始效能分析並查看詳細報告。
  • BenchmarkDotNet 是一個流行的 .NET 效能基準測試庫,用於測試和比較不同程式區塊的效能。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
      using BenchmarkDotNet.Attributes;
      using BenchmarkDotNet.Running;
    
      public class Benchmarks
      {
          [Benchmark]
          public void TestMethod1()
          {
              // 測試程式1
          }
    
          [Benchmark]
          public void TestMethod2()
          {
              // 測試程式2
          }
      }
    
      public class Program
      {
          public static void Main()
          {
              var summary = BenchmarkRunner.Run<Benchmarks>();
          }
      }
    

程式優化技巧

程式優化可以提高應用程式的運行效率和資源利用率。以下是一些常見的程式優化技巧:

  • 減少不必要的物件創建:頻繁創建和銷毀物件會導致記憶體分配和垃圾回收的開銷。可以使用物件池來重用物件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    public class MyClass
    {
      private static List<MyClass> pool = new List<MyClass>();
    
      public static MyClass GetObject()
      {
          if (pool.Count > 0)
          {
              var obj = pool[0];
              pool.RemoveAt(0);
              return obj;
          }
          return new MyClass();
      }
    
      public static void ReleaseObject(MyClass obj)
      {
          pool.Add(obj);
      }
    }
    
  • 使用適當的資料結構:選擇合適的資料結構可以顯著提高效能。例如,對於頻繁查找的操作,Dictionary 通常比 List 更高效。
    1
    2
    3
    4
    5
    6
    7
    8
    
      var dictionary = new Dictionary<int, string>();
      dictionary.Add(1, "Value1");
      dictionary.Add(2, "Value2");
    
      if (dictionary.ContainsKey(1))
      {
          Console.WriteLine(dictionary[1]);
      }
    
  • 避免不必要的鎖定:在多執行緒環境中,鎖定會導致效能下降。應盡量減少鎖定範圍或使用細粒度鎖定。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
      private readonly object lockObject = new object();
    
      public void PerformAction()
      {
          lock (lockObject)
          {
              // 僅對必要的程式進行鎖定
          }
      }
    

常見效能問題及解決方案

  • 高 CPU 使用率:通常由於無限循環或計算密集型操作引起。可以通過優化算法或將計算密集型操作移至後台執行來解決。
    1
    2
    3
    4
    5
    6
    7
    
    public async Task PerformBackgroundTask()
    {
      await Task.Run(() =>
      {
          // 計算密集型操作
      });
    }
    
  • 高記憶體使用率:可能是由記憶體洩漏或物件過多引起的。可以通過分析記憶體分配並優化程式來解決。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    public void PerformOperation()
    {
      var largeList = new List<int>();
      for (int i = 0; i < 1000000; i++)
      {
          largeList.Add(i);
      }
    
      // 釋放不再需要的記憶體
      largeList.Clear();
      largeList = null;
    }
    
  • I/O 操作效能低下:操作 (如文件讀寫和網路請求) 通常是效能瓶頸。可以通過異步操作和緩存來提高效能。
    1
    2
    3
    4
    5
    6
    7
    
    public async Task<string> ReadFileAsync(string filePath)
    {
      using (StreamReader reader = new StreamReader(filePath))
      {
          return await reader.ReadToEndAsync();
      }
    }
    

小結

  1. 效能優化是提高應用程式效率和用戶體驗的重要步驟。
  2. 通過使用效能分析工具識別效能瓶頸,應用程式優化技巧,並解決常見效能問題,開發者可以確保應用程式在各種環境下高效運行。

安全性考量

在開發應用程式時,安全性是至關重要的。忽視安全性會導致敏感數據泄露、應用程式被攻擊等嚴重後果。本章將介紹常見的安全威脅、安全編碼的最佳實踐以及如何在 C# 中使用加密和散列來保護數據。

常見安全威脅

  • SQL 注入 (SQL Injection) 攻擊者通過將惡意 SQL 程式插入到應用程式的 SQL 查詢中,來篡改或訪問數據庫。
    1
    2
    
    string userInput = "1; DROP TABLE Users"; // 惡意輸入
    string query = "SELECT * FROM Users WHERE UserId = " + userInput;
    
  • 跨站腳本 (XSS) 攻擊者將惡意腳本注入到網頁中,當其他用戶訪問該網頁時,這些腳本會在用戶瀏覽器中執行。
    1
    2
    
      <!-- 用戶輸入未經處理直接顯示在頁面上 -->
      <div id="content"><script>alert('XSS Attack');</script></div>
    
  • 數據洩露 未加密的敏感數據在傳輸或儲存過程中可能被攔截或訪問。
    1
    
    File.WriteAllText("passwords.txt", "user:password"); // 敏感數據未加密
    

安全編碼最佳實踐

  • 使用參數化查詢 參數化查詢可以防止 SQL 注入。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
      string query = "SELECT * FROM Users WHERE UserId = @UserId";
      SqlCommand command = new SqlCommand(query, connection);
      command.Parameters.AddWithValue("@UserId", userInput);
      connection.Open();
      using (SqlDataReader reader = command.ExecuteReader())
      {
          while (reader.Read())
          {
              Console.WriteLine(reader["UserName"]);
          }
      }
    }
    
  • 驗證和轉義用戶輸入 驗證和轉義用戶輸入可以防止 XSS 攻擊。
    1
    2
    3
    4
    5
    6
    7
    8
    
      string SafeHtmlEncode(string input)
      {
          return System.Net.WebUtility.HtmlEncode(input);
      }
    
      string userInput = "<script>alert('XSS');</script>";
      string safeInput = SafeHtmlEncode(userInput);
      Console.WriteLine(safeInput); // 輸出安全的 HTML 編碼內容
    
  • 加密敏感數據 使用加密來保護儲存和傳輸中的敏感數據。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    public string EncryptString(string plainText, string key)
    {
      using (Aes aes = Aes.Create())
      {
          aes.Key = Encoding.UTF8.GetBytes(key);
          aes.IV = new byte[16];
          using (MemoryStream ms = new MemoryStream())
          {
              using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
              {
                  using (StreamWriter sw = new StreamWriter(cs))
                  {
                      sw.Write(plainText);
                  }
              }
              return Convert.ToBase64String(ms.ToArray());
          }
      }
    }
    

使用加密與散列

加密和散列是保護數據安全的兩種重要技術。加密用於保護數據在傳輸和儲存過程中的機密性,而散列用於驗證數據的完整性。

  • 加密:將明文轉換為密文,只有使用正確的密鑰才能解密。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    public string DecryptString(string cipherText, string key)
    {
      using (Aes aes = Aes.Create())
      {
          aes.Key = Encoding.UTF8.GetBytes(key);
          aes.IV = new byte[16];
          using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(cipherText)))
          {
              using (CryptoStream cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
              {
                  using (StreamReader sr = new StreamReader(cs))
                  {
                      return sr.ReadToEnd();
                  }
              }
          }
      }
    }
    
  • 散列:將輸入數據轉換為固定長度的字串,且散列結果唯一對應於輸入數據。常用於儲存密碼。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    public string ComputeHash(string input)
    {
      using (SHA256 sha256 = SHA256.Create())
      {
          byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
          StringBuilder builder = new StringBuilder();
          for (int i = 0; i < bytes.Length; i++)
          {
              builder.Append(bytes[i].ToString("x2"));
          }
          return builder.ToString();
      }
    }
    

小結

  1. 安全性考量是應用程式開發中不可忽視的部分。
  2. 通過了解常見的安全威脅、遵循安全編碼的最佳實踐,以及使用加密和散列技術,可以有效提高應用程式的安全性。

部署與發佈

部署與發佈是軟體開發過程中的最後一步,將開發好的應用程式交付給最終用戶。本章將介紹發佈策略、版本管理與部署工具,以及部署的最佳實踐,幫助開發者順利地將應用程式投入使用。

發佈策略

發佈策略是指如何計劃和管理應用程式的發佈,以確保軟體的穩定性和可用性。

連續交付 (Continuous Delivery)

連續交付是一種軟體工程方法,確保程式在短週期內可以穩定地部署到生產環境。這需要自動化的測試和部署管道。

範例:

  • 每次程式變更都經過自動化測試。
  • 通過自動化部署工具將測試通過的程式部署到生產環境。

藍綠部署 (Blue-Green Deployment)

藍綠部署是一種零停機時間的部署方法,通過維護兩個相同的環境 (藍色和綠色) 來進行應用程式的更新。

範例:

  • 當綠色環境在運行時,將新的應用程式版本部署到藍色環境。
  • 測試藍色環境的應用程式版本。
  • 切換流量到藍色環境,並將藍色環境變為活躍環境。

版本管理與部署工具

有效的版本管理和部署工具可以大大簡化部署過程,確保程式的一致性和可追溯性。

Git

Git 是一個分佈式版本控制系統,用於管理程式庫的變更。

範例:

  • 使用 Git 來管理程式變更。
  • 創建和管理分支,用於開發新功能和修復錯誤。
  • 合併分支並解決合併衝突。

有關 Git 的詳細介紹可查看Git 實用指南:常用命令及最佳實踐一文。

GitHub Actions

GitHub Actions 是一個 CI/CD 工具,允許自動化構建、測試和部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
name: CI/CD Pipeline

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Setup .NET
        uses: actions/setup-dotnet@v1
        with:
          dotnet-version: 6.0.x

      - name: Install dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release --no-restore

      - name: Test
        run: dotnet test --no-restore --verbosity normal

      - name: Deploy
        run: dotnet publish --configuration Release --output ./publish

後面會單獨撰寫一篇 Github Skill 會詳細提到這個功能的用法。

Azure DevOps

Azure DevOps 提供了一整套開發工具,包括版本控制、持續集成和持續交付 (CI/CD) 、以及項目管理。

範例:

  • 使用 Azure Pipelines 來自動化構建、測試和部署。
  • 使用 Azure Repos 來管理程式版本。

部署的最佳實踐

  1. 自動化部署:可以減少人為錯誤,提高部署效率和一致性。
    • 使用 CI/CD 工具來自動化部署流程。
    • 定期檢查和更新自動化腳本,確保它們始終保持最新。
  2. 監控和日誌記錄:部署後的監控和日誌記錄有助於及時發現和解決問題,確保應用程式的穩定性。
    • 使用監控工具 (如 Azure Monitor 或 New Relic) 來監控應用程式效能。
    • 使用集中化日誌管理工具 (如 ELK 堆疊或 Splunk) 來收集和分析日誌。
  3. 回滾策略:在部署過程中遇到問題時,回滾策略可以快速恢復到穩定狀態。
    • 使用藍綠部署或金絲雀部署來實作快速回滾。
    • 保持上一個穩定版本的備份,確保能夠快速恢復。

小結

  1. 部署與發佈是將軟體交付給用戶的關鍵步驟。
  2. 通過制定合理的發佈策略、使用有效的版本管理與部署工具、以及遵循最佳實踐,開發者可以確保應用程式的穩定性和可靠性。

資源與進一步學習

持續學習和保持技術更新是每位開發者成長的重要途徑。本章將介紹一些推薦的學習資源,包括書籍、網站和課程,並討論如何保持技術更新以及加入 C# 社群以獲取更多學習機會和支持。

推薦學習資源 (網站、課程)

網站

  1. Microsoft Docs - C# Documentation
    • 官方文檔,包含了 C# 的所有基礎和進階概念,是學習和參考的絕佳資源。
  2. Pluralsight - C# Courses
    • 提供大量的 C# 視頻課程,從入門到高級均有覆蓋。
  3. Stack Overflow - C# Questions
    • 技術問答網站,能快速找到解決方案和學習最佳實踐。

線上課程

  1. Udemy - Complete C# Masterclass
    • 完整的 C# 教學課程,涵蓋基礎到進階的各種主題。
  2. Coursera - C# Programming for Unity Game Development
    • 針對 Unity 遊戲開發的 C# 課程,適合對遊戲開發有興趣的學習者。
  3. LinkedIn Learning - C# Essential Training
    • 涵蓋 C# 語法和物件導向程式的基礎課程。

書籍部份種類繁多喜好不一,就請自行挑選囉。

如何保持技術更新

  1. 訂閱技術博客和新聞:
    • 訂閱知名技術博客和新聞網站,如 Medium、Dev.to 和 Hacker News,了解最新技術趨勢和最佳實踐。
  2. 參加線上和線下技術會議:
    • 參加 C# 和 .NET 社群的線上和線下會議,如 Microsoft Build、.NET Conf 等,學習最新技術和工具。
  3. 持續學習和實踐:
    • 利用線上學習平台和開源項目進行持續學習和實踐,保持技能更新。
  4. 參加技術討論和社群活動:
    • 參加 C# 和 .NET 技術討論群組和社群活動,如 Stack Overflow、Reddit 的 r/csharp 頻道等,與其他開發者交流經驗和學習新知識。

加入 C# 社群

  1. 參加本地用戶組:
    • 查找並加入本地的 C# 或 .NET 用戶組,參加定期的技術交流和分享活動。
  2. 加入線上社群:
    • 加入線上社群,如 Stack Overflow、Reddit、Slack 等,與全球開發者交流經驗和學習新知識。
  3. 貢獻開源項目:
    • 參與或貢獻 C# 開源項目,與其他開發者合作,學習新技術並提升自己的程式技能。可以在 GitHub 上查找和參與感興趣的開源項目。

小結

  1. 持續學習和保持技術更新是每位開發者成長的關鍵。
  2. 通過利用推薦的學習資源、參加技術會議和社群活動,以及不斷實踐和交流,可以不斷提升自己的技能和知識。

總結

在這篇文章中,我們深入探討了 C# 開發的各個方面,從基礎知識到進階特性,從效能優化到安全性考量,並且覆蓋了測試、部署和資源管理等關鍵主題。 希望這篇文章能夠為您提供有價值的資訊和指導,幫助您在 C# 開發之路上不斷前進和成長。祝您在未來的開發旅程中取得更大的成功!

腳註

  1. 強大且多樣的功能: C# 提供了豐富的語言特性,如異步程式、LINQ、泛型等,使得開發者能夠更加高效地撰寫高效能和可維護的程式。 ↩︎

  2. 優秀的工具支持: Visual Studio 和 Visual Studio Code 等開發工具為 C# 提供了強大的編輯和除錯支持,提升了開發者的工作效率。 ↩︎

  3. 跨平台能力: 隨著 .NET Core 的推出,C# 逐漸實作了跨平台開發,能夠在 Windows、Linux 和 macOS 上運行。 ↩︎

  4. 活躍的社群與豐富的資源: C# 擁有一個活躍的開發者社群,提供了大量的學習資源和開發支持,幫助新手快速入門並解決遇到的各種問題。 ↩︎

  5. **魔法數字 (Magic Numbers) **是指在程式碼中直接使用的數字 (或字串) 常量,這些數字或字串沒有明確的說明其意圖或用途。它們使程式難以理解和維護,因為它們的含義對讀者來說不明顯。 ↩︎

  6. **硬編碼 (Hard Coding) **是指將數值、字串或其他固定資料直接寫入程式碼,而不是使用變數或常數來儲存這些值。硬編碼會使得程式不靈活,修改這些值變得困難且容易出錯。 ↩︎

本文章以 CC BY 4.0 授權