C# - 快速指南



C# - 概述

C# 是一種現代的、通用的、面向物件的程式語言,由微軟開發,並經歐洲計算機制造商協會 (ECMA) 和國際標準化組織 (ISO) 批准。

C# 由 Anders Hejlsberg 及其團隊在開發 .Net Framework 期間開發。

C# 是為公共語言基礎結構 (CLI) 設計的,它由可執行程式碼和執行時環境組成,允許在不同的計算機平臺和架構上使用各種高階語言。

以下原因使得 C# 成為一種廣泛使用的專業語言:

  • 它是一種現代的通用程式語言
  • 它是面向物件的。
  • 它是面向元件的。
  • 它易於學習。
  • 它是一種結構化語言。
  • 它生成高效的程式。
  • 它可以在各種計算機平臺上編譯。
  • 它是 .Net Framework 的一部分。

C# 的強大程式設計特性

儘管 C# 的構造緊密遵循傳統的 C 和 C++ 等高階語言,並且作為一種面向物件的程式語言,它與 Java 具有很強的相似性,但它擁有許多強大的程式設計特性,使其受到全球眾多程式設計師的喜愛。

以下是 C# 的一些重要特性的列表:

  • 布林條件
  • 自動垃圾回收
  • 標準庫
  • 程式集版本控制
  • 屬性和事件
  • 委託和事件管理
  • 易於使用的泛型
  • 索引器
  • 條件編譯
  • 簡單的多執行緒
  • LINQ 和 Lambda 表示式
  • 與 Windows 整合

C# - 環境

線上嘗試選項

我們已線上設定了 C# 程式設計環境,以便您可以線上編譯和執行所有可用的示例。它可以讓您對您正在閱讀的內容充滿信心,並使您能夠使用不同的選項驗證程式。請隨意修改任何示例並在網上執行它。

使用我們位於 CodingGround 的線上編譯器嘗試以下示例。

using System;

namespace HelloWorldApplication {
   
   class HelloWorld {

      static void Main(string[] args) {
         /* my first program in C# */
         Console.WriteLine("Hello World");
         Console.ReadKey();
      }
   }
}

在本教程中提供的多數示例中,您會在我們網站程式碼部分的右上角找到一個“嘗試一下”選項,它會將您帶到線上編譯器。因此,請充分利用它並享受您的學習過程。

在本章中,我們將討論建立 C# 程式所需的工具。我們已經提到 C# 是 .Net framework 的一部分,用於編寫 .Net 應用程式。因此,在討論執行 C# 程式的可用工具之前,讓我們瞭解一下 C# 如何與 .Net framework 相關聯。

.Net Framework

.Net framework 是一個革命性的平臺,可幫助您編寫以下型別的應用程式:

  • Windows 應用程式
  • Web 應用程式
  • Web 服務

.Net framework 應用程式是多平臺應用程式。該框架的設計方式使其可以從以下任何語言中使用:C#、C++、Visual Basic、Jscript、COBOL 等。所有這些語言都可以訪問框架並相互通訊。

.Net framework 包含一個龐大的程式碼庫,供 C# 等客戶端語言使用。以下是 .Net framework 的一些元件:

  • 公共語言執行時 (CLR)
  • .Net Framework 類庫
  • 公共語言規範
  • 公共型別系統
  • 元資料和程式集
  • Windows 窗體
  • ASP.Net 和 ASP.Net AJAX
  • ADO.Net
  • Windows 工作流基礎 (WF)
  • Windows Presentation Foundation
  • Windows Communication Foundation (WCF)
  • LINQ

有關這些元件各自執行的工作,請參閱 ASP.Net - 簡介,有關每個元件的詳細資訊,請參閱 Microsoft 的文件。

C# 的整合開發環境 (IDE)

Microsoft 為 C# 程式設計提供了以下開發工具:

  • Visual Studio 2010 (VS)
  • Visual C# 2010 Express (VCE)
  • Visual Web Developer

後兩個可以從 Microsoft 官方網站免費獲得。使用這些工具,您可以編寫各種 C# 程式,從簡單的命令列應用程式到更復雜的應用程式。您還可以使用基本的文字編輯器(如記事本)編寫 C# 原始碼檔案,並使用命令列編譯器(也是 .NET Framework 的一部分)將程式碼編譯成程式集。

Visual C# Express 和 Visual Web Developer Express 版本是 Visual Studio 的精簡版本,具有相同的介面。它們保留了 Visual Studio 的大部分功能。在本教程中,我們使用了 Visual C# 2010 Express。

您可以從 Microsoft Visual Studio 下載它。它會自動安裝到您的機器上。

注意:安裝 Express 版本需要活動的網際網路連線。

在 Linux 或 Mac OS 上編寫 C# 程式

儘管 .NET Framework 執行在 Windows 作業系統上,但有一些替代版本可以在其他作業系統上執行。Mono 是 .NET Framework 的一個開源版本,它包括一個 C# 編譯器,並在多個作業系統上執行,包括各種 Linux 和 Mac OS 版本。請檢視 Go Mono

Mono 的既定目標不僅能夠跨平臺執行 Microsoft .NET 應用程式,而且還能為 Linux 開發人員提供更好的開發工具。Mono 可以在許多作業系統上執行,包括 Android、BSD、iOS、Linux、OS X、Windows、Solaris 和 UNIX。

C# - 程式結構

在我們學習 C# 程式語言的基本構建塊之前,讓我們先看一下 C# 程式的基本結構,以便在接下來的章節中將其作為參考。

建立 Hello World 程式

C# 程式包含以下部分:

  • 名稱空間宣告
  • 一個類
  • 類方法
  • 類屬性
  • 一個 Main 方法
  • 語句和表示式
  • 註釋

讓我們看一個列印“Hello World”字樣的簡單程式碼:

using System;

namespace HelloWorldApplication {
   
   class HelloWorld {
      
      static void Main(string[] args) {
         /* my first program in C# */
         Console.WriteLine("Hello World");
         Console.ReadKey();
      }
   }
}

當這段程式碼被編譯和執行時,它會產生以下結果:

Hello World

讓我們看一下給定程式的各個部分:

  • 程式的第一行 using System; - using 關鍵字用於在程式中包含 System 名稱空間。一個程式通常有多個 using 語句。

  • 下一行是 namespace 宣告。namespace 是類的集合。HelloWorldApplication 名稱空間包含類 HelloWorld

  • 下一行是 class 宣告,類 HelloWorld 包含程式使用的的資料和方法定義。類通常包含多個方法。方法定義了類的行為。但是,HelloWorld 類只有一個方法 Main

  • 下一行定義了 Main 方法,它是所有 C# 程式的 入口點Main 方法說明了類在執行時所做的事情。

  • 下一行 /*...*/ 被編譯器忽略,它用於在程式中新增 註釋

  • Main 方法使用語句 Console.WriteLine("Hello World"); 指定其行為。

    WriteLine 是在 System 名稱空間中定義的 Console 類的 方法。此語句導致訊息“Hello, World!”顯示在螢幕上。

  • 最後一行 Console.ReadKey(); 是為 VS.NET 使用者準備的。這使得程式等待按鍵,並且當程式從 Visual Studio .NET 啟動時,它可以防止螢幕快速執行和關閉。

值得注意以下幾點:

  • C# 區分大小寫。
  • 所有語句和表示式都必須以分號 (;) 結尾。
  • 程式執行從 Main 方法開始。
  • 與 Java 不同,程式檔名可以與類名不同。

編譯和執行程式

如果您使用 Visual Studio.Net 編譯和執行 C# 程式,請執行以下步驟:

  • 啟動 Visual Studio。

  • 在選單欄上,選擇檔案 -> 新建 -> 專案。

  • 從模板中選擇 Visual C#,然後選擇 Windows。

  • 選擇控制檯應用程式。

  • 為您的專案指定一個名稱,然後單擊“確定”按鈕。

  • 這將在解決方案資源管理器中建立一個新專案。

  • 在程式碼編輯器中編寫程式碼。

  • 單擊“執行”按鈕或按 F5 鍵執行專案。將出現一個命令提示符視窗,其中包含“Hello World”行。

您可以使用命令列而不是 Visual Studio IDE 編譯 C# 程式:

  • 開啟一個文字編輯器並新增上述程式碼。

  • 將檔案儲存為 helloworld.cs

  • 開啟命令提示符工具並轉到儲存檔案的目錄。

  • 鍵入 csc helloworld.cs 並按 Enter 鍵編譯您的程式碼。

  • 如果程式碼中沒有錯誤,命令提示符會將您帶到下一行並生成helloworld.exe可執行檔案。

  • 鍵入helloworld執行您的程式。

  • 您可以在螢幕上看到輸出“Hello World”。

C# - 基本語法

C# 是一種面向物件的程式語言。在面向物件程式設計方法中,程式由各種物件組成,這些物件透過動作相互互動。物件可能採取的動作稱為方法。相同型別的物件被稱為具有相同的型別,或者被稱為屬於同一個類。

例如,讓我們考慮一個矩形物件。它具有諸如長度和寬度之類的屬性。根據設計,它可能需要接受這些屬性值、計算面積和顯示詳細資訊的方法。

讓我們看看矩形類的實現並討論C#的基本語法:

using System;

namespace RectangleApplication {
   
   class Rectangle {
      // member variables
      double length;
      double width;
      
      public void Acceptdetails() {
         length = 4.5;    
         width = 3.5;
      }
      
      public double GetArea() {
         return length * width; 
      }
      
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }
   
   class ExecuteRectangle {
   
      static void Main(string[] args) {
         Rectangle r = new Rectangle();
         r.Acceptdetails();
         r.Display();
         Console.ReadLine(); 
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Length: 4.5
Width: 3.5
Area: 15.75

using關鍵字

任何C#程式中的第一條語句是

using System;

using關鍵字用於在程式中包含名稱空間。一個程式可以包含多個using語句。

class關鍵字

class關鍵字用於宣告一個類。

C#中的註釋

註釋用於解釋程式碼。編譯器會忽略註釋條目。C#程式中的多行註釋以/*開頭,以*/結尾,如下所示:

/* This program demonstrates
The basic syntax of C# programming 
Language */

單行註釋以'//'符號表示。例如,

}//end class Rectangle    

成員變數

變數是類的屬性或資料成員,用於儲存資料。在前面的程式中,Rectangle類有兩個名為lengthwidth的成員變數。

成員函式

函式是一組執行特定任務的語句。類的成員函式在類中宣告。我們的示例類Rectangle包含三個成員函式:AcceptDetailsGetAreaDisplay

例項化類

在前面的程式中,類ExecuteRectangle包含Main()方法並例項化Rectangle類。

識別符號

識別符號是用於識別類、變數、函式或任何其他使用者定義項的名稱。C#中命名類的基本規則如下:

  • 名稱必須以字母開頭,後面可以跟一系列字母、數字(0-9)或下劃線。識別符號的第一個字元不能是數字。

  • 它不能包含任何嵌入的空格或符號,例如? - + ! @ # % ^ & * ( ) [ ] { } . ; : " ' / 和 \。但是,可以使用下劃線(_)。

  • 它不應是C#關鍵字。

C#關鍵字

關鍵字是預定義給C#編譯器的保留字。這些關鍵字不能用作識別符號。但是,如果您想將這些關鍵字用作識別符號,則可以在關鍵字前加上@字元。

在C#中,某些識別符號在程式碼上下文中具有特殊含義,例如get和set稱為上下文關鍵字。

下表列出了C#中的保留關鍵字和上下文關鍵字:

保留關鍵字
abstract as base bool break byte case
catch char checked class const continue decimal
default delegate do double else enum event
explicit extern false finally fixed float for
foreach goto if implicit in in (泛型修飾符) int
interface internal is lock long namespace new
null object operator out out (泛型修飾符) override params
private protected public readonly ref return sbyte
sealed short sizeof stackalloc static string struct
switch this throw true try typeof uint
ulong unchecked unsafe ushort using virtual void
volatile while
上下文關鍵字
add alias ascending descending dynamic from get
global group into join let orderby partial (型別)
partial
(方法)
remove select set

C# - 資料型別

C#中的變數分為以下型別:

  • 值型別
  • 引用型別
  • 指標型別

值型別

值型別變數可以直接賦值。它們派生自類System.ValueType

值型別直接包含資料。一些示例包括int、char和float,它們分別儲存數字、字母和浮點數。當您宣告int型別時,系統會分配記憶體來儲存該值。

下表列出了C# 2010中可用的值型別:

型別 表示 範圍 預設值
bool 布林值 真或假 False
byte 8位無符號整數 0到255 0
char 16位Unicode字元 U +0000到U +ffff '\0'
decimal 128位精確十進位制值,具有28-29位有效數字 (-7.9 x 1028到7.9 x 1028) / 100到28 0.0M
double 64位雙精度浮點數型別 (+/-)5.0 x 10-324到(+/-)1.7 x 10308 0.0D
float 32位單精度浮點數型別 -3.4 x 1038到+ 3.4 x 1038 0.0F
int 32位有符號整數型別 -2,147,483,648到2,147,483,647 0
long 64位有符號整數型別 -9,223,372,036,854,775,808到9,223,372,036,854,775,807 0L
sbyte 8位有符號整數型別 -128到127 0
short 16位有符號整數型別 -32,768到32,767 0
uint 32位無符號整數型別 0到4,294,967,295 0
ulong 64位無符號整數型別 0到18,446,744,073,709,551,615 0
ushort 16位無符號整數型別 0到65,535 0

要獲取特定平臺上型別或變數的確切大小,可以使用sizeof方法。表示式sizeof(type)以位元組為單位生成物件或型別的儲存大小。以下是如何在任何機器上獲取int型別大小的示例:

using System;

namespace DataTypeApplication {
   
   class Program {

      static void Main(string[] args) {
         Console.WriteLine("Size of int: {0}", sizeof(int));
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Size of int: 4

引用型別

引用型別不包含儲存在變數中的實際資料,而是包含對變數的引用。

換句話說,它們引用一個記憶體位置。使用多個變數,引用型別可以引用一個記憶體位置。如果一個變數更改了記憶體位置中的資料,則另一個變數會自動反映此值變化。內建引用型別的示例包括:object、dynamic和string

物件型別

物件型別是C#公共型別系統(CTS)中所有資料型別的最終基類。Object是System.Object類的別名。物件型別可以分配任何其他型別的值,包括值型別、引用型別、預定義或使用者定義型別。但是,在分配值之前,需要進行型別轉換。

當值型別轉換為物件型別時,稱為裝箱,反之,當物件型別轉換為值型別時,稱為拆箱

object obj;
obj = 100; // this is boxing

動態型別

您可以在動態資料型別變數中儲存任何型別的值。這些型別的變數的型別檢查在執行時進行。

宣告動態型別的語法如下:

dynamic <variable_name> = value;

例如,

dynamic d = 20;

動態型別類似於物件型別,不同之處在於物件型別變數的型別檢查在編譯時進行,而動態型別變數的型別檢查在執行時進行。

字串型別

字串型別允許您將任何字串值分配給變數。字串型別是System.String類的別名。它派生自物件型別。字串型別的值可以使用兩種形式的字串文字分配:帶引號和@帶引號。

例如,

String str = "Tutorials Point";

@帶引號的字串文字如下所示:

@"Tutorials Point";

使用者定義的引用型別包括:類、介面或委託。我們將在後面的章節中討論這些型別。

指標型別

指標型別變數儲存另一個型別的記憶體地址。C#中的指標與C或C++中的指標具有相同的功能。

宣告指標型別的語法如下:

type* identifier;

例如,

char* cptr;
int* iptr;

我們將在“不安全程式碼”章節中討論指標型別。

C# - 型別轉換

型別轉換是指將一種型別的資料轉換為另一種型別。它也稱為型別強制轉換。在C#中,型別強制轉換有兩種形式:

  • 隱式型別轉換 - 這些轉換由C#以型別安全的方式執行。例如,從較小的整數型別到較大的整數型別的轉換,以及從派生類到基類的轉換。

  • 顯式型別轉換 - 這些轉換由使用者使用預定義函式顯式執行。顯式轉換需要強制轉換運算子。

以下示例顯示了顯式型別轉換:

using System;

namespace TypeConversionApplication {

   class ExplicitConversion {
   
      static void Main(string[] args) {
         double d = 5673.74; 
         int i;
         
         // cast double to int.
         i = (int)d;
         Console.WriteLine(i);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

5673

C#型別轉換方法

C#提供以下內建型別轉換方法:

序號 方法和描述
1

ToBoolean

儘可能地將型別轉換為布林值。

2

ToByte

將型別轉換為位元組。

3

ToChar

儘可能地將型別轉換為單個Unicode字元。

4

ToDateTime

將型別(整數或字串型別)轉換為日期時間結構。

5

ToDecimal

將浮點數或整數型別轉換為十進位制型別。

6

ToDouble

將型別轉換為雙精度型別。

7

ToInt16

將型別轉換為16位整數。

8

ToInt32

將型別轉換為32位整數。

9

ToInt64

將型別轉換為64位整數。

10

ToSbyte

將型別轉換為有符號位元組型別。

11

ToSingle

將型別轉換為小浮點數。

12

ToString

將型別轉換為字串。

13

ToType

將型別轉換為指定的型別。

14

ToUInt16

將型別轉換為無符號int型別。

15

ToUInt32

將型別轉換為無符號長整型。

16

ToUInt64

將型別轉換為無符號大整數。

以下示例將各種值型別轉換為字串型別:

using System;

namespace TypeConversionApplication {
   
   class StringConversion {
      
      static void Main(string[] args) {
         int i = 75;
         float f = 53.005f;
         double d = 2345.7652;
         bool b = true;

         Console.WriteLine(i.ToString());
         Console.WriteLine(f.ToString());
         Console.WriteLine(d.ToString());
         Console.WriteLine(b.ToString());
         Console.ReadKey();
            
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

75
53.005
2345.7652
True

C# - 變數

變數只不過是賦予儲存區域的名稱,我們的程式可以操作這些儲存區域。C#中的每個變數都具有特定的型別,該型別決定了變數記憶體的大小和佈局、可以儲存在該記憶體中的值的範圍以及可以應用於變數的操作集。

C#中提供的基本值型別可以分為:

型別 示例
整數型別 sbyte、byte、short、ushort、int、uint、long、ulong和char
浮點數型別 float和double
十進位制型別 decimal
布林型別 真或假值,根據分配情況而定
可空型別 可空資料型別

C#還允許定義其他變數值型別,例如enum,以及變數引用型別,例如class,我們將在後續章節中介紹。

定義變數

C#中變數定義的語法如下:

<data_type> <variable_list>;

這裡,data_type必須是有效的C#資料型別,包括char、int、float、double或任何使用者定義的資料型別,而variable_list可以包含一個或多個以逗號分隔的識別符號名稱。

這裡顯示了一些有效的變數定義:

int i, j, k;
char c, ch;
float f, salary;
double d;

您可以在定義時初始化變數,如下所示:

int i = 100;

初始化變數

變數使用等號後跟常量表達式進行初始化(賦值)。初始化的一般形式如下:

variable_name = value;

變數可以在宣告時進行初始化。初始化器由一個等號後跟一個常量表達式組成,例如:

<data_type> <variable_name> = value;

一些例子如下:

int d = 3, f = 5;    /* initializing d and f. */
byte z = 22;         /* initializes z. */
double pi = 3.14159; /* declares an approximation of pi. */
char x = 'x';        /* the variable x has the value 'x'. */

良好的程式設計習慣是正確初始化變數,否則有時程式可能會產生意外的結果。

以下示例使用了各種型別的變數:

using System;

namespace VariableDefinition {

   class Program {
   
      static void Main(string[] args) {
         short a;
         int b ;
         double c;

         /* actual initialization */
         a = 10;
         b = 20;
         c = a + b;
         Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

a = 10, b = 20, c = 30

從使用者接受值

System 名稱空間中的 Console 類提供了一個函式 ReadLine(),用於接受來自使用者的輸入並將其儲存到變數中。

例如,

int num;
num = Convert.ToInt32(Console.ReadLine());

函式 Convert.ToInt32() 將使用者輸入的資料轉換為 int 資料型別,因為 Console.ReadLine() 接受字串格式的資料。

C# 中的左值和右值表示式

C# 中有兩種表示式:

  • 左值 (lvalue) - 可以出現在賦值運算子左側或右側的表示式。

  • 右值 (rvalue) - 可以出現在賦值運算子右側但不能出現在左側的表示式。

變數是左值,因此可以出現在賦值運算子的左側。數字字面量是右值,因此不能被賦值,也不能出現在左側。以下是一個有效的 C# 語句:

int g = 20;

但以下語句無效,會生成編譯時錯誤:

10 = 20;

C# - 常量和字面量

常量是指程式在其執行過程中不能更改的固定值。這些固定值也稱為字面量。常量可以是任何基本資料型別,例如整數常量、浮點常量、字元常量或字串字面量。還有列舉常量。

常量與普通變數的處理方式相同,只是它們的數值在定義後不能修改。

整數字面量

整數字面量可以是十進位制或十六進位制常量。字首指定基數或進位制:十六進位制為 0x 或 0X,十進位制沒有字首。

整數字面量還可以帶字尾,該字尾是 U 和 L 的組合,分別表示無符號和長整數。字尾可以是大寫或小寫,並且可以按任意順序排列。

以下是一些整數字面量的示例:

212         /* Legal */
215u        /* Legal */
0xFeeL      /* Legal */

以下是各種型別整數字面量的其他示例:

85         /* decimal */
0x4b       /* hexadecimal */
30         /* int */
30u        /* unsigned int */
30l        /* long */
30ul       /* unsigned long */

浮點字面量

浮點字面量包含整數部分、小數點、小數部分和指數部分。您可以以十進位制形式或指數形式表示浮點字面量。

以下是一些浮點字面量的示例:

3.14159       /* Legal */
314159E-5F    /* Legal */
510E          /* Illegal: incomplete exponent */
210f          /* Illegal: no decimal or exponent */
.e55          /* Illegal: missing integer or fraction */

以十進位制形式表示時,必須包含小數點、指數或兩者;而使用指數形式表示時,必須包含整數部分、小數部分或兩者。帶符號的指數由 e 或 E 引入。

字元常量

字元字面量用單引號括起來。例如,'x' 可以儲存在 char 型別的簡單變數中。字元字面量可以是普通字元(如 'x')、轉義序列(如 '\t')或通用字元(如 '\u02C0')。

在 C# 中,某些字元在前面加上反斜槓時具有特殊含義,用於表示換行符 (\n) 或製表符 (\t) 等。以下是一些此類轉義序列程式碼的列表:

轉義序列 含義
\\ \ 字元
\' ' 字元
\" " 字元
\? ? 字元
\a 警報或鈴聲
\b 退格
\f 換頁
\n 換行
\r 回車
\t 水平製表符
\v 垂直製表符
\xhh . . . 一個或多個數字的十六進位制數

以下示例顯示了一些轉義序列字元:

using System;

namespace EscapeChar {

   class Program {
   
      static void Main(string[] args) {
         Console.WriteLine("Hello\tWorld\n\n");
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Hello   World

字串字面量

字串字面量或常量用雙引號 "" 或 @"" 括起來。字串包含與字元字面量類似的字元:普通字元、轉義序列和通用字元。

您可以使用字串字面量將長行分解成多行,並使用空格分隔各個部分。

以下是一些字串字面量的示例。這三種形式都是相同的字串。

"hello, dear"
"hello, \
dear"
"hello, " "d" "ear"
@"hello dear"

定義常量

常量使用 const 關鍵字定義。定義常量的語法如下:

const <data_type> <constant_name> = value;

以下程式演示瞭如何在程式中定義和使用常量:

using System;

namespace DeclaringConstants {

    class Program {
    
        static void Main(string[] args) {
            const double pi = 3.14159;   
            
            // constant declaration 
            double r;
            Console.WriteLine("Enter Radius: ");
            r = Convert.ToDouble(Console.ReadLine());
            double areaCircle = pi * r * r;
            Console.WriteLine("Radius: {0}, Area: {1}", r, areaCircle);
            Console.ReadLine();
        }
    }
}

當以上程式碼編譯並執行時,會產生以下結果:

Enter Radius: 
3
Radius: 3, Area: 28.27431

C# - 運算子

運算子是告訴編譯器執行特定數學或邏輯操作的符號。C# 具有豐富的內建運算子集,並提供以下型別的運算子:

  • 算術運算子
  • 關係運算符
  • 邏輯運算子
  • 位運算子
  • 賦值運算子
  • 其他運算子

本教程將逐一解釋算術、關係、邏輯、位、賦值和其他運算子。

算術運算子

下表顯示了 C# 支援的所有算術運算子。假設變數 A 為 10,變數 B 為 20,則:

顯示示例

運算子 描述 示例
+ 將兩個運算元相加 A + B = 30
- 從第一個運算元中減去第二個運算元 A - B = -10
* 將兩個運算元相乘 A * B = 200
/ 將分子除以分母 B / A = 2
% 模運算子,整數除法後的餘數 B % A = 0
++ 遞增運算子將整數的值增加 1 A++ = 11
-- 遞減運算子將整數的值減少 1 A-- = 9

關係運算符

下表顯示了 C# 支援的所有關係運算符。假設變數 A 為 10,變數 B 為 20,則:

顯示示例

運算子 描述 示例
== 檢查兩個運算元的值是否相等,如果相等則條件為真。 (A == B) 為假。
!= 檢查兩個運算元的值是否不相等,如果不相等則條件為真。 (A != B) 為真。
> 檢查左側運算元的值是否大於右側運算元的值,如果大於則條件為真。 (A > B) 為假。
< 檢查左側運算元的值是否小於右側運算元的值,如果小於則條件為真。 (A < B) 為真。
>= 檢查左側運算元的值是否大於或等於右側運算元的值,如果大於或等於則條件為真。 (A >= B) 為假。
<= 檢查左側運算元的值是否小於或等於右側運算元的值,如果小於或等於則條件為真。 (A <= B) 為真。

邏輯運算子

下表顯示了 C# 支援的所有邏輯運算子。假設變數 A 的布林值為 true,變數 B 的布林值為 false,則:

顯示示例

運算子 描述 示例
&& 稱為邏輯與運算子。如果兩個運算元均非零,則條件為真。 (A && B) 為假。
|| 稱為邏輯或運算子。如果兩個運算元中的任何一個非零,則條件為真。 (A || B) 為真。
! 稱為邏輯非運算子。用於反轉其運算元的邏輯狀態。如果條件為真,則邏輯非運算子將使其為假。 !(A && B) 為真。

位運算子

位運算子在位上操作,並執行逐位操作。&、| 和 ^ 的真值表如下:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假設 A = 60;B = 13;則它們的二進位制格式如下:

A = 0011 1100

B = 0000 1101

-------------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

C# 支援的位運算子列在下表中。假設變數 A 為 60,變數 B 為 13,則:

顯示示例

運算子 描述 示例
& 二進位制與運算子將位複製到結果中,如果該位存在於兩個運算元中。 (A & B) = 12,即 0000 1100
| 二進位制或運算子將位複製到結果中,如果該位存在於任一運算元中。 (A | B) = 61,即 0011 1101
^ 二進位制異或運算子將位複製到結果中,如果該位在一個運算元中設定,但在另一個運算元中未設定。 (A ^ B) = 49,即 0011 0001
~ 二進位制取反運算子是一元運算子,具有“翻轉”位的功效。 (~A ) = -61,由於帶符號二進位制數,在二進位制補碼中為 1100 0011。
<< 二進位制左移運算子。左側運算元的值向左移動右側運算元指定的位數。 A << 2 = 240,即 1111 0000
>> 二進位制右移運算子。左側運算元的值向右移動右側運算元指定的位數。 A >> 2 = 15,即 0000 1111

賦值運算子

C# 支援以下賦值運算子:

顯示示例

運算子 描述 示例
= 簡單賦值運算子,將右側運算元的值賦給左側運算元 C = A + B 將 A + B 的值賦給 C
+= 加法賦值運算子,將右側運算元加到左側運算元上,並將結果賦給左側運算元 C += A 等價於 C = C + A
-= 減法賦值運算子,從左側運算元中減去右側運算元,並將結果賦給左側運算元 C -= A 等價於 C = C - A
*= 乘法賦值運算子,將右側運算元乘以左側運算元,並將結果賦給左側運算元 C *= A 等價於 C = C * A
/= 除法賦值運算子,將左側運算元除以右側運算元,並將結果賦給左側運算元 C /= A 等價於 C = C / A
%= 模賦值運算子,使用兩個運算元進行模運算,並將結果賦給左側運算元 C %= A 等價於 C = C % A
<<= 左移賦值運算子 C <<= 2 等價於 C = C << 2
>>= 右移賦值運算子 C >>= 2 等價於 C = C >> 2
&= 按位與賦值運算子 C &= 2 等價於 C = C & 2
^= 按位異或賦值運算子 C ^= 2 等價於 C = C ^ 2
|= 按位或賦值運算子 C |= 2 等價於 C = C | 2

其他運算子

C# 支援一些其他重要的運算子,包括sizeof、typeof? :

顯示示例

運算子 描述 示例
sizeof() 返回資料型別的尺寸。 sizeof(int) 返回 4。
typeof() 返回類的型別。 typeof(StreamReader);
& 返回變數的地址。 &a; 返回變數的實際地址。
* 指向變數的指標。 *a; 建立名為 'a' 的指向變數的指標。
? : 條件表示式 如果條件為真 ? 則值為 X : 否則值為 Y
is 確定物件是否屬於某種型別。 If( Ford is Car) // 檢查 Ford 是否是 Car 類的物件。
as 如果轉換失敗,則不引發異常的轉換。 Object obj = new StringReader("Hello");

StringReader r = obj as StringReader;

C# 中的運算子優先順序

運算子優先順序決定了表示式中項的組合方式。這會影響表示式的計算。某些運算子比其他運算子具有更高的優先順序;例如,乘法運算子的優先順序高於加法運算子。

例如 x = 7 + 3 * 2; 這裡,x 被賦值為 13,而不是 20,因為運算子 * 的優先順序高於 +,所以首先計算 3*2,然後將 7 加到結果中。

這裡,優先順序最高的運算子出現在表的最上面,優先順序最低的出現在最下面。在表示式中,優先順序高的運算子首先計算。

顯示示例

類別 運算子 結合性
字尾 () [] -> . ++ - - 從左到右
一元 + - ! ~ ++ -- (type)* & sizeof 從右到左
乘法 * / % 從左到右
加法 + - 從左到右
移位 << >> 從左到右
關係 < <= > >= 從左到右
相等性 == != 從左到右
按位與 & 從左到右
按位異或 ^ 從左到右
按位或 | 從左到右
邏輯與 && 從左到右
邏輯或 || 從左到右
條件 ?: 從右到左
賦值 = += -= *= /= %=>>= <<= &= ^= |= 從右到左
逗號 , 從左到右

C# - 決策

決策結構要求程式設計師指定一個或多個條件,由程式進行評估或測試,以及在確定條件為真時要執行的語句或語句組,以及可選地,在確定條件為假時要執行的其他語句。

以下是大多數程式語言中常見的決策結構的一般形式:

Decision making statements in C#

C# 提供以下型別的決策語句。點選以下連結檢視其詳細資訊。

序號 語句 & 描述
1 if 語句

if 語句由一個布林表示式後跟一個或多個語句組成。

2 if...else 語句

if 語句後面可以跟一個可選的else 語句,當布林表示式為假時執行。

3 巢狀 if 語句

可以在另一個ifelse if 語句中使用一個ifelse if 語句。

4 switch 語句

switch 語句允許測試一個變數與一系列值是否相等。

5 巢狀 switch 語句

可以在另一個switch 語句中使用一個switch 語句。

?: 運算子

我們在上一章中介紹了條件運算子 ? :,它可以用來代替if...else 語句。它具有以下一般形式:

Exp1 ? Exp2 : Exp3;

其中 Exp1、Exp2 和 Exp3 是表示式。注意冒號的使用和位置。

?: 表示式的值如下確定:首先計算 Exp1。如果為真,則計算 Exp2,並將其作為整個 ? 表示式的值。如果 Exp1 為假,則計算 Exp3,並將其值作為表示式的值。

C# - 迴圈

可能存在需要多次執行程式碼塊的情況。通常,語句是按順序執行的:函式中的第一個語句首先執行,然後是第二個語句,依此類推。

程式語言提供了各種控制結構,允許更復雜的執行路徑。

迴圈語句允許我們多次執行一個語句或一組語句,以下是大多數程式語言中迴圈語句的一般形式:

Loop Architecture

C# 提供以下型別的迴圈來處理迴圈需求。點選以下連結檢視其詳細資訊。

序號 迴圈型別 & 描述
1 while 迴圈

當給定條件為真時,它重複執行一個語句或一組語句。它在執行迴圈體之前測試條件。

2 for 迴圈

它多次執行一系列語句,並簡化管理迴圈變數的程式碼。

3 do...while 迴圈

它類似於 while 語句,只是它在迴圈體末尾測試條件。

4 巢狀迴圈

可以在任何其他 while、for 或 do..while 迴圈中使用一個或多個迴圈。

迴圈控制語句

迴圈控制語句改變執行的正常順序。當執行離開作用域時,在該作用域中建立的所有自動物件都會被銷燬。

C# 提供以下控制語句。點選以下連結檢視其詳細資訊。

序號 控制語句 & 描述
1 break 語句

終止迴圈switch 語句,並將執行轉移到迴圈或 switch 後面的語句。

2 continue 語句

導致迴圈跳過其主體剩餘部分,並在重新迭代之前立即重新測試其條件。

無限迴圈

如果條件永遠不會變為假,則迴圈將變成無限迴圈。for 迴圈通常用於此目的。由於構成 for 迴圈的三個表示式都不需要,因此可以透過將條件表示式留空來建立無限迴圈。

示例

using System;

namespace Loops {

   class Program {
   
      static void Main(string[] args) {
         for (; ; ) {
            Console.WriteLine("Hey! I am Trapped");
         }
      }
   }
} 

當條件表示式不存在時,它被假定為真。你可能有初始化和增量表達式,但程式設計師更常使用 for(;;) 構造來表示無限迴圈。

C# - 封裝

封裝被定義為“將一個或多個專案封裝在物理或邏輯包中的過程”。在面向物件程式設計方法中,封裝防止訪問實現細節。

抽象和封裝是面向物件程式設計中的相關特性。抽象允許顯示相關資訊,而封裝使程式設計師能夠實現所需的抽象級別

封裝是透過使用訪問修飾符實現的。訪問修飾符定義了類成員的作用域和可見性。C# 支援以下訪問修飾符:

  • Public
  • Private
  • Protected
  • Internal
  • Protected internal

Public 訪問修飾符

Public 訪問修飾符允許類將其成員變數和成員函式公開給其他函式和物件。任何公共成員都可以從類外部訪問。

以下示例說明了這一點:

using System;

namespace RectangleApplication {

   class Rectangle {
      //member variables
      public double length;
      public double width;
      
      public double GetArea() {
         return length * width;
      }
      
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle
   
   class ExecuteRectangle {
      static void Main(string[] args) {
         Rectangle r = new Rectangle();
         r.length = 4.5;
         r.width = 3.5;
         r.Display();
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Length: 4.5
Width: 3.5
Area: 15.75

在前面的示例中,成員變數 length 和 width 被宣告為public,因此可以使用 Rectangle 類的例項(名為r)從函式 Main() 中訪問它們。

成員函式Display()GetArea() 也可以直接訪問這些變數,而無需使用類的任何例項。

成員函式Display() 也被宣告為public,因此也可以使用 Rectangle 類的例項(名為r)從Main() 中訪問它。

Private 訪問修飾符

Private 訪問修飾符允許類將其成員變數和成員函式隱藏在其他函式和物件之外。只有同一類的函式才能訪問其私有成員。即使是類的例項也無法訪問其私有成員。

以下示例說明了這一點:

using System;

namespace RectangleApplication {

   class Rectangle {
      //member variables
      private double length;
      private double width;
      
      public void Acceptdetails() {
         Console.WriteLine("Enter Length: ");
         length = Convert.ToDouble(Console.ReadLine());
         Console.WriteLine("Enter Width: ");
         width = Convert.ToDouble(Console.ReadLine());
      }
      
      public double GetArea() {
         return length * width;
      }
      
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle
   
   class ExecuteRectangle {
      static void Main(string[] args) {
         Rectangle r = new Rectangle();
         r.Acceptdetails();
         r.Display();
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Enter Length:
4.4
Enter Width:
3.3
Length: 4.4
Width: 3.3
Area: 14.52

在前面的示例中,成員變數 length 和 width 被宣告為private,因此無法從函式 Main() 中訪問它們。成員函式AcceptDetails()Display() 可以訪問這些變數。由於成員函式AcceptDetails()Display() 被宣告為public,因此可以使用 Rectangle 類的例項(名為r)從Main() 中訪問它們。

Protected 訪問修飾符

Protected 訪問修飾符允許子類訪問其基類的成員變數和成員函式。這樣,它有助於實現繼承。我們將在繼承章節中更詳細地討論這一點。

Internal 訪問修飾符

Internal 訪問修飾符允許類將其成員變數和成員函式公開給當前程式集中的其他函式和物件。換句話說,任何具有 internal 訪問修飾符的成員都可以從定義該成員的應用程式中定義的任何類或方法中訪問。

以下程式說明了這一點:

using System;

namespace RectangleApplication {

   class Rectangle {
      //member variables
      internal double length;
      internal double width;
      
      double GetArea() {
         return length * width;
      }
      
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle
   
   class ExecuteRectangle {
      static void Main(string[] args) {
         Rectangle r = new Rectangle();
         r.length = 4.5;
         r.width = 3.5;
         r.Display();
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Length: 4.5
Width: 3.5
Area: 15.75

在前面的示例中,請注意成員函式GetArea() 沒有宣告任何訪問修飾符。那麼,如果我們不提及任何訪問修飾符,類成員的預設訪問修飾符是什麼?它是private

Protected Internal 訪問修飾符

protected internal 訪問修飾符允許類將其成員變數和成員函式隱藏在其他類物件和函式之外,除了同一應用程式中的子類。這在實現繼承時也會用到。

C# - 方法

方法是一組一起執行任務的語句。每個 C# 程式至少有一個包含名為 Main 的方法的類。

要使用方法,你需要:

  • 定義方法
  • 呼叫方法

在 C# 中定義方法

定義方法時,基本上是在宣告其結構的元素。在 C# 中定義方法的語法如下:

<Access Specifier> <Return Type> <Method Name>(Parameter List) {
   Method Body
}

以下是方法的各個元素:

  • 訪問修飾符 - 確定從另一個類訪問變數或方法的可見性。

  • 返回型別 - 方法可以返回值。返回型別是方法返回的值的資料型別。如果方法不返回值,則返回型別為void

  • 方法名稱 - 方法名稱是唯一的識別符號,並且區分大小寫。它不能與類中宣告的任何其他識別符號相同。

  • 引數列表 - 引數括在括號內,用於向方法傳遞和接收資料。引數列表指的是方法的引數的型別、順序和數量。引數是可選的;也就是說,方法可能不包含任何引數。

  • 方法體 - 包含完成所需活動所需的指令集。

示例

以下程式碼片段顯示了一個函式FindMax,它接受兩個整數值並返回兩者中較大的一個。它具有 public 訪問修飾符,因此可以使用類的例項從類外部訪問它。

class NumberManipulator {

   public int FindMax(int num1, int num2) {
      /* local variable declaration */
      int result;

      if (num1 > num2)
         result = num1;
      else
         result = num2;

      return result;
   }
   ...
}

在 C# 中呼叫方法

可以使用方法的名稱來呼叫方法。以下示例說明了這一點:

using System;

namespace CalculatorApplication {

   class NumberManipulator {
   
      public int FindMax(int num1, int num2) {
         /* local variable declaration */
         int result;
         
         if (num1 > num2)
            result = num1;
         else
            result = num2;
         return result;
      }
      
      static void Main(string[] args) {
         /* local variable definition */
         int a = 100;
         int b = 200;
         int ret;
         NumberManipulator n = new NumberManipulator();

         //calling the FindMax method
         ret = n.FindMax(a, b);
         Console.WriteLine("Max value is : {0}", ret );
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Max value is : 200

你也可以使用類的例項從其他類呼叫公共方法。例如,方法FindMax 屬於NumberManipulator 類,你可以從另一個類Test 中呼叫它。

using System;

namespace CalculatorApplication {

   class NumberManipulator {
   
      public int FindMax(int num1, int num2) {
         /* local variable declaration */
         int result;
         
         if(num1 > num2)
            result = num1;
         else
            result = num2;
         
         return result;
      }
   }
   
   class Test {
   
      static void Main(string[] args) {
         /* local variable definition */
         int a = 100;
         int b = 200;
         int ret;
         NumberManipulator n = new NumberManipulator();
         
         //calling the FindMax method
         ret = n.FindMax(a, b);
         Console.WriteLine("Max value is : {0}", ret );
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Max value is : 200

遞迴方法呼叫

方法可以呼叫自身。這稱為遞迴。以下是一個使用遞迴函式計算給定數字的階乘的示例:

using System;

namespace CalculatorApplication {

   class NumberManipulator {
   
      public int factorial(int num) {
         /* local variable declaration */
         int result;
         if (num == 1) {
            return 1;
         }
         else {
            result = factorial(num - 1) * num;
            return result;
         }
      }
      
      static void Main(string[] args) {
         NumberManipulator n = new NumberManipulator();
         //calling the factorial method {0}", n.factorial(6));
         Console.WriteLine("Factorial of 7 is : {0}", n.factorial(7));
         Console.WriteLine("Factorial of 8 is : {0}", n.factorial(8));
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Factorial of 6 is: 720
Factorial of 7 is: 5040
Factorial of 8 is: 40320

向方法傳遞引數

呼叫帶引數的方法時,需要將引數傳遞給方法。引數傳遞給方法有三種方式:

序號 機制 & 描述
1 值引數

此方法將引數的實際值複製到函式的形式引數中。在這種情況下,在函式內部對引數所做的更改不會影響引數。

2 引用引數

此方法將引數的記憶體地址的引用複製到形式引數中。這意味著對引數所做的更改會影響引數。

3 輸出引數

此方法有助於返回多個值。

C# - 可空型別

C# 提供了一種特殊的資料型別,即可空型別,您可以為其分配正常範圍的值以及空值。

例如,您可以將從 -2,147,483,648 到 2,147,483,647 的任何值或 null 儲存在 Nullable<Int32> 變數中。類似地,您可以在 Nullable<bool> 變數中分配 true、false 或 null。宣告可空型別的語法如下所示:

< data_type> ? <variable_name> = null;

以下示例演示了可空資料型別的用法:

using System;

namespace CalculatorApplication {

   class NullablesAtShow {
   
      static void Main(string[] args) {
         int? num1 = null;
         int? num2 = 45;
         double? num3 = new double?();
         double? num4 = 3.14157;
         
         bool? boolval = new bool?();

         // display the values
         Console.WriteLine("Nullables at Show: {0}, {1}, {2}, {3}", num1, num2, num3, num4);
         Console.WriteLine("A Nullable boolean value: {0}", boolval);
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Nullables at Show: , 45,  , 3.14157
A Nullable boolean value:

空合併運算子 (??)

空合併運算子與可空值型別和引用型別一起使用。它用於將運算元轉換為另一個可空(或不可空)值型別運算元的型別,其中允許隱式轉換。

如果第一個運算元的值為 null,則運算子返回第二個運算元的值,否則返回第一個運算元的值。以下示例說明了這一點:

using System;

namespace CalculatorApplication {

   class NullablesAtShow {
   
      static void Main(string[] args) {
         double? num1 = null;
         double? num2 = 3.14157;
         double num3;
         num3 = num1 ?? 5.34;      
         Console.WriteLine(" Value of num3: {0}", num3);
         num3 = num2 ?? 5.34;
         Console.WriteLine(" Value of num3: {0}", num3);
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Value of num3: 5.34
Value of num3: 3.14157

C# - 陣列

陣列儲存相同型別元素的固定大小的順序集合。陣列用於儲存資料集合,但通常更有用的是將陣列視為儲存在連續記憶體位置的相同型別變數的集合。

與其宣告單獨的變數,如 number0、number1、... 和 number99,不如宣告一個數組變數,如 numbers,並使用 numbers[0]、numbers[1] 和 ...、numbers[99] 來表示單獨的變數。陣列中的特定元素透過索引訪問。

所有陣列都由連續的記憶體位置組成。最低地址對應於第一個元素,最高地址對應於最後一個元素。

Arrays in C#

宣告陣列

要在 C# 中宣告陣列,可以使用以下語法:

datatype[] arrayName;

其中,

  • 資料型別用於指定陣列中元素的型別。

  • [ ] 指定陣列的秩。秩指定陣列的大小。

  • 陣列名稱指定陣列的名稱。

例如,

double[] balance;

初始化陣列

宣告陣列不會在記憶體中初始化陣列。當陣列變數被初始化時,您可以為陣列分配值。

陣列是一種引用型別,因此您需要使用new關鍵字來建立陣列的例項。例如,

double[] balance = new double[10];

為陣列賦值

您可以使用索引號為各個陣列元素賦值,例如:

double[] balance = new double[10];
balance[0] = 4500.0;

您可以在宣告時為陣列賦值,如下所示:

double[] balance = { 2340.0, 4523.69, 3421.0};

您還可以建立和初始化陣列,如下所示:

int [] marks = new int[5]  { 99,  98, 92, 97, 95};

您也可以省略陣列的大小,如下所示:

int [] marks = new int[]  { 99,  98, 92, 97, 95};

您可以將一個數組變數複製到另一個目標陣列變數中。在這種情況下,目標和源都指向相同的記憶體位置:

int [] marks = new int[]  { 99,  98, 92, 97, 95};
int[] score = marks;

當您建立陣列時,C# 編譯器會隱式地將每個陣列元素初始化為一個預設值,具體取決於陣列型別。例如,對於 int 陣列,所有元素都初始化為 0。

訪問陣列元素

透過索引陣列名稱來訪問元素。這可以透過在陣列名稱後面方括號內放置元素的索引來完成。例如,

double salary = balance[9];

以下示例演示了上述概念,即宣告、賦值和訪問陣列:

using System;

namespace ArrayApplication {

   class MyArray {
   
      static void Main(string[] args) {
         int []  n = new int[10]; /* n is an array of 10 integers */
         int i,j;

         /* initialize elements of array n */
         for ( i = 0; i < 10; i++ ) {
            n[ i ] = i + 100;
         }
         
         /* output each array element's value */
         for (j = 0; j < 10; j++ ) {
            Console.WriteLine("Element[{0}] = {1}", j, n[j]);
         }
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109

使用foreach迴圈

在前面的示例中,我們使用了 for 迴圈來訪問每個陣列元素。您還可以使用foreach語句來迭代陣列。

using System;

namespace ArrayApplication {

   class MyArray {
   
      static void Main(string[] args) {
         int []  n = new int[10]; /* n is an array of 10 integers */
         
         /* initialize elements of array n */
         for ( int i = 0; i < 10; i++ ) {
            n[i] = i + 100;
         }
         
         /* output each array element's value */
         foreach (int j in n ) {
            int i = j-100;
            Console.WriteLine("Element[{0}] = {1}", i, j);
            
         }
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Element[0] = 100
Element[1] = 101
Element[2] = 102
Element[3] = 103
Element[4] = 104
Element[5] = 105
Element[6] = 106
Element[7] = 107
Element[8] = 108
Element[9] = 109

C# 陣列

以下是一些與陣列相關的重要的概念,C# 程式設計師應該清楚:

序號 概念和描述
1 多維陣列

C# 支援多維陣列。多維陣列最簡單的形式是二維陣列。

2 交錯陣列

C# 支援多維陣列,即陣列的陣列。

3 將陣列傳遞給函式

您可以透過指定陣列的名稱(不帶索引)將指向陣列的指標傳遞給函式。

4 引數陣列

這用於將未知數量的引數傳遞給函式。

5 Array 類

定義在 System 名稱空間中,它是所有陣列的基類,並提供用於處理陣列的各種屬性和方法。

C# - 字串

在 C# 中,您可以使用字串作為字元陣列,但是,更常見的做法是使用string關鍵字來宣告字串變數。string 關鍵字是System.String類的別名。

建立字串物件

您可以使用以下方法之一建立字串物件:

  • 透過將字串文字分配給 String 變數

  • 透過使用 String 類建構函式

  • 透過使用字串連線運算子 (+)

  • 透過檢索返回字串的屬性或呼叫方法

  • 透過呼叫格式化方法將值或物件轉換為其字串表示形式

以下示例演示了這一點:

using System;

namespace StringApplication {

   class Program {
   
      static void Main(string[] args) {
         //from string literal and string concatenation
         string fname, lname;
         fname = "Rowan";
         lname = "Atkinson";
			
         char []letters= { 'H', 'e', 'l', 'l','o' };
         string [] sarray={ "Hello", "From", "Tutorials", "Point" };
			
         string fullname = fname + lname;
         Console.WriteLine("Full Name: {0}", fullname);
         
         //by using string constructor { 'H', 'e', 'l', 'l','o' };
         string greetings = new string(letters);
         Console.WriteLine("Greetings: {0}", greetings);
         
         //methods returning string { "Hello", "From", "Tutorials", "Point" };
         string message = String.Join(" ", sarray);
         Console.WriteLine("Message: {0}", message);
         
         //formatting method to convert a value
         DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
         string chat = String.Format("Message sent at {0:t} on {0:D}", waiting);
         Console.WriteLine("Message: {0}", chat);
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Full Name: RowanAtkinson
Greetings: Hello
Message: Hello From Tutorials Point
Message: Message sent at 5:58 PM on Wednesday, October 10, 2012

String 類的屬性

String 類具有以下兩個屬性:

序號 屬性和描述
1

Chars

獲取當前String物件中指定位置處的Char物件。

2

Length

獲取當前 String 物件中的字元數。

String 類的方法

String 類有許多方法可以幫助您處理字串物件。下表提供了一些最常用的方法:

序號 方法和描述
1

public static int Compare(string strA, string strB)

比較兩個指定的字串物件,並返回一個整數,指示它們在排序順序中的相對位置。

2

public static int Compare(string strA, string strB, bool ignoreCase )

比較兩個指定的字串物件,並返回一個整數,指示它們在排序順序中的相對位置。但是,如果布林引數為 true,則忽略大小寫。

3

public static string Concat(string str0, string str1)

連線兩個字串物件。

4

public static string Concat(string str0, string str1, string str2)

連線三個字串物件。

5

public static string Concat(string str0, string str1, string str2, string str3)

連線四個字串物件。

6

public bool Contains(string value)

返回一個值,指示指定的 String 物件是否出現在此字串中。

7

public static string Copy(string str)

建立一個新的 String 物件,其值與指定的字串相同。

8

public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)

將 String 物件指定位置處的指定數量的字元複製到 Unicode 字元陣列的指定位置。

9

public bool EndsWith(string value)

確定字串物件的末尾是否與指定的字串匹配。

10

public bool Equals(string value)

確定當前 String 物件和指定的 String 物件是否具有相同的值。

11

public static bool Equals(string a, string b)

確定兩個指定的 String 物件是否具有相同的值。

12

public static string Format(string format, Object arg0)

將指定字串中一個或多個格式項替換為指定物件的字串表示形式。

13

public int IndexOf(char value)

返回當前字串中指定 Unicode 字元第一次出現的從零開始的索引。

14

public int IndexOf(string value)

返回此例項中指定字串第一次出現的從零開始的索引。

15

public int IndexOf(char value, int startIndex)

返回此字串中指定 Unicode 字元第一次出現的從零開始的索引,從指定字元位置開始搜尋。

16

public int IndexOf(string value, int startIndex)

返回此例項中指定字串第一次出現的從零開始的索引,從指定字元位置開始搜尋。

17

public int IndexOfAny(char[] anyOf)

返回此例項中指定 Unicode 字元陣列中任何字元第一次出現的從零開始的索引。

18

public int IndexOfAny(char[] anyOf, int startIndex)

返回此例項中指定 Unicode 字元陣列中任何字元第一次出現的從零開始的索引,從指定字元位置開始搜尋。

19

public string Insert(int startIndex, string value)

返回一個新字串,其中指定的字串插入到當前字串物件的指定索引位置。

20

public static bool IsNullOrEmpty(string value)

指示指定的字串是否為 null 或空字串。

21

public static string Join(string separator, params string[] value)

連線字串陣列的所有元素,在每個元素之間使用指定的連線符。

22

public static string Join(string separator, string[] value, int startIndex, int count)

連線字串陣列的指定元素,在每個元素之間使用指定的連線符。

23

public int LastIndexOf(char value)

返回當前字串物件中指定 Unicode 字元最後一次出現的從零開始的索引位置。

24

public int LastIndexOf(string value)

返回當前字串物件中指定字串最後一次出現的從零開始的索引位置。

25

public string Remove(int startIndex)

刪除當前例項中從指定位置開始到最後一個位置的所有字元,並返回字串。

26

public string Remove(int startIndex, int count)

刪除當前字串中從指定位置開始的指定數量的字元,並返回字串。

27

public string Replace(char oldChar, char newChar)

將當前字串物件中指定 Unicode 字元的所有出現替換為指定的 Unicode 字元,並返回新字串。

28

public string Replace(string oldValue, string newValue)

將當前字串物件中指定字串的所有出現替換為指定的字串,並返回新字串。

29

public string[] Split(params char[] separator)

返回一個字串陣列,其中包含當前字串物件中的子字串,這些子字串由指定 Unicode 字元陣列的元素分隔。

30

public string[] Split(char[] separator, int count)

返回一個字串陣列,其中包含當前字串物件中的子字串,這些子字串由指定 Unicode 字元陣列的元素分隔。int 引數指定要返回的最大子字串數。

31

public bool StartsWith(string value)

確定此字串例項的開頭是否與指定的字串匹配。

32

public char[] ToCharArray()

返回一個 Unicode 字元陣列,其中包含當前字串物件中的所有字元。

33

public char[] ToCharArray(int startIndex, int length)

返回當前字串物件中從指定索引開始到指定長度的所有字元的 Unicode 字元陣列。

34

public string ToLower()

返回此字串轉換為小寫的副本。

35

public string ToUpper()

返回此字串轉換為大寫的副本。

36

public string Trim()

移除當前字串物件中所有前導和尾隨的空白字元。

您可以訪問 MSDN 庫以獲取方法和 String 類建構函式的完整列表。

示例

以下示例演示了上面提到的一些方法 -

比較字串

using System;

namespace StringApplication {

   class StringProg {
   
      static void Main(string[] args) {
         string str1 = "This is test";
         string str2 = "This is text";

         if (String.Compare(str1, str2) == 0) {
            Console.WriteLine(str1 + " and " + str2 +  " are equal.");
         } else {
            Console.WriteLine(str1 + " and " + str2 + " are not equal.");
         }
         Console.ReadKey() ;
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

This is test and This is text are not equal.

字串包含字串

using System;

namespace StringApplication {

   class StringProg {
   
      static void Main(string[] args) {
         string str = "This is test";
         
         if (str.Contains("test")) {
            Console.WriteLine("The sequence 'test' was found.");
         }
         Console.ReadKey() ;
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

The sequence 'test' was found.

獲取子字串

using System;

namespace StringApplication {

   class StringProg {
   
      static void Main(string[] args) {
         string str = "Last night I dreamt of San Pedro";
         Console.WriteLine(str);
         string substr = str.Substring(23);
         Console.WriteLine(substr);
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

San Pedro

連線字串

using System;

namespace StringApplication {

   class StringProg {
   
      static void Main(string[] args) {
         string[] starray = new string[]{"Down the way nights are dark",
            "And the sun shines daily on the mountain top",
            "I took a trip on a sailing ship",
            "And when I reached Jamaica",
            "I made a stop"};

         string str = String.Join("\n", starray);
         Console.WriteLine(str);
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Down the way nights are dark
And the sun shines daily on the mountain top
I took a trip on a sailing ship
And when I reached Jamaica
I made a stop

C# - 結構體

在 C# 中,結構體是一種值型別資料型別。它可以幫助您使單個變數儲存各種資料型別的相關資料。struct 關鍵字用於建立結構體。

結構體用於表示記錄。假設您想跟蹤圖書館中書籍的資訊。您可能希望跟蹤每本書的以下屬性 -

  • 標題
  • 作者
  • 主題
  • 圖書 ID

定義結構體

要定義結構體,必須使用 struct 語句。struct 語句定義了一種新的資料型別,其中包含程式的多個成員。

例如,以下是如何宣告 Book 結構體 -

struct Books {
   public string title;
   public string author;
   public string subject;
   public int book_id;
};  

以下程式展示了結構體的用法 -

using System;

struct Books {
   public string title;
   public string author;
   public string subject;
   public int book_id;
};  

public class testStructure {

   public static void Main(string[] args) {
      Books Book1;   /* Declare Book1 of type Book */
      Books Book2;   /* Declare Book2 of type Book */

      /* book 1 specification */
      Book1.title = "C Programming";
      Book1.author = "Nuha Ali"; 
      Book1.subject = "C Programming Tutorial";
      Book1.book_id = 6495407;

      /* book 2 specification */
      Book2.title = "Telecom Billing";
      Book2.author = "Zara Ali";
      Book2.subject =  "Telecom Billing Tutorial";
      Book2.book_id = 6495700;

      /* print Book1 info */
      Console.WriteLine( "Book 1 title : {0}", Book1.title);
      Console.WriteLine("Book 1 author : {0}", Book1.author);
      Console.WriteLine("Book 1 subject : {0}", Book1.subject);
      Console.WriteLine("Book 1 book_id :{0}", Book1.book_id);

      /* print Book2 info */
      Console.WriteLine("Book 2 title : {0}", Book2.title);
      Console.WriteLine("Book 2 author : {0}", Book2.author);
      Console.WriteLine("Book 2 subject : {0}", Book2.subject);
      Console.WriteLine("Book 2 book_id : {0}", Book2.book_id);       

      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

C# 結構體的特性

您已經使用了一個名為 Books 的簡單結構體。C# 中的結構體與傳統 C 或 C++ 中的結構體有很大不同。C# 結構體具有以下特性 -

  • 結構體可以包含方法、欄位、索引器、屬性、運算子方法和事件。

  • 結構體可以有定義的建構函式,但不能有解構函式。但是,您不能為結構體定義預設建構函式。預設建構函式會自動定義,並且不能更改。

  • 與類不同,結構體不能繼承其他結構體或類。

  • 結構體不能用作其他結構體或類的基類。

  • 結構體可以實現一個或多個介面。

  • 結構體成員不能指定為抽象的、虛擬的或受保護的。

  • 當您使用 New 運算子建立結構體物件時,它將被建立,並呼叫相應的建構函式。與類不同,結構體可以在不使用 New 運算子的情況下例項化。

  • 如果不使用 New 運算子,則欄位將保持未賦值,並且在所有欄位初始化之前,該物件都無法使用。

類與結構體

類和結構體具有以下基本區別 -

  • 類是引用型別,結構體是值型別
  • 結構體不支援繼承
  • 結構體不能有預設建構函式

根據以上討論,讓我們重寫前面的示例 -

using System;

struct Books {
   private string title;
   private string author;
   private string subject;
   private int book_id;
   
   public void getValues(string t, string a, string s, int id) {
      title = t;
      author = a;
      subject = s;
      book_id = id;
   }
   
   public void display() {
      Console.WriteLine("Title : {0}", title);
      Console.WriteLine("Author : {0}", author);
      Console.WriteLine("Subject : {0}", subject);
      Console.WriteLine("Book_id :{0}", book_id);
   }
};  

public class testStructure {

   public static void Main(string[] args) {
      Books Book1 = new Books();   /* Declare Book1 of type Book */
      Books Book2 = new Books();   /* Declare Book2 of type Book */

      /* book 1 specification */
      Book1.getValues("C Programming",
      "Nuha Ali", "C Programming Tutorial",6495407);

      /* book 2 specification */
      Book2.getValues("Telecom Billing",
      "Zara Ali", "Telecom Billing Tutorial", 6495700);

      /* print Book1 info */
      Book1.display();

      /* print Book2 info */
      Book2.display(); 

      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Title : C Programming
Author : Nuha Ali
Subject : C Programming Tutorial
Book_id : 6495407
Title : Telecom Billing
Author : Zara Ali
Subject : Telecom Billing Tutorial
Book_id : 6495700

C# - 列舉

列舉是一組命名的整型常量。使用 enum 關鍵字宣告列舉型別。

C# 列舉是值資料型別。換句話說,列舉包含它自己的值,不能繼承或傳遞繼承。

宣告 enum 變數

宣告列舉的一般語法如下 -

enum <enum_name> {
   enumeration list 
};

其中,

  • enum_name 指定列舉型別名稱。

  • enumeration list 是一個用逗號分隔的識別符號列表。

列舉列表中的每個符號都代表一個整數值,比它前面的符號大 1。預設情況下,第一個列舉符號的值為 0。例如 -

enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

示例

以下示例演示了 enum 變數的用法 -

using System;

namespace EnumApplication {

   class EnumProgram {
      enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

      static void Main(string[] args) {
         int WeekdayStart = (int)Days.Mon;
         int WeekdayEnd = (int)Days.Fri;
         Console.WriteLine("Monday: {0}", WeekdayStart);
         Console.WriteLine("Friday: {0}", WeekdayEnd);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Monday: 1
Friday: 5

C# - 類

當您定義一個類時,您定義了一個數據型別的藍圖。這實際上並沒有定義任何資料,但它確實定義了類名所代表的含義。也就是說,類的物件由什麼組成以及可以對該物件執行什麼操作。物件是類的例項。構成類的這些方法和變數稱為類的成員。

定義類

類定義以關鍵字 class 後跟類名開頭;以及用一對花括號括起來的類體。以下是類定義的一般形式 -

<access specifier> class  class_name {
   // member variables
   <access specifier> <data type> variable1;
   <access specifier> <data type> variable2;
   ...
   <access specifier> <data type> variableN;
   // member methods
   <access specifier> <return type> method1(parameter_list) {
      // method body
   }
   <access specifier> <return type> method2(parameter_list) {
      // method body
   }
   ...
   <access specifier> <return type> methodN(parameter_list) {
      // method body
   }
}

注意 -

  • 訪問修飾符指定成員以及類本身的訪問規則。如果未提及,則類型別的預設訪問修飾符為 internal。成員的預設訪問許可權為 private

  • 資料型別指定變數的型別,返回值型別指定方法返回的資料的型別(如果有)。

  • 要訪問類成員,可以使用點 (.) 運算子。

  • 點運算子將物件的名稱與成員的名稱連結起來。

以下示例說明了迄今為止討論的概念 -

using System;

namespace BoxApplication {

    class Box {
       public double length;   // Length of a box
       public double breadth;  // Breadth of a box
       public double height;   // Height of a box
    }
    
    class Boxtester {

        static void Main(string[] args) {
            Box Box1 = new Box();   // Declare Box1 of type Box
            Box Box2 = new Box();   // Declare Box2 of type Box
            double volume = 0.0;    // Store the volume of a box here

            // box 1 specification
            Box1.height = 5.0;
            Box1.length = 6.0;
            Box1.breadth = 7.0;

            // box 2 specification
            Box2.height = 10.0;
            Box2.length = 12.0;
            Box2.breadth = 13.0;
           
            // volume of box 1
            volume = Box1.height * Box1.length * Box1.breadth;
            Console.WriteLine("Volume of Box1 : {0}",  volume);

            // volume of box 2
            volume = Box2.height * Box2.length * Box2.breadth;
            Console.WriteLine("Volume of Box2 : {0}", volume);
            Console.ReadKey();
        }
    }
}

當以上程式碼編譯並執行時,會產生以下結果:

Volume of Box1 : 210
Volume of Box2 : 1560

成員函式和封裝

類的成員函式是其定義或原型在類定義中類似於任何其他變數的函式。它對它是成員的類的任何物件進行操作,並可以訪問該物件類的所有成員。

成員變數是物件(從設計角度)的屬性,並且它們被設為私有以實現封裝。這些變數只能使用公共成員函式訪問。

讓我們將上述概念用於設定和獲取類中不同類成員的值 -

using System;

namespace BoxApplication {

   class Box {
      private double length;   // Length of a box
      private double breadth;  // Breadth of a box
      private double height;   // Height of a box
      
      public void setLength( double len ) {
         length = len;
      }
      
      public void setBreadth( double bre ) {
         breadth = bre;
      }
      
      public void setHeight( double hei ) {
         height = hei;
      }
      public double getVolume() {
         return length * breadth * height;
      }
   }
   
   class Boxtester {
   
      static void Main(string[] args) {
         Box Box1 = new Box();   // Declare Box1 of type Box
         Box Box2 = new Box();
         double volume;
         
         // Declare Box2 of type Box
         // box 1 specification
         Box1.setLength(6.0);
         Box1.setBreadth(7.0);
         Box1.setHeight(5.0);
         
         // box 2 specification
         Box2.setLength(12.0);
         Box2.setBreadth(13.0);
         Box2.setHeight(10.0);
         
         // volume of box 1
         volume = Box1.getVolume();
         Console.WriteLine("Volume of Box1 : {0}" ,volume);
         
         // volume of box 2
         volume = Box2.getVolume();
         Console.WriteLine("Volume of Box2 : {0}", volume);
         
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Volume of Box1 : 210
Volume of Box2 : 1560

C# 建構函式

類的建構函式是類的特殊成員函式,每當我們建立該類的新的物件時都會執行它。

建構函式的名稱與類的名稱完全相同,並且它沒有任何返回值型別。以下示例解釋了建構函式的概念 -

using System;

namespace LineApplication {

   class Line {
      private double length;   // Length of a line
      
      public Line() {
         Console.WriteLine("Object is being created");
      }

      public void setLength( double len ) {
         length = len;
      }
      
      public double getLength() {
         return length;
      }

      static void Main(string[] args) {
         Line line = new Line();    
         
         // set line length
         line.setLength(6.0);
         Console.WriteLine("Length of line : {0}", line.getLength());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Object is being created
Length of line : 6

預設建構函式沒有任何引數,但如果需要,建構函式可以有引數。此類建構函式稱為引數化建構函式。此技術可以幫助您在物件建立時為其分配初始值,如以下示例所示 -

using System;

namespace LineApplication {

   class Line {
      private double length;   // Length of a line
      
      public Line(double len) {  //Parameterized constructor
         Console.WriteLine("Object is being created, length = {0}", len);
         length = len;
      }

      public void setLength( double len ) {
         length = len;
      }
      
      public double getLength() {
         return length;
      }

      static void Main(string[] args) {
         Line line = new Line(10.0);
         Console.WriteLine("Length of line : {0}", line.getLength()); 
         
         // set line length
         line.setLength(6.0);
         Console.WriteLine("Length of line : {0}", line.getLength()); 
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Object is being created, length = 10
Length of line : 10
Length of line : 6

C# 解構函式

解構函式是類的特殊成員函式,每當其類的物件超出範圍時都會執行。解構函式的名稱與類的名稱完全相同,前面帶有波浪號 (~),並且它既不能返回值,也不能接受任何引數。

解構函式對於在退出程式之前釋放記憶體資源非常有用。解構函式不能被繼承或過載。

以下示例解釋了解構函式的概念 -

using System;

namespace LineApplication {

   class Line {
      private double length;   // Length of a line
      
      public Line() {   // constructor
         Console.WriteLine("Object is being created");
      }
      
      ~Line() {   //destructor
         Console.WriteLine("Object is being deleted");
      }

      public void setLength( double len ) {
         length = len;
      }

      public double getLength() {
         return length;
      }

      static void Main(string[] args) {
         Line line = new Line();

         // set line length
         line.setLength(6.0);
         Console.WriteLine("Length of line : {0}", line.getLength());           
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Object is being created
Length of line : 6
Object is being deleted

C# 類中的靜態成員

我們可以使用 static 關鍵字將類成員定義為靜態的。當我們將類的成員宣告為靜態時,這意味著無論建立了多少個類的物件,靜態成員都只有一個副本。

關鍵字 static 表示類只有一個成員例項。靜態變數用於定義常量,因為可以透過呼叫類來檢索它們的值,而無需建立它的例項。靜態變數可以在成員函式或類定義之外初始化。您也可以在類定義內部初始化靜態變數。

以下示例演示了 靜態變數的用法 -

using System;

namespace StaticVarApplication {

   class StaticVar {
      public static int num;
      
      public void count() {
         num++;
      }
      
      public int getNum() {
         return num;
      }
   }
   
   class StaticTester {
   
      static void Main(string[] args) {
         StaticVar s1 = new StaticVar();
         StaticVar s2 = new StaticVar();
         s1.count();
         s1.count();
         s1.count();
         s2.count();
         s2.count();
         s2.count();
         Console.WriteLine("Variable num for s1: {0}", s1.getNum());
         Console.WriteLine("Variable num for s2: {0}", s2.getNum());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Variable num for s1: 6
Variable num for s2: 6

您還可以將成員函式宣告為靜態的。此類函式只能訪問靜態變數。靜態函式甚至在建立物件之前就存在。以下示例演示了靜態函式的用法 -

using System;

namespace StaticVarApplication {

   class StaticVar {
      public static int num;
      
      public void count() {
         num++;
      }
      
      public static int getNum() {
         return num;
      }
   }
   
   class StaticTester {
   
      static void Main(string[] args) {
         StaticVar s = new StaticVar();
         s.count();
         s.count();
         s.count();
         Console.WriteLine("Variable num: {0}", StaticVar.getNum());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Variable num: 3

C# - 繼承

面向物件程式設計中最重要概念之一是繼承。繼承允許我們根據另一個類定義類,這使得建立和維護應用程式變得更容易。這也提供了重用程式碼功能的機會,並加快了實施時間。

在建立類時,程式設計師可以指定新類應該繼承現有類的成員,而不是編寫全新的資料成員和成員函式。此現有類稱為基類,新類稱為派生類

繼承的概念實現了IS-A關係。例如,哺乳動物IS A動物,狗IS-A哺乳動物,因此狗IS-A動物,依此類推。

基類和派生類

一個類可以從多個類或介面派生,這意味著它可以繼承多個基類或介面的資料和函式。

C# 中用於建立派生類的語法如下 -

<acess-specifier> class <base_class> {
   ...
}

class <derived_class> : <base_class> {
   ...
}

考慮一個基類 Shape 及其派生類 Rectangle -

using System;

namespace InheritanceApplication {
   
   class Shape {
      
      public void setWidth(int w) {
         width = w;
      }
      
      public void setHeight(int h) {
         height = h;
      }
      protected int width;
      protected int height;
   }

   // Derived class
   class Rectangle: Shape {
      
      public int getArea() { 
         return (width * height); 
      }
   }
   
   class RectangleTester {
   
      static void Main(string[] args) {
         Rectangle Rect = new Rectangle();

         Rect.setWidth(5);
         Rect.setHeight(7);

         // Print the area of the object.
         Console.WriteLine("Total area: {0}",  Rect.getArea());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Total area: 35

初始化基類

派生類繼承基類的成員變數和成員方法。因此,在建立子類之前,應先建立超類物件。您可以在成員初始化列表中提供超類初始化的指令。

以下程式演示了這一點 -

using System;

namespace RectangleApplication {

   class Rectangle {
      //member variables
      protected double length;
      protected double width;
      
      public Rectangle(double l, double w) {
         length = l;
         width = w;
      }
      
      public double GetArea() {
         return length * width;
      }
      
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle  
   
   class Tabletop : Rectangle {
      private double cost;
      public Tabletop(double l, double w) : base(l, w) { }
      
      public double GetCost() {
         double cost;
         cost = GetArea() * 70;
         return cost;
      }
      
      public void Display() {
         base.Display();
         Console.WriteLine("Cost: {0}", GetCost());
      }
   }
   
   class ExecuteRectangle {
      
      static void Main(string[] args) {
         Tabletop t = new Tabletop(4.5, 7.5);
         t.Display();
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Length: 4.5
Width: 7.5
Area: 33.75
Cost: 2362.5

C# 中的多重繼承

C# 不支援多重繼承。但是,您可以使用介面來實現多重繼承。以下程式演示了這一點 -

using System;

namespace InheritanceApplication {
   
   class Shape {
      
      public void setWidth(int w) {
         width = w;
      }
      
      public void setHeight(int h) {
         height = h;
      }
      protected int width;
      protected int height;
   }

   // Base class PaintCost
   public interface PaintCost {
      int getCost(int area);
   }
   
   // Derived class
   class Rectangle : Shape, PaintCost {
      
      public int getArea() {
         return (width * height);
      }
      
      public int getCost(int area) {
         return area * 70;
      }
   }
   
   class RectangleTester {
      
      static void Main(string[] args) {
         Rectangle Rect = new Rectangle();
         int area;
         Rect.setWidth(5);
         Rect.setHeight(7);
         area = Rect.getArea();
         
         // Print the area of the object.
         Console.WriteLine("Total area: {0}",  Rect.getArea());
         Console.WriteLine("Total paint cost: ${0}" , Rect.getCost(area));
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Total area: 35
Total paint cost: $2450

C# - 多型

術語多型性表示具有多種形式。在面向物件程式設計正規化中,多型性通常表示為“一個介面,多個函式”。

多型性可以是靜態的或動態的。在靜態多型性中,對函式的響應是在編譯時確定的。在動態多型性中,它是在執行時確定的。

靜態多型性

在編譯期間將函式與物件連結的機制稱為早期繫結。它也稱為靜態繫結。C# 提供了兩種實現靜態多型性的技術。它們是 -

  • 函式過載
  • 運算子過載

我們將在下一章中討論運算子過載。

函式過載

您可以在同一作用域中擁有多個相同函式名稱的定義。函式的定義必須透過引數列表中引數的型別和/或數量彼此不同。您不能過載僅返回值型別不同的函式宣告。

以下示例展示了使用函式 print() 列印不同資料型別 -

using System;

namespace PolymorphismApplication {

   class Printdata {
   
      void print(int i) {
         Console.WriteLine("Printing int: {0}", i );
      }

      void print(double f) {
         Console.WriteLine("Printing float: {0}" , f);
      }

      void print(string s) {
         Console.WriteLine("Printing string: {0}", s);
      }
      
      static void Main(string[] args) {
         Printdata p = new Printdata();
         
         // Call print to print integer
         p.print(5);
         
         // Call print to print float
         p.print(500.263);
         
         // Call print to print string
         p.print("Hello C++");
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Printing int: 5
Printing float: 500.263
Printing string: Hello C++

動態多型性

C# 允許您建立抽象類,這些類用於提供介面的部分類實現。當派生類從它繼承時,實現就完成了。抽象類包含抽象方法,這些方法由派生類實現。派生類具有更專業的函式。

以下是關於抽象類的規則 -

  • 您不能建立抽象類的例項

  • 您不能在抽象類之外宣告抽象方法

  • 當類被宣告為 sealed 時,它不能被繼承,抽象類不能被宣告為 sealed。

以下程式演示了一個抽象類 -

using System;

namespace PolymorphismApplication {

   abstract class Shape {
      public abstract int area();
   }
   
   class Rectangle:  Shape {
      private int length;
      private int width;
      
      public Rectangle( int a = 0, int b = 0) {
         length = a;
         width = b;
      }
      
      public override int area () { 
         Console.WriteLine("Rectangle class area :");
         return (width * length); 
      }
   }

   class RectangleTester {
      
      static void Main(string[] args) {
         Rectangle r = new Rectangle(10, 7);
         double a = r.area();
         Console.WriteLine("Area: {0}",a);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Rectangle class area :
Area: 70

當您在類中定義了一個要在繼承類中實現的函式時,您使用虛擬函式。虛擬函式可以在不同的繼承類中以不同的方式實現,對這些函式的呼叫將在執行時決定。

動態多型性由抽象類虛擬函式實現。

以下程式演示了這一點 -

using System;

namespace PolymorphismApplication {

   class Shape {
      protected int width, height;
      
      public Shape( int a = 0, int b = 0) {
         width = a;
         height = b;
      }
      
      public virtual int area() {
         Console.WriteLine("Parent class area :");
         return 0;
      }
   }
   
   class Rectangle: Shape {
      public Rectangle( int a = 0, int b = 0): base(a, b) {

      }
      
      public override int area () {
         Console.WriteLine("Rectangle class area :");
         return (width * height); 
      }
   }
   
   class Triangle: Shape {
      public Triangle(int a = 0, int b = 0): base(a, b) {
      
      }
      
      public override int area() {
         Console.WriteLine("Triangle class area :");
         return (width * height / 2); 
      }
   }
   
   class Caller {
      public void CallArea(Shape sh) {
         int a;
         a = sh.area();
         Console.WriteLine("Area: {0}", a);
      }
   }  
   
   class Tester {

      static void Main(string[] args) {
         Caller c = new Caller();
         Rectangle r = new Rectangle(10, 7);
         Triangle t = new Triangle(10, 5);
         c.CallArea(r);
         c.CallArea(t);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Rectangle class area:
Area: 70
Triangle class area:
Area: 25

C# - 運算子過載

您可以重新定義或過載 C# 中提供的大多數內建運算子。因此,程式設計師也可以將運算子與使用者定義型別一起使用。過載的運算子是具有特殊名稱的函式,關鍵字 operator 後跟要定義的運算子的符號。與任何其他函式類似,過載的運算子具有返回型別和引數列表。

例如,請檢視以下函式 -

public static Box operator+ (Box b, Box c) {
   Box box = new Box();
   box.length = b.length + c.length;
   box.breadth = b.breadth + c.breadth;
   box.height = b.height + c.height;
   return box;
}

上述函式為使用者定義的類 Box 實現加法運算子 (+) 。它新增兩個 Box 物件的屬性並返回結果 Box 物件。

實現運算子過載

以下程式展示了完整的實現 -

using System;

namespace OperatorOvlApplication {

   class Box {
      private double length;   // Length of a box
      private double breadth;  // Breadth of a box
      private double height;   // Height of a box

      public double getVolume() {
         return length * breadth * height;
      }
      
      public void setLength( double len ) {
         length = len;
      }

      public void setBreadth( double bre ) {
         breadth = bre;
      }

      public void setHeight( double hei ) {
         height = hei;
      }
      
      // Overload + operator to add two Box objects.
      public static Box operator+ (Box b, Box c) {
         Box box = new Box();
         box.length = b.length + c.length;
         box.breadth = b.breadth + c.breadth;
         box.height = b.height + c.height;
         return box;
      }
   }

   class Tester {
   
      static void Main(string[] args) {
         Box Box1 = new Box();   // Declare Box1 of type Box
         Box Box2 = new Box();   // Declare Box2 of type Box
         Box Box3 = new Box();   // Declare Box3 of type Box
         double volume = 0.0;    // Store the volume of a box here

         // box 1 specification
         Box1.setLength(6.0);
         Box1.setBreadth(7.0);
         Box1.setHeight(5.0);

         // box 2 specification
         Box2.setLength(12.0);
         Box2.setBreadth(13.0);
         Box2.setHeight(10.0);

         // volume of box 1
         volume = Box1.getVolume();
         Console.WriteLine("Volume of Box1 : {0}", volume);

         // volume of box 2
         volume = Box2.getVolume();
         Console.WriteLine("Volume of Box2 : {0}", volume);

         // Add two object as follows:
         Box3 = Box1 + Box2;

         // volume of box 3
         volume = Box3.getVolume();
         Console.WriteLine("Volume of Box3 : {0}", volume);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400

可過載和不可過載運算子

下表描述了 C# 中運算子的可過載性 -

序號 運算子 & 描述
1

+, -, !, ~, ++, --

這些一元運算子接受一個運算元,並且可以過載。

2

+, -, *, /, %

這些二元運算子接受一個運算元,並且可以過載。

3

==, !=, <, >, <=, >=

比較運算子可以過載。

4

&&, ||

條件邏輯運算子不能直接過載。

5

+=, -=, *=, /=, %=

賦值運算子不能過載。

6

=, ., ?:, ->, new, is, sizeof, typeof

這些運算子不能過載。

示例

根據以上討論,讓我們擴充套件前面的示例,並重載更多運算子 -

using System;

namespace OperatorOvlApplication {

   class Box {
      private double length;    // Length of a box
      private double breadth;   // Breadth of a box
      private double height;    // Height of a box
      
      public double getVolume() {
         return length * breadth * height;
      }
      
      public void setLength( double len ) {
         length = len;
      }
      
      public void setBreadth( double bre ) {
         breadth = bre;
      }
      
      public void setHeight( double hei ) {
         height = hei;
      }
      
      // Overload + operator to add two Box objects.
      public static Box operator+ (Box b, Box c) {
         Box box = new Box();
         box.length = b.length + c.length;
         box.breadth = b.breadth + c.breadth;
         box.height = b.height + c.height;
         return box;
      }

      public static bool operator == (Box lhs, Box rhs) {
         bool status = false;
         if (lhs.length == rhs.length && lhs.height == rhs.height && lhs.breadth == rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public static bool operator !=(Box lhs, Box rhs) {
         bool status = false;
         
         if (lhs.length != rhs.length || lhs.height != rhs.height || lhs.breadth != rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public static bool operator <(Box lhs, Box rhs) {
         bool status = false;
         
         if (lhs.length < rhs.length && lhs.height < rhs.height && lhs.breadth < rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public static bool operator >(Box lhs, Box rhs) {
         bool status = false;
         
         if (lhs.length > rhs.length && lhs.height > rhs.height && lhs.breadth > rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public static bool operator <=(Box lhs, Box rhs) {
         bool status = false;
         
         if (lhs.length <= rhs.length && lhs.height <= rhs.height && lhs.breadth <= rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public static bool operator >=(Box lhs, Box rhs) {
         bool status = false;
         
         if (lhs.length >= rhs.length && lhs.height >= rhs.height && lhs.breadth >= rhs.breadth) {
            status = true;
         }
         return status;
      }
      
      public override string ToString() {
         return String.Format("({0}, {1}, {2})", length, breadth, height);
      }
   }
   
   class Tester {
   
      static void Main(string[] args) {
         Box Box1 = new Box();   // Declare Box1 of type Box
         Box Box2 = new Box();   // Declare Box2 of type Box
         Box Box3 = new Box();   // Declare Box3 of type Box
         Box Box4 = new Box();
         double volume = 0.0;    // Store the volume of a box here
         
         // box 1 specification
         Box1.setLength(6.0);
         Box1.setBreadth(7.0);
         Box1.setHeight(5.0);
         
         // box 2 specification
         Box2.setLength(12.0);
         Box2.setBreadth(13.0);
         Box2.setHeight(10.0);
         
         //displaying the Boxes using the overloaded ToString():
         Console.WriteLine("Box 1: {0}", Box1.ToString());
         Console.WriteLine("Box 2: {0}", Box2.ToString());
         
         // volume of box 1
         volume = Box1.getVolume();
         Console.WriteLine("Volume of Box1 : {0}", volume);
         
         // volume of box 2
         volume = Box2.getVolume();
         Console.WriteLine("Volume of Box2 : {0}", volume);
         
         // Add two object as follows:
         Box3 = Box1 + Box2;
         Console.WriteLine("Box 3: {0}", Box3.ToString());
         
         // volume of box 3
         volume = Box3.getVolume();
         Console.WriteLine("Volume of Box3 : {0}", volume);
         
         //comparing the boxes
         if (Box1 > Box2)
            Console.WriteLine("Box1 is greater than Box2");
         else
            Console.WriteLine("Box1 is  greater than Box2");
         
         if (Box1 < Box2)
            Console.WriteLine("Box1 is less than Box2");
         else
            Console.WriteLine("Box1 is not less than Box2");
         
         if (Box1 >= Box2)
            Console.WriteLine("Box1 is greater or equal to Box2");
         else
            Console.WriteLine("Box1 is not greater or equal to Box2");
         
         if (Box1 <= Box2)
            Console.WriteLine("Box1 is less or equal to Box2");
         else
            Console.WriteLine("Box1 is not less or equal to Box2");
         
         if (Box1 != Box2)
            Console.WriteLine("Box1 is not equal to Box2");
         else
            Console.WriteLine("Box1 is not greater or equal to Box2");
         Box4 = Box3;
         
         if (Box3 == Box4)
            Console.WriteLine("Box3 is equal to Box4");
         else
            Console.WriteLine("Box3 is not equal to Box4");

         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Box 1: (6, 7, 5)
Box 2: (12, 13, 10)
Volume of Box1 : 210
Volume of Box2 : 1560
Box 3: (18, 20, 15)
Volume of Box3 : 5400
Box1 is not greater than Box2
Box1 is less than Box2
Box1 is not greater or equal to Box2
Box1 is less or equal to Box2
Box1 is not equal to Box2
Box3 is equal to Box4

C# - 介面

介面被定義為所有繼承該介面的類都應該遵循的語法契約。介面定義了語法契約的“是什麼”部分,而派生類定義了語法契約的“如何”部分。

介面定義屬性、方法和事件,這些是介面的成員。介面僅包含成員的宣告。派生類負責定義這些成員。它通常有助於提供派生類將遵循的標準結構。

抽象類在某種程度上具有相同的目的,但是,當基類只需要宣告少量方法,而派生類實現功能時,它們主要用於這種情況。

宣告介面

介面使用 interface 關鍵字宣告。它類似於類宣告。介面語句預設情況下是公開的。以下是一個介面宣告的示例 -

public interface ITransactions {
   // interface members
   void showTransaction();
   double getAmount();
}

示例

以下示例演示了上述介面的實現 -

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System;

namespace InterfaceApplication {
   
   public interface ITransactions {
      // interface members
      void showTransaction();
      double getAmount();
   }
   
   public class Transaction : ITransactions {
      private string tCode;
      private string date;
      private double amount;
      
      public Transaction() {
         tCode = " ";
         date = " ";
         amount = 0.0;
      }
      
      public Transaction(string c, string d, double a) {
         tCode = c;
         date = d;
         amount = a;
      }
      
      public double getAmount() {
         return amount;
      }
      
      public void showTransaction() {
         Console.WriteLine("Transaction: {0}", tCode);
         Console.WriteLine("Date: {0}", date);
         Console.WriteLine("Amount: {0}", getAmount());
      }
   }
   
   class Tester {
     
      static void Main(string[] args) {
         Transaction t1 = new Transaction("001", "8/10/2012", 78900.00);
         Transaction t2 = new Transaction("002", "9/10/2012", 451900.00);
         t1.showTransaction();
         t2.showTransaction();
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Transaction: 001
Date: 8/10/2012
Amount: 78900
Transaction: 002
Date: 9/10/2012
Amount: 451900

C# - 名稱空間

名稱空間旨在提供一種方法來使一組名稱與另一組名稱分開。在一個名稱空間中宣告的類名不會與另一個名稱空間中宣告的相同類名衝突。

定義名稱空間

名稱空間定義以關鍵字namespace開頭,後跟名稱空間名稱,如下所示 -

namespace namespace_name {
   // code declarations
}

要呼叫函式或變數的啟用名稱空間的版本,請在前面加上名稱空間名稱,如下所示 -

namespace_name.item_name;

以下程式演示了名稱空間的使用 -

using System;

namespace first_space {

   class namespace_cl {
   
      public void func() {
         Console.WriteLine("Inside first_space");
      }
   }
}

namespace second_space {

   class namespace_cl {
   
      public void func() {
         Console.WriteLine("Inside second_space");
      }
   }
}

class TestClass {

   static void Main(string[] args) {
      first_space.namespace_cl fc = new first_space.namespace_cl();
      second_space.namespace_cl sc = new second_space.namespace_cl();
      fc.func();
      sc.func();
      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Inside first_space
Inside second_space

using關鍵字

using關鍵字表示程式正在使用給定名稱空間中的名稱。例如,我們在程式中使用了System名稱空間。類 Console 在那裡定義。我們只需編寫 -

Console.WriteLine ("Hello there");

我們也可以寫出完全限定的名稱,如下所示 -

System.Console.WriteLine("Hello there");

您還可以使用using名稱空間指令避免在名稱空間前加上字首。此指令告訴編譯器後續程式碼正在使用指定名稱空間中的名稱。因此,名稱空間對以下程式碼隱含有效 -

讓我們使用 using 指令重寫前面的示例 -

using System;
using first_space;
using second_space;

namespace first_space {

   class abc {
   
      public void func() {
         Console.WriteLine("Inside first_space");
      }
   }
}

namespace second_space {

   class efg {
   
      public void func() {
         Console.WriteLine("Inside second_space");
      }
   }
}   

class TestClass {

   static void Main(string[] args) {
      abc fc = new abc();
      efg sc = new efg();
      fc.func();
      sc.func();
      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Inside first_space
Inside second_space

巢狀名稱空間

您可以按如下方式在一個名稱空間內定義另一個名稱空間 -

namespace namespace_name1 {
   
   // code declarations
   namespace namespace_name2 {
      // code declarations
   }
}

您可以使用點 (.) 運算子訪問巢狀名稱空間的成員,如下所示 -

using System;
using first_space;
using first_space.second_space;

namespace first_space {

   class abc {
   
      public void func() {
         Console.WriteLine("Inside first_space");
      }
   }
   
   namespace second_space {
   
      class efg {
      
         public void func() {
            Console.WriteLine("Inside second_space");
         }
      }
   }   
}
 
class TestClass {

   static void Main(string[] args) {
      abc fc = new abc();
      efg sc = new efg();
      fc.func();
      sc.func();
      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Inside first_space
Inside second_space

C# - 預處理器指令

預處理器指令指示編譯器在實際編譯開始之前預處理資訊。

所有預處理器指令都以 # 開頭,並且在一行上預處理器指令之前只能出現空白字元。預處理器指令不是語句,因此它們不會以分號 (;) 結尾。

C# 編譯器沒有單獨的預處理器;但是,這些指令被處理得好像有一個一樣。在 C# 中,預處理器指令用於幫助進行條件編譯。與 C 和 C++ 指令不同,它們不用於建立宏。預處理器指令必須是行上唯一的指令。

C# 中的預處理器指令

下表列出了 C# 中可用的預處理器指令 -

序號 預處理器指令 & 描述
1

#define

它定義一個字元序列,稱為符號。

2

#undef

它允許您取消定義符號。

3

#if

它允許測試符號或符號以檢視它們是否評估為真。

4

#else

它允許與 #if 一起建立複合條件指令。

5

#elif

它允許建立複合條件指令。

6

#endif

指定條件指令的結束。

7

#line

它允許您修改編譯器的行號和(可選)錯誤和警告的輸出檔名。

8

#error

它允許從程式碼中的特定位置生成錯誤。

9

#warning

它允許從程式碼中的特定位置生成一級警告。

10

#region

它允許您指定程式碼塊,當使用 Visual Studio 程式碼編輯器的概述功能時,您可以展開或摺疊該程式碼塊。

11

#endregion

它標記 #region 塊的結束。

#define 預處理器

#define 預處理器指令建立符號常量。

#define 允許您定義一個符號,以便透過使用該符號作為傳遞給 #if 指令的表示式,該表示式評估為真。其語法如下 -

#define symbol

以下程式說明了這一點:

#define PI 
using System;

namespace PreprocessorDAppl {

   class Program {
   
      static void Main(string[] args) {
         #if (PI)
            Console.WriteLine("PI is defined");
         #else
            Console.WriteLine("PI is not defined");
         #endif
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

PI is defined

條件指令

您可以使用 #if 指令建立條件指令。條件指令對於測試符號或符號以檢查它們是否評估為真很有用。如果它們確實評估為真,則編譯器會評估 #if 和下一個指令之間的所有程式碼。

條件指令的語法為 -

#if symbol [operator symbol]...

其中,symbol 是要測試的符號的名稱。您還可以使用 true 和 false 或在符號前加上否定運算子。

operator symbol 是用於評估符號的運算子。運算子可以是以下任何一個 -

  • == (相等)
  • != (不相等)
  • && (與)
  • || (或)

您還可以使用括號對符號和運算子進行分組。條件指令用於為除錯版本編譯程式碼或在為特定配置編譯時使用。以#if指令開頭的條件指令必須顯式地以#endif指令結束。

以下程式演示了條件指令的使用 -

#define DEBUG
#define VC_V10
using System;

public class TestClass {

   public static void Main() {
      #if (DEBUG && !VC_V10)
         Console.WriteLine("DEBUG is defined");
      #elif (!DEBUG && VC_V10)
         Console.WriteLine("VC_V10 is defined");
      #elif (DEBUG && VC_V10)
         Console.WriteLine("DEBUG and VC_V10 are defined");
      #else
         Console.WriteLine("DEBUG and VC_V10 are not defined");
      #endif
      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

DEBUG and VC_V10 are defined

C# - 正則表示式

正則表示式是一種可以與輸入文字匹配的模式。.Net 框架提供了一個正則表示式引擎,允許進行這種匹配。模式由一個或多個字元文字、運算子或結構組成。

定義正則表示式的結構

有各種類別的字元、運算子和結構可以讓您定義正則表示式。點選以下連結查詢這些結構。

Regex 類

Regex 類用於表示正則表示式。它具有以下常用方法 -

序號 方法和描述
1

public bool IsMatch(string input)

指示 Regex 建構函式中指定的正則表示式是否在指定的輸入字串中找到匹配項。

2

public bool IsMatch(string input, int startat)

指示 Regex 建構函式中指定的正則表示式是否在指定的輸入字串中找到匹配項,從字串中的指定起始位置開始。

3

public static bool IsMatch(string input, string pattern)

指示指定的正則表示式是否在指定的輸入字串中找到匹配項。

4

public MatchCollection Matches(string input)

在指定的輸入字串中搜索正則表示式的所有出現。

5

public string Replace(string input, string replacement)

在指定的輸入字串中,將與正則表示式模式匹配的所有字串替換為指定的替換字串。

6

public string[] Split(string input)

根據 Regex 建構函式中指定的正則表示式模式定義的位置,將輸入字串拆分為子字串陣列。

有關方法和屬性的完整列表,請閱讀有關 C# 的 Microsoft 文件。

示例 1

以下示例匹配以“S”開頭的單詞 -

using System;
using System.Text.RegularExpressions;

namespace RegExApplication {

   class Program {
   
      private static void showMatch(string text, string expr) {
         Console.WriteLine("The Expression: " + expr);
         MatchCollection mc = Regex.Matches(text, expr);
         foreach (Match m in mc) {
            Console.WriteLine(m);
         }
      }
      
      static void Main(string[] args) {
         string str = "A Thousand Splendid Suns";
         
         Console.WriteLine("Matching words that start with 'S': ");
         showMatch(str, @"\bS\S*");
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Matching words that start with 'S':
The Expression: \bS\S*
Splendid
Suns

示例 2

以下示例匹配以“m”開頭並以“e”結尾的單詞 -

using System;
using System.Text.RegularExpressions;

namespace RegExApplication {

   class Program {
      private static void showMatch(string text, string expr) {
         Console.WriteLine("The Expression: " + expr);
         MatchCollection mc = Regex.Matches(text, expr);
         foreach (Match m in mc) {
            Console.WriteLine(m);
         }
      }
      static void Main(string[] args) {
         string str = "make maze and manage to measure it";

         Console.WriteLine("Matching words start with 'm' and ends with 'e':");
         showMatch(str, @"\bm\S*e\b");
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Matching words start with 'm' and ends with 'e':
The Expression: \bm\S*e\b
make
maze
manage
measure

示例 3

此示例替換額外的空格 -

using System;
using System.Text.RegularExpressions;

namespace RegExApplication {

   class Program {
   
      static void Main(string[] args) {
         string input = "Hello   World   ";
         string pattern = "\\s+";
         string replacement = " ";
         Regex rgx = new Regex(pattern);
         string result = rgx.Replace(input, replacement);

         Console.WriteLine("Original String: {0}", input);
         Console.WriteLine("Replacement String: {0}", result);    
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Original String: Hello World   
Replacement String: Hello World   

C# - 異常處理

異常是在程式執行期間出現的錯誤。C# 異常是對程式執行期間出現的異常情況的響應,例如嘗試除以零。

異常提供了一種將控制權從程式的一部分轉移到另一部分的方法。C# 異常處理建立在四個關鍵字之上:trycatchfinallythrow

  • try − try 塊標識一個程式碼塊,為此啟用特定異常。它後面跟著一個或多個 catch 塊。

  • catch − 程式在程式中要處理問題的某個位置使用異常處理程式捕獲異常。catch 關鍵字表示捕獲異常。

  • finally − finally 塊用於執行給定的語句集,無論是否丟擲異常。例如,如果您開啟一個檔案,無論是否引發異常,都必須關閉它。

  • throw − 當出現問題時,程式會丟擲異常。這是使用 throw 關鍵字完成的。

語法

假設一個塊引發了異常,則方法使用 try 和 catch 關鍵字的組合捕獲異常。try/catch 塊放置在可能生成異常的程式碼周圍。try/catch 塊內的程式碼稱為受保護程式碼,使用 try/catch 的語法如下 -

try {
   // statements causing exception
} catch( ExceptionName e1 ) {
   // error handling code
} catch( ExceptionName e2 ) {
   // error handling code
} catch( ExceptionName eN ) {
   // error handling code
} finally {
   // statements to be executed
}

您可以列出多個 catch 語句以捕獲不同型別的異常,以防您的 try 塊在不同情況下引發多個異常。

C# 中的異常類

C# 異常由類表示。C# 中的異常類主要直接或間接地派生自System.Exception類。一些從 System.Exception 類派生的異常類是System.ApplicationException System.SystemException類。

System.ApplicationException類支援應用程式程式生成的異常。因此,程式設計師定義的異常應該從此類派生。

System.SystemException類是所有預定義系統異常的基類。

下表提供了一些從 Sytem.SystemException 類派生的預定義異常類 -

序號 異常類 & 描述
1

System.IO.IOException

處理 I/O 錯誤。

2

System.IndexOutOfRangeException

處理方法引用超出範圍的陣列索引時生成的錯誤。

3

System.ArrayTypeMismatchException

處理型別與陣列型別不匹配時生成的錯誤。

4

System.NullReferenceException

處理引用空物件時生成的錯誤。

5

System.DivideByZeroException

處理除數為零導致的錯誤。

6

System.InvalidCastException

處理型別轉換期間產生的錯誤。

7

System.OutOfMemoryException

處理由於可用記憶體不足而產生的錯誤。

8

System.StackOverflowException

處理棧溢位產生的錯誤。

異常處理

C# 提供了一種結構化的異常處理解決方案,即使用 try 和 catch 程式碼塊。使用這些程式碼塊,可以將核心程式語句與錯誤處理語句分開。

這些錯誤處理塊使用 **try**、**catch** 和 **finally** 關鍵字實現。以下是在除以零條件發生時丟擲異常的示例:

using System;

namespace ErrorHandlingApplication {

   class DivNumbers {
      int result;
      
      DivNumbers() {
         result = 0;
      }
      
      public void division(int num1, int num2) {
         try {
            result = num1 / num2;
         } catch (DivideByZeroException e) {
            Console.WriteLine("Exception caught: {0}", e);
         } finally {
            Console.WriteLine("Result: {0}", result);
         }
      }
      
      static void Main(string[] args) {
         DivNumbers d = new DivNumbers();
         d.division(25, 0);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Exception caught: System.DivideByZeroException: Attempted to divide by zero. 
at ...
Result: 0

建立使用者定義的異常

您也可以定義自己的異常。使用者定義的異常類是從 **Exception** 類派生的。以下示例演示了這一點:

using System;

namespace UserDefinedException {

   class TestTemperature {
   
      static void Main(string[] args) {
         Temperature temp = new Temperature();
         try {
            temp.showTemp();
         } catch(TempIsZeroException e) {
            Console.WriteLine("TempIsZeroException: {0}", e.Message);
         }
         Console.ReadKey();
      }
   }
}

public class TempIsZeroException: Exception {

   public TempIsZeroException(string message): base(message) {
   
   }
}

public class Temperature {
   int temperature = 0;
   
   public void showTemp() {
      
      if(temperature == 0) {
         throw (new TempIsZeroException("Zero Temperature found"));
      } else {
         Console.WriteLine("Temperature: {0}", temperature);
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

TempIsZeroException: Zero Temperature found

丟擲物件

如果物件直接或間接從 **System.Exception** 類派生,則可以丟擲該物件。您可以在 catch 程式碼塊中使用 throw 語句丟擲當前物件,如下所示:

Catch(Exception e) {
   ...
   Throw e
}

C# - 檔案 I/O

**檔案**是儲存在磁碟上的一組資料,具有特定的名稱和目錄路徑。當檔案被開啟以進行讀取或寫入時,它就變成了一個**流**。

流基本上是透過通訊路徑傳遞的位元組序列。主要有兩種流:**輸入流**和**輸出流**。**輸入流**用於從檔案讀取資料(讀取操作),**輸出流**用於向檔案寫入資料(寫入操作)。

C# I/O 類

System.IO 名稱空間包含各種用於執行檔案操作的類,例如建立和刪除檔案、讀取或寫入檔案、關閉檔案等。

下表顯示了 System.IO 名稱空間中一些常用的非抽象類:

序號 I/O 類 & 描述
1

BinaryReader

從二進位制流中讀取原始資料。

2

BinaryWriter

以二進位制格式寫入原始資料。

3

BufferedStream

位元組流的臨時儲存。

4

Directory

幫助操作目錄結構。

5

DirectoryInfo

用於執行目錄操作。

6

DriveInfo

提供驅動器資訊。

7

File

幫助操作檔案。

8

FileInfo

用於執行檔案操作。

9

FileStream

用於讀取和寫入檔案中的任何位置。

10

MemoryStream

用於對儲存在記憶體中的流式資料進行隨機訪問。

11

Path

執行路徑資訊操作。

12

StreamReader

用於從位元組流中讀取字元。

13

StreamWriter

用於向流中寫入字元。

14

StringReader

用於從字串緩衝區讀取。

15

StringWriter

用於向字串緩衝區寫入。

FileStream 類

System.IO 名稱空間中的 **FileStream** 類有助於讀取、寫入和關閉檔案。此類派生自抽象類 Stream。

您需要建立一個 **FileStream** 物件來建立新檔案或開啟現有檔案。建立 **FileStream** 物件的語法如下所示:

FileStream <object_name> = new FileStream( <file_name>, <FileMode Enumerator>,
   <FileAccess Enumerator>, <FileShare Enumerator>);

例如,我們建立了一個 FileStream 物件 **F** 用於讀取名為 **sample.txt 的檔案,如下所示:**

FileStream F = new FileStream("sample.txt", FileMode.Open, FileAccess.Read,
   FileShare.Read);
序號 引數 & 描述
1

FileMode

**FileMode** 列舉定義了開啟檔案的各種方法。FileMode 列舉的成員有:

  • **Append** - 開啟現有檔案並將游標置於檔案末尾,或者如果檔案不存在,則建立該檔案。

  • **Create** - 建立一個新檔案。

  • **CreateNew** - 指定給作業系統,它應該建立一個新檔案。

  • **Open** - 開啟現有檔案。

  • **OpenOrCreate** - 指定給作業系統,如果檔案存在則開啟它,否則建立一個新檔案。

  • **Truncate** - 開啟現有檔案並將大小截斷為零位元組。

2

FileAccess

**FileAccess** 列舉具有成員:**Read**、**ReadWrite** 和 **Write**。

3

FileShare

**FileShare** 列舉具有以下成員:

  • **Inheritable** - 允許檔案控制代碼將繼承傳遞給子程序

  • **None** - 拒絕共享當前檔案

  • **Read** - 允許開啟檔案進行讀取。

  • **ReadWrite** - 允許開啟檔案進行讀取和寫入

  • **Write** - 允許開啟檔案進行寫入

示例

以下程式演示了 **FileStream** 類的用法:

using System;
using System.IO;

namespace FileIOApplication {
   
   class Program {
      
      static void Main(string[] args) {
         FileStream F = new FileStream("test.dat", FileMode.OpenOrCreate, 
            FileAccess.ReadWrite);
         
         for (int i = 1; i <= 20; i++) {
            F.WriteByte((byte)i);
         }
         
         F.Position = 0;
         for (int i = 0; i <= 20; i++) {
            Console.Write(F.ReadByte() + " ");
         }
         F.Close();
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -1

C# 中的高階檔案操作

前面的示例提供了 C# 中簡單的檔案操作。但是,要利用 C# System.IO 類的強大功能,您需要了解這些類的常用屬性和方法。

序號 主題 & 描述
1 讀取和寫入文字檔案

它涉及讀取和寫入文字檔案。**StreamReader** 和 **StreamWriter** 類有助於完成此操作。

2 讀取和寫入二進位制檔案

它涉及讀取和寫入二進位制檔案。**BinaryReader** 和 **BinaryWriter** 類有助於完成此操作。

3 操作 Windows 檔案系統

它使 C# 程式設計師能夠瀏覽和定位 Windows 檔案和目錄。

C# - 屬性

**特性**是一個宣告性標籤,用於向執行時傳達有關程式中各種元素(如類、方法、結構、列舉、程式集等)行為的資訊。您可以透過使用特性向程式新增宣告性資訊。宣告性標籤由放置在它所用元素上方的方括號 ([ ]) 表示。

特性用於向程式新增元資料,例如編譯器指令和其他資訊,如註釋、描述、方法和類。.Net Framework 提供兩種型別的特性:*預定義*特性和*自定義構建*特性。

指定特性

指定特性的語法如下所示:

[attribute(positional_parameters, name_parameter = value, ...)]
element

特性名稱及其值在方括號內指定,位於應用特性的元素之前。位置引數指定基本資訊,名稱引數指定可選資訊。

預定義特性

.Net Framework 提供三個預定義特性:

  • AttributeUsage
  • 條件
  • Obsolete

AttributeUsage

預定義特性 **AttributeUsage** 描述瞭如何使用自定義特性類。它指定了可以應用特性的專案型別。

指定此特性的語法如下所示:

[AttributeUsage (
   validon,
   AllowMultiple = allowmultiple,
   Inherited = inherited
)]

其中,

  • validon 引數指定可以在其上放置特性的語言元素。它是列舉 *AttributeTargets* 值的組合。預設值為 *AttributeTargets.All*。

  • allowmultiple 引數(可選)為該特性的 *AllowMultiple* 屬性提供值,一個布林值。如果為 true,則該特性是多用途的。預設為 false(單用途)。

  • inherited 引數(可選)為該特性的 *Inherited* 屬性提供值,一個布林值。如果為 true,則該特性會被派生類繼承。預設值為 false(未繼承)。

例如,

[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property, 
AllowMultiple = true)]

條件

此預定義特性標記一個條件方法,其執行取決於指定的預處理識別符號。

它根據指定的值(如 **Debug** 或 **Trace**)導致條件編譯方法呼叫。例如,它在除錯程式碼時顯示變數的值。

指定此特性的語法如下所示:

[Conditional(
   conditionalSymbol
)]

例如,

[Conditional("DEBUG")]

以下示例演示了該特性:

#define DEBUG
using System;
using System.Diagnostics;

public class Myclass {
   [Conditional("DEBUG")]
   
   public static void Message(string msg) {
      Console.WriteLine(msg);
   }
}

class Test {
   static void function1() {
      Myclass.Message("In Function 1.");
      function2();
   }
   
   static void function2() {
      Myclass.Message("In Function 2.");
   }
   
   public static void Main() {
      Myclass.Message("In Main function.");
      function1();
      Console.ReadKey();
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

In Main function
In Function 1
In Function 2

Obsolete

此預定義特性標記不應使用的程式實體。它使您能夠通知編譯器丟棄特定的目標元素。例如,當在類中使用新方法時,如果仍然希望在類中保留舊方法,則可以透過顯示一條訊息來將其標記為已過時,該訊息應使用新方法而不是舊方法。

指定此特性的語法如下所示:

[Obsolete (
   message
)]

[Obsolete (
   message,
   iserror
)]

其中,

  • message 引數是一個字串,描述該專案已過時的原因以及應使用什麼代替。

  • iserror 引數是一個布林值。如果其值為 true,則編譯器應將專案的用法視為錯誤。預設值為 false(編譯器生成警告)。

以下程式演示了這一點 -

using System;

public class MyClass {
   [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
   
   static void OldMethod() {
      Console.WriteLine("It is the old method");
   }
   
   static void NewMethod() {
      Console.WriteLine("It is the new method"); 
   }
   
   public static void Main() {
      OldMethod();
   }
}

當您嘗試編譯程式時,編譯器會給出錯誤訊息,指出:

 Don't use OldMethod, use NewMethod instead

建立自定義特性

.Net Framework 允許建立自定義特性,這些特性可用於儲存宣告性資訊,並在執行時檢索。此資訊可以與任何目標元素相關,具體取決於設計標準和應用程式需求。

建立和使用自定義特性涉及四個步驟:

  • 宣告自定義特性
  • 構造自定義特性
  • 在目標程式元素上應用自定義特性
  • 透過反射訪問特性

最後一步涉及編寫一個簡單的程式來讀取元資料以查詢各種註釋。元資料是關於資料的資料或用於描述其他資料的資訊。此程式應使用反射在執行時訪問特性。我們將在下一章中討論這一點。

宣告自定義特性

新的自定義特性應從 **System.Attribute** 類派生。例如,

//a custom attribute BugFix to be assigned to a class and its members
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute

在前面的程式碼中,我們聲明瞭一個名為 *DeBugInfo* 的自定義特性。

構造自定義特性

讓我們構造一個名為 *DeBugInfo* 的自定義特性,它儲存除錯任何程式獲得的資訊。讓它儲存以下資訊:

  • 錯誤的程式碼編號
  • 識別錯誤的開發人員的姓名
  • 程式碼上次審查的日期
  • 用於儲存開發人員備註的字串訊息

DeBugInfo 類有三個私有屬性用於儲存前三個資訊,以及一個公有屬性用於儲存訊息。因此,錯誤編號、開發人員姓名和審查日期是 DeBugInfo 類的位置引數,而訊息是可選引數或命名引數。

每個屬性都必須至少有一個建構函式。位置引數應該透過建構函式傳遞。以下程式碼顯示了 DeBugInfo 類 -

//a custom attribute BugFix to be assigned to a class and its members
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute {
   private int bugNo;
   private string developer;
   private string lastReview;
   public string message;
   
   public DeBugInfo(int bg, string dev, string d) {
      this.bugNo = bg;
      this.developer = dev;
      this.lastReview = d;
   }

   public int BugNo {
      get {
         return bugNo;
      }
   }
   
   public string Developer {
      get {
         return developer;
      }
   }
   
   public string LastReview {
      get {
         return lastReview;
      }
   }
   
   public string Message {
      get {
         return message;
      }
      set {
         message = value;
      }
   }
}

應用自定義屬性

透過將屬性放置在其目標的正前方來應用它 -

[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle {
   //member variables
   protected double length;
   protected double width;
   public Rectangle(double l, double w) {
      length = l;
      width = w;
   }
   [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
   
   public double GetArea() {
      return length * width;
   }
   [DeBugInfo(56, "Zara Ali", "19/10/2012")]
   
   public void Display() {
      Console.WriteLine("Length: {0}", length);
      Console.WriteLine("Width: {0}", width);
      Console.WriteLine("Area: {0}", GetArea());
   }
}

在下一章中,我們將使用反射類物件檢索屬性資訊。

C# - 反射

反射物件用於在執行時獲取型別資訊。提供對正在執行的程式的元資料訪問的類位於 System.Reflection 名稱空間中。

System.Reflection 名稱空間包含允許您獲取有關應用程式的資訊以及動態嚮應用程式新增型別、值和物件的類。

反射的應用

反射具有以下應用 -

  • 它允許在執行時檢視屬性資訊。

  • 它允許檢查程式集中各種型別並例項化這些型別。

  • 它允許延遲繫結到方法和屬性

  • 它允許在執行時建立新型別,然後使用這些型別執行某些任務。

檢視元資料

我們在上一章中提到,使用反射可以檢視屬性資訊。

System.Reflection 類的 MemberInfo 物件需要初始化才能發現與類關聯的屬性。為此,您定義目標類的物件,如下所示 -

System.Reflection.MemberInfo info = typeof(MyClass);

以下程式演示了這一點 -

using System;

[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute {
   public readonly string Url;
   
   public string Topic   // Topic is a named parameter {
      get {
         return topic;
      }
      
      set {
         topic = value;
      }
   }
   
   public HelpAttribute(string url)   // url is a positional parameter {
      this.Url = url;
   }
   private string topic;
}

[HelpAttribute("Information on the class MyClass")]
class MyClass {

}

namespace AttributeAppl {

   class Program {
   
      static void Main(string[] args) {
         System.Reflection.MemberInfo info = typeof(MyClass);
         object[] attributes = info.GetCustomAttributes(true);
         for (int i = 0; i < attributes.Length; i++) {
            System.Console.WriteLine(attributes[i]);
         }
         Console.ReadKey();
      }
   }
}

當它被編譯並執行時,它將顯示附加到 MyClass 類上的自定義屬性的名稱 -

HelpAttribute

示例

在這個例子中,我們使用了上一章建立的 DeBugInfo 屬性,並使用反射來讀取 Rectangle 類中的元資料。

using System;
using System.Reflection;

namespace BugFixApplication {
   //a custom attribute BugFix to be
   //assigned to a class and its members
   [AttributeUsage(AttributeTargets.Class |
   AttributeTargets.Constructor |
   AttributeTargets.Field |
   AttributeTargets.Method |
   AttributeTargets.Property,
   AllowMultiple = true)]

   public class DeBugInfo : System.Attribute {
      private int bugNo;
      private string developer;
      private string lastReview;
      public string message;
      
      public DeBugInfo(int bg, string dev, string d) {
         this.bugNo = bg;
         this.developer = dev;
         this.lastReview = d;
      }
      
      public int BugNo {
         get {
            return bugNo;
         }
      }
      
      public string Developer {
         get {
            return developer;
         }
      }
      
      public string LastReview {
         get {
            return lastReview;
         }
      }
      
      public string Message {
         get {
            return message;
         }
         set {
            message = value;
         }
      }
   }
   [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
   [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
   
   class Rectangle {
      //member variables
      protected double length;
      protected double width;
      public Rectangle(double l, double w) {
         length = l;
         width = w;
      }
      
      [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
      public double GetArea() {
         return length * width;
      }
      
      [DeBugInfo(56, "Zara Ali", "19/10/2012")]
      public void Display() {
         Console.WriteLine("Length: {0}", length);
         Console.WriteLine("Width: {0}", width);
         Console.WriteLine("Area: {0}", GetArea());
      }
   }//end class Rectangle
   
   class ExecuteRectangle {
      static void Main(string[] args) {
         Rectangle r = new Rectangle(4.5, 7.5);
         r.Display();
         Type type = typeof(Rectangle);
         
         //iterating through the attribtues of the Rectangle class
         foreach (Object attributes in type.GetCustomAttributes(false)) {
            DeBugInfo dbi = (DeBugInfo)attributes;
            
            if (null != dbi) {
               Console.WriteLine("Bug no: {0}", dbi.BugNo);
               Console.WriteLine("Developer: {0}", dbi.Developer);
               Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
               Console.WriteLine("Remarks: {0}", dbi.Message);
            }
         }

         //iterating through the method attribtues
         foreach (MethodInfo m in type.GetMethods()) {
            
            foreach (Attribute a in m.GetCustomAttributes(true)) {
               DeBugInfo dbi = (DeBugInfo)a;
               
               if (null != dbi) {
                  Console.WriteLine("Bug no: {0}, for Method: {1}", dbi.BugNo, m.Name);
                  Console.WriteLine("Developer: {0}", dbi.Developer);
                  Console.WriteLine("Last Reviewed: {0}", dbi.LastReview);
                  Console.WriteLine("Remarks: {0}", dbi.Message);
               }
            }
         }
         Console.ReadLine();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Length: 4.5
Width: 7.5
Area: 33.75
Bug No: 49
Developer: Nuha Ali
Last Reviewed: 10/10/2012
Remarks: Unused variable
Bug No: 45
Developer: Zara Ali
Last Reviewed: 12/8/2012
Remarks: Return type mismatch
Bug No: 55, for Method: GetArea
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks: Return type mismatch
Bug No: 56, for Method: Display
Developer: Zara Ali
Last Reviewed: 19/10/2012
Remarks: 

C# - 屬性

屬性是類、結構和介面的命名成員。類或結構中的成員變數或方法稱為欄位。屬性是欄位的擴充套件,並使用相同的語法訪問。它們使用訪問器,透過這些訪問器可以讀取、寫入或操作私有欄位的值。

屬性不命名儲存位置。相反,它們具有訪問器,用於讀取、寫入或計算其值。

例如,讓我們有一個名為 Student 的類,其中包含年齡、姓名和程式碼的私有欄位。我們不能直接從類範圍之外訪問這些欄位,但我們可以使用屬性來訪問這些私有欄位。

訪問器

屬性的訪問器包含有助於獲取(讀取或計算)或設定(寫入)屬性的可執行語句。訪問器宣告可以包含 get 訪問器、set 訪問器或兩者兼而有之。例如 -

// Declare a Code property of type string:
public string Code {
   get {
      return code;
   }
   set {
      code = value;
   }
}

// Declare a Name property of type string:
public string Name {
   get {
      return name;
   }
   set {
      name = value;
   }
}

// Declare a Age property of type int:
public int Age { 
   get {
      return age;
   }
   set {
      age = value;
   }
}

示例

以下示例演示了屬性的使用 -

using System;
namespace tutorialspoint {
   class Student {
      private string code = "N.A";
      private string name = "not known";
      private int age = 0;
      
      // Declare a Code property of type string:
      public string Code {
         get {
            return code;
         }
         set {
            code = value;
         }
      }
      
      // Declare a Name property of type string:
      public string Name {
         get {
            return name;
         }
         set {
            name = value;
         }
      }
      
      // Declare a Age property of type int:
      public int Age {
         get {
            return age;
         }
         set {
            age = value;
         }
      }
      public override string ToString() {
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
   }
   
   class ExampleDemo {
      public static void Main() {
      
         // Create a new Student object:
         Student s = new Student();
         
         // Setting code, name and the age of the student
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info: {0}", s);
         
         //let us increase age
         s.Age += 1;
         Console.WriteLine("Student Info: {0}", s);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Student Info: Code = 001, Name = Zara, Age = 9
Student Info: Code = 001, Name = Zara, Age = 10

抽象屬性

抽象類可以具有抽象屬性,該屬性應在派生類中實現。以下程式說明了這一點 -

using System;
namespace tutorialspoint {
   public abstract class Person {
      public abstract string Name {
         get;
         set;
      }
      public abstract int Age {
         get;
         set;
      }
   }
   
   class Student : Person {
   
      private string code = "N.A";
      private string name = "N.A";
      private int age = 0;
      
      // Declare a Code property of type string:
      public string Code {
         get {
            return code;
         }
         set {
            code = value;
         }
      }
      
      // Declare a Name property of type string:
      public override string Name {
         get {
            return name;
         }
         set {
            name = value;
         }
      }
      
      // Declare a Age property of type int:
      public override int Age {
         get {
            return age;
         }
         set {
            age = value;
         }
      }
      public override string ToString() {
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
   }
   
   class ExampleDemo {
      public static void Main() {
         // Create a new Student object:
         Student s = new Student();
         
         // Setting code, name and the age of the student
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info:- {0}", s);
         
         //let us increase age
         s.Age += 1;
         Console.WriteLine("Student Info:- {0}", s);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Student Info: Code = 001, Name = Zara, Age = 9
Student Info: Code = 001, Name = Zara, Age = 10

C# - 索引器

索引器允許像陣列一樣對物件進行索引。當您為類定義索引器時,此類表現得類似於虛擬陣列。然後,您可以使用陣列訪問運算子([ ])訪問此類的例項。

語法

一維索引器具有以下語法 -

element-type this[int index] {

   // The get accessor.
   get {
      // return the value specified by index
   }
   
   // The set accessor.
   set {
      // set the value specified by index
   }
}

索引器的使用

索引器行為的宣告在某種程度上類似於屬性。與屬性類似,您使用getset訪問器來定義索引器。但是,屬性返回或設定特定的資料成員,而索引器返回或設定物件例項中的特定值。換句話說,它將例項資料分解成更小的部分併為每個部分編制索引,獲取或設定每個部分。

定義屬性涉及提供屬性名稱。索引器不用名稱定義,而是使用this關鍵字,該關鍵字引用物件例項。以下示例演示了此概念 -

using System;

namespace IndexerApplication {
   
   class IndexedNames {
      private string[] namelist = new string[size];
      static public int size = 10;
      
      public IndexedNames() {
         for (int i = 0; i < size; i++)
         namelist[i] = "N. A.";
      }
      
      public string this[int index] {
      
         get {
            string tmp;
         
            if( index >= 0 && index <= size-1 ) {
               tmp = namelist[index];
            } else {
               tmp = "";
            }
            
            return ( tmp );
         }
         set {
            if( index >= 0 && index <= size-1 ) {
               namelist[index] = value;
            }
         }
      }
      
      static void Main(string[] args) {
         IndexedNames names = new IndexedNames();
         names[0] = "Zara";
         names[1] = "Riz";
         names[2] = "Nuha";
         names[3] = "Asif";
         names[4] = "Davinder";
         names[5] = "Sunil";
         names[6] = "Rubic";
         
         for ( int i = 0; i < IndexedNames.size; i++ ) {
            Console.WriteLine(names[i]);
         }
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.

過載索引器

索引器可以過載。索引器也可以用多個引數宣告,並且每個引數可以是不同的型別。索引不必是整數。C# 允許索引為其他型別,例如字串。

以下示例演示了過載索引器 -

using System;

namespace IndexerApplication {

   class IndexedNames {
      private string[] namelist = new string[size];
      static public int size = 10;
      
      public IndexedNames() {
         for (int i = 0; i < size; i++) {
            namelist[i] = "N. A.";
         }
      }
      
      public string this[int index] {
         
         get {
            string tmp;
            
            if( index >= 0 && index <= size-1 ) {
               tmp = namelist[index];
            } else {
               tmp = "";
            }
            
            return ( tmp );
         }
         set {
            if( index >= 0 && index <= size-1 ) {
               namelist[index] = value;
            }
         }
      }
      
      public int this[string name] {
         
         get {
            int index = 0;
            
            while(index < size) {
               if (namelist[index] == name) {
                return index;
               }
               index++;
            }
            return index;
         }

      }

      static void Main(string[] args) {
         IndexedNames names = new IndexedNames();
         names[0] = "Zara";
         names[1] = "Riz";
         names[2] = "Nuha";
         names[3] = "Asif";
         names[4] = "Davinder";
         names[5] = "Sunil";
         names[6] = "Rubic";
         
         //using the first indexer with int parameter
         for (int i = 0; i < IndexedNames.size; i++) {
            Console.WriteLine(names[i]);
         }
         
         //using the second indexer with the string parameter
         Console.WriteLine(names["Nuha"]);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Zara
Riz
Nuha
Asif
Davinder
Sunil
Rubic
N. A.
N. A.
N. A.
2

C# - 委託

C# 委託類似於 C 或 C++ 中的函式指標。委託是一種引用型別變數,它儲存對方法的引用。引用可以在執行時更改。

委託特別用於實現事件和回撥方法。所有委託都隱式派生自System.Delegate類。

宣告委託

委託聲明確定委託可以引用的方法。委託可以引用與委託具有相同簽名的方法。

例如,考慮一個委託 -

public delegate int MyDelegate (string s);

前面的委託可用於引用任何具有單個string引數並返回int型別變數的方法。

委託宣告的語法為 -

delegate <return type> <delegate-name> <parameter list>

例項化委託

一旦聲明瞭委託型別,就必須使用new關鍵字建立委託物件並將其與特定方法關聯。建立委託時,傳遞給new表示式的引數寫法類似於方法呼叫,但沒有方法的引數。例如 -

public delegate void printString(string s);
...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);

以下示例演示了可以用於引用接受整數引數並返回整數值的方法的委託的宣告、例項化和使用。

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl {
   
   class TestDelegate {
      static int num = 10;
      
      public static int AddNum(int p) {
         num += p;
         return num;
      }

      public static int MultNum(int q) {
         num *= q;
         return num;
      }
      
      public static int getNum() {
         return num;
      }

      static void Main(string[] args) {
         //create delegate instances
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         
         //calling the methods using the delegate objects
         nc1(25);
         Console.WriteLine("Value of Num: {0}", getNum());
         nc2(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Value of Num: 35
Value of Num: 175

委託的多播

可以使用“+”運算子組合委託物件。組合的委託會呼叫它從中組合的兩個委託。只能組合相同型別的委託。“-”運算子可用於從組合的委託中刪除元件委託。

使用委託的此屬性,您可以建立一個方法呼叫列表,這些方法將在呼叫委託時被呼叫。這稱為委託的多播。以下程式演示了委託的多播 -

using System;

delegate int NumberChanger(int n);
namespace DelegateAppl {

   class TestDelegate {
      static int num = 10;
      
      public static int AddNum(int p) {
         num += p;
         return num;
      }

      public static int MultNum(int q) {
         num *= q;
         return num;
      }
      
      public static int getNum() {
         return num;
      }

      static void Main(string[] args) {
         //create delegate instances
         NumberChanger nc;
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         nc = nc1;
         nc += nc2;
         
         //calling multicast
         nc(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Value of Num: 75

使用委託

以下示例演示了委託的使用。委託printString可用於引用將字串作為輸入並返回空值的方法。

我們使用此委託來呼叫兩個方法,第一個將字串列印到控制檯,第二個將字串列印到檔案 -

using System;
using System.IO;

namespace DelegateAppl {

   class PrintString {
      static FileStream fs;
      static StreamWriter sw;
      
      // delegate declaration
      public delegate void printString(string s);

      // this method prints to the console
      public static void WriteToScreen(string str) {
         Console.WriteLine("The String is: {0}", str);
      }
      
      //this method prints to a file
      public static void WriteToFile(string s) {
         fs = new FileStream("c:\\message.txt",
         FileMode.Append, FileAccess.Write);
         sw = new StreamWriter(fs);
         sw.WriteLine(s);
         sw.Flush();
         sw.Close();
         fs.Close();
      }
      
      // this method takes the delegate as parameter and uses it to
      // call the methods as required
      public static void sendString(printString ps) {
         ps("Hello World");
      }
      
      static void Main(string[] args) {
         printString ps1 = new printString(WriteToScreen);
         printString ps2 = new printString(WriteToFile);
         sendString(ps1);
         sendString(ps2);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

The String is: Hello World

C# - 事件

事件是使用者操作,例如按鍵、點選、滑鼠移動等,或某些事件,例如系統生成的通知。應用程式需要在事件發生時對其做出響應。例如,中斷。事件用於程序間通訊。

將委託與事件一起使用

事件在類中宣告和引發,並使用同一類或其他類中的委託與事件處理程式關聯。包含事件的類用於釋出事件。這稱為釋出者類。接受此事件的其他一些類稱為訂閱者類。事件使用釋出者-訂閱者模型。

釋出者是一個包含事件定義和委託的物件。事件-委託關聯也在此物件中定義。釋出者類物件呼叫事件,並將其通知給其他物件。

訂閱者是一個接受事件並提供事件處理程式的物件。釋出者類中的委託呼叫訂閱者類的方法(事件處理程式)。

宣告事件

要在類內部宣告事件,首先必須為事件宣告委託型別,如下所示

public delegate string BoilerLogHandler(string str);

然後,使用event關鍵字宣告事件 -

event BoilerLogHandler BoilerEventLog;

前面的程式碼定義了一個名為BoilerLogHandler的委託和一個名為BoilerEventLog的事件,該事件在引發時呼叫委託。

示例

using System;

namespace SampleApp {
   public delegate string MyDel(string str);
	
   class EventProgram {
      event MyDel MyEvent;
		
      public EventProgram() {
         this.MyEvent += new MyDel(this.WelcomeUser);
      }
		
      public string WelcomeUser(string username) {
         return "Welcome " + username;
      }
		
      static void Main(string[] args) {
         EventProgram obj1 = new EventProgram();
         string result = obj1.MyEvent("Tutorials Point");
         Console.WriteLine(result);
      }

   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Welcome Tutorials Point

C# - 集合

集合類是用於資料儲存和檢索的專門類。這些類為堆疊、佇列、列表和雜湊表提供支援。大多數集合類都實現相同的介面。

集合類用於各種目的,例如動態為元素分配記憶體以及根據索引訪問專案列表等。這些類建立 Object 類的物件的集合,Object 類是 C# 中所有資料型別的基類。

各種集合類及其用法

以下是System.Collection名稱空間中常用的各種類。單擊以下連結以檢查其詳細資訊。

序號 類和說明及用法
1 ArrayList

它表示可以單獨索引的物件的有序集合。

它基本上是陣列的替代方案。但是,與陣列不同,您可以使用索引在指定位置新增和刪除列表中的專案,並且陣列會自動調整自身大小。它還允許動態記憶體分配、新增、搜尋和排序列表中的專案。

2 Hashtable

它使用訪問集合中的元素。

當您需要使用鍵訪問元素並且可以識別有用的鍵值時,使用雜湊表。雜湊表中的每個專案都有一個鍵/值對。鍵用於訪問集合中的專案。

3 SortedList

它使用索引來訪問列表中的專案。

排序列表是陣列和雜湊表的組合。它包含一個專案列表,可以使用鍵或索引訪問。如果使用索引訪問專案,則為 ArrayList,如果使用鍵訪問專案,則為 Hashtable。專案集合始終按鍵值排序。

4 Stack

它表示物件的後進先出集合。

當您需要後進先出訪問專案時使用它。當您在列表中新增專案時,稱為推送專案,當您將其刪除時,稱為彈出專案。

5 Queue

它表示物件的先進先出集合。

當您需要先進先出 (FIFO) 訪問專案時使用它。當您在列表中新增專案時,稱為入隊 (enqueue),當您刪除專案時,稱為出隊 (deque)。

6 位陣列 (BitArray)

它表示使用值 1 和 0 的二進位制表示的陣列。

當您需要儲存位但事先不知道位數時,可以使用它。您可以使用整數索引訪問 BitArray 集合中的專案,索引從零開始。

C# - 泛型

泛型允許您在類或方法中定義程式設計元素的資料型別的規範,直到它實際在程式中使用。換句話說,泛型允許您編寫可以處理任何資料型別的類或方法。

您使用替換資料型別引數來編寫類或方法的規範。當編譯器遇到類的建構函式或方法的函式呼叫時,它會生成程式碼來處理特定的資料型別。一個簡單的例子將有助於理解這個概念 -

using System;
using System.Collections.Generic;

namespace GenericApplication {

   public class MyGenericArray<T> {
      private T[] array;
      
      public MyGenericArray(int size) {
         array = new T[size + 1];
      }
      
      public T getItem(int index) {
         return array[index];
      }
      
      public void setItem(int index, T value) {
         array[index] = value;
      }
   }
   
   class Tester {
      static void Main(string[] args) {
         
         //declaring an int array
         MyGenericArray<int> intArray = new MyGenericArray<int>(5);
         
         //setting values
         for (int c = 0; c < 5; c++) {
            intArray.setItem(c, c*5);
         }
         
         //retrieving the values
         for (int c = 0; c < 5; c++) {
            Console.Write(intArray.getItem(c) + " ");
         }
         
         Console.WriteLine();
         
         //declaring a character array
         MyGenericArray<char> charArray = new MyGenericArray<char>(5);
         
         //setting values
         for (int c = 0; c < 5; c++) {
            charArray.setItem(c, (char)(c+97));
         }
         
         //retrieving the values
         for (int c = 0; c< 5; c++) {
            Console.Write(charArray.getItem(c) + " ");
         }
         Console.WriteLine();
         
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

0 5 10 15 20
a b c d e

泛型的特性

泛型是一種以以下方式豐富程式的技術 -

  • 它有助於最大程度地提高程式碼重用性、型別安全性以及效能。

  • 您可以建立泛型集合類。.NET Framework 類庫在System.Collections.Generic名稱空間中包含幾個新的泛型集合類。您可以使用這些泛型集合類來代替System.Collections名稱空間中的集合類。

  • 您可以建立自己的泛型介面、類、方法、事件和委託。

  • 您可以建立受約束的泛型類,以啟用對特定資料型別上的方法的訪問。

  • 您可以透過反射在執行時獲取有關泛型資料型別中使用的型別的資訊。

泛型方法

在前面的示例中,我們使用了泛型類;我們可以使用型別引數宣告泛型方法。以下程式說明了這個概念 -

using System;
using System.Collections.Generic;

namespace GenericMethodAppl {
   
   class Program {
      
      static void Swap<T>(ref T lhs, ref T rhs) {
         T temp;
         temp = lhs;
         lhs = rhs;
         rhs = temp;
      }
      
      static void Main(string[] args) {
         int a, b;
         char c, d;
         a = 10;
         b = 20;
         c = 'I';
         d = 'V';
         
         //display values before swap:
         Console.WriteLine("Int values before calling swap:");
         Console.WriteLine("a = {0}, b = {1}", a, b);
         Console.WriteLine("Char values before calling swap:");
         Console.WriteLine("c = {0}, d = {1}", c, d);
         
         //call swap
         Swap<int>(ref a, ref b);
         Swap<char>(ref c, ref d);
         
         //display values after swap:
         Console.WriteLine("Int values after calling swap:");
         Console.WriteLine("a = {0}, b = {1}", a, b);
         Console.WriteLine("Char values after calling swap:");
         Console.WriteLine("c = {0}, d = {1}", c, d);
         
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Int values before calling swap:
a = 10, b = 20
Char values before calling swap:
c = I, d = V
Int values after calling swap:
a = 20, b = 10
Char values after calling swap:
c = V, d = I

泛型委託

您可以使用型別引數定義泛型委託。例如 -

delegate T NumberChanger<T>(T n);

以下示例顯示了此委託的用法 -

using System;
using System.Collections.Generic;

delegate T NumberChanger<T>(T n);
namespace GenericDelegateAppl {
   
   class TestDelegate {
      static int num = 10;
      
      public static int AddNum(int p) {
         num += p;
         return num;
      }
      
      public static int MultNum(int q) {
         num *= q;
         return num;
      }
      
      public static int getNum() {
         return num;
      }
      
      static void Main(string[] args) {
         //create delegate instances
         NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
         NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
         
         //calling the methods using the delegate objects
         nc1(25);
         Console.WriteLine("Value of Num: {0}", getNum());
         nc2(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Value of Num: 35
Value of Num: 175

C# - 匿名方法

我們討論了委託用於引用與委託簽名相同的任何方法。換句話說,您可以使用該委託物件呼叫可以由委託引用的方法。

匿名方法提供了一種將程式碼塊作為委託引數傳遞的技術。匿名方法是沒有名稱的方法,只有主體。

您不需要在匿名方法中指定返回型別;它是從方法體內的 return 語句推斷出來的。

編寫匿名方法

匿名方法是在建立委託例項時使用delegate關鍵字宣告的。例如,

delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x) {
   Console.WriteLine("Anonymous Method: {0}", x);
};

程式碼塊Console.WriteLine("Anonymous Method: {0}", x);是匿名方法的主體。

委託可以用匿名方法和命名方法以相同的方式呼叫,即透過將方法引數傳遞給委託物件。

例如,

nc(10);

示例

以下示例演示了這個概念 -

using System;

delegate void NumberChanger(int n);
namespace DelegateAppl {
   
   class TestDelegate {
      static int num = 10;
      
      public static void AddNum(int p) {
         num += p;
         Console.WriteLine("Named Method: {0}", num);
      }
      
      public static void MultNum(int q) {
         num *= q;
         Console.WriteLine("Named Method: {0}", num);
      }
      
      public static int getNum() {
         return num;
      }
      
      static void Main(string[] args) {
         //create delegate instances using anonymous method
         NumberChanger nc = delegate(int x) {
            Console.WriteLine("Anonymous Method: {0}", x);
         };
         
         //calling the delegate using the anonymous method 
         nc(10);
         
         //instantiating the delegate using the named methods 
         nc =  new NumberChanger(AddNum);
         
         //calling the delegate using the named methods 
         nc(5);
         
         //instantiating the delegate using another named methods 
         nc =  new NumberChanger(MultNum);
         
         //calling the delegate using the named methods 
         nc(2);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Anonymous Method: 10
Named Method: 15
Named Method: 30

C# - 不安全程式碼

當代碼塊由unsafe修飾符標記時,C# 允許在程式碼塊的函式中使用指標變數。不安全程式碼或非託管程式碼是使用指標變數的程式碼塊。

注意 - 要在codingground上執行本章中提到的程式,請在專案 >> 編譯選項 >> 編譯命令中設定編譯選項為

mcs *.cs -out:main.exe -unsafe"

指標

指標是一個變數,其值為另一個變數的地址,即記憶體位置的直接地址。與任何變數或常量類似,您必須在使用指標儲存任何變數地址之前宣告它。

指標宣告的一般形式為 -

type *var-name;

以下是有效的指標宣告 -

int    *ip;    /* pointer to an integer */
double *dp;    /* pointer to a double */
float  *fp;    /* pointer to a float */
char   *ch     /* pointer to a character */

以下示例說明了在 C# 中使用指標,使用 unsafe 修飾符 -

using System;

namespace UnsafeCodeApplication {
   
   class Program {
      
      static unsafe void Main(string[] args) {
         int var = 20;
         int* p = &var;
         Console.WriteLine("Data is: {0} ",  var);
         Console.WriteLine("Address is: {0}",  (int)p);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果 -

Data is: 20
Address is: 99215364

除了將整個方法宣告為不安全之外,您還可以將程式碼的一部分宣告為不安全。下一節中的示例演示了這一點。

使用指標檢索資料值

您可以使用ToString()方法檢索儲存在指標變數引用的位置處的資料。以下示例演示了這一點 -

using System;

namespace UnsafeCodeApplication {
   
   class Program {
      
      public static void Main() {
         
         unsafe {
            int var = 20;
            int* p = &var;
            Console.WriteLine("Data is: {0} " , var);
            Console.WriteLine("Data is: {0} " , p->ToString());
            Console.WriteLine("Address is: {0} " , (int)p);
         }
         
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果 -

Data is: 20
Data is: 20
Address is: 77128984

將指標作為引數傳遞給方法

您可以將指標變數作為引數傳遞給方法。以下示例說明了這一點 -

using System;

namespace UnsafeCodeApplication {
   
   class TestPointer {
      
      public unsafe void swap(int* p, int *q) {
         int temp = *p;
         *p = *q;
         *q = temp;
      }
      
      public unsafe static void Main() {
         TestPointer p = new TestPointer();
         int var1 = 10;
         int var2 = 20;
         int* x = &var1;
         int* y = &var2;
         
         Console.WriteLine("Before Swap: var1:{0}, var2: {1}", var1, var2);
         p.swap(x, y);

         Console.WriteLine("After Swap: var1:{0}, var2: {1}", var1, var2);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

Before Swap: var1: 10, var2: 20
After Swap: var1: 20, var2: 10

使用指標訪問陣列元素

在 C# 中,陣列名稱和與陣列資料相同的型別的資料指標不是相同的變數型別。例如,int *p 和 int[] p 不是相同的型別。您可以遞增指標變數 p,因為它在記憶體中不是固定的,但陣列地址在記憶體中是固定的,您不能遞增它。

因此,如果您需要像我們在 C 或 C++ 中傳統地那樣使用指標變數訪問陣列資料(請檢視:C 指標),則需要使用fixed關鍵字來固定指標。

以下示例演示了這一點:

using System;

namespace UnsafeCodeApplication {
   
   class TestPointer {
      
      public unsafe static void Main() {
         int[]  list = {10, 100, 200};
         fixed(int *ptr = list)
         
         /* let us have array address in pointer */
         for ( int i = 0; i < 3; i++) {
            Console.WriteLine("Address of list[{0}]={1}",i,(int)(ptr + i));
            Console.WriteLine("Value of list[{0}]={1}", i, *(ptr + i));
         }
         
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果 -

Address of list[0] = 31627168
Value of list[0] = 10
Address of list[1] = 31627172
Value of list[1] = 100
Address of list[2] = 31627176
Value of list[2] = 200

編譯不安全程式碼

要編譯不安全程式碼,您必須使用命令列編譯器指定/unsafe命令列開關。

例如,要從命令列編譯名為 prog1.cs 的包含不安全程式碼的程式,請使用以下命令 -

csc /unsafe prog1.cs

如果您使用的是 Visual Studio IDE,則需要在專案屬性中啟用不安全程式碼的使用。

為此 -

  • 透過雙擊解決方案資源管理器中的屬性節點開啟專案屬性

  • 點選生成選項卡。

  • 選擇“允許不安全程式碼”選項。

C# - 多執行緒

執行緒定義為程式的執行路徑。每個執行緒定義一個唯一的控制流。如果您的應用程式涉及複雜且耗時的操作,則設定不同的執行路徑或執行緒通常很有幫助,每個執行緒執行特定的作業。

執行緒是輕量級程序。執行緒的一個常見示例是現代作業系統併發程式設計的實現。使用執行緒可以節省 CPU 週期的浪費並提高應用程式的效率。

到目前為止,我們編寫的程式只有一個執行緒作為一個程序執行,該程序是應用程式的執行例項。但是,這樣應用程式一次只能執行一項作業。為了使其一次執行多個任務,可以將其劃分為更小的執行緒。

執行緒生命週期

執行緒的生命週期從建立 System.Threading.Thread 類的物件開始,並在執行緒終止或完成執行時結束。

以下是執行緒生命週期中的各種狀態 -

  • 未啟動狀態 - 當建立執行緒例項但未呼叫 Start 方法時的情況。

  • 就緒狀態 - 當執行緒準備執行並等待 CPU 週期時的情況。

  • 不可執行狀態 - 當執行緒不可執行時,

    • 已呼叫 Sleep 方法
    • 已呼叫 Wait 方法
    • 被 I/O 操作阻塞
  • 死亡狀態 - 當執行緒完成執行或被中止時的情況。

主執行緒

在 C# 中,System.Threading.Thread類用於處理執行緒。它允許在多執行緒應用程式中建立和訪問各個執行緒。在程序中第一個執行的執行緒稱為執行緒。

當 C# 程式開始執行時,會自動建立主執行緒。使用Thread類建立的執行緒稱為主執行緒的子執行緒。您可以使用 Thread 類的CurrentThread屬性訪問執行緒。

以下程式演示了主執行緒執行 -

using System;
using System.Threading;

namespace MultithreadingApplication {
   
   class MainThreadProgram {
      
      static void Main(string[] args) {
         Thread th = Thread.CurrentThread;
         th.Name = "MainThread";
         Console.WriteLine("This is {0}", th.Name);
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

This is MainThread

Thread 類的屬性和方法

下表顯示了Thread類的一些最常用的屬性 -

序號 屬性和描述
1

CurrentContext

獲取執行緒正在執行的當前上下文。

2

CurrentCulture

獲取或設定當前執行緒的區域性。

3

CurrentPrinciple

獲取或設定執行緒的當前主體(用於基於角色的安全)。

4

CurrentThread

獲取當前正在執行的執行緒。

5

CurrentUICulture

獲取或設定 Resource Manager 在執行時查詢特定於區域性的資源時使用的當前區域性。

6

ExecutionContext

獲取一個 ExecutionContext 物件,其中包含有關當前執行緒的各種上下文的資訊。

7

IsAlive

獲取一個值,指示當前執行緒的執行狀態。

8

IsBackground

獲取或設定一個值,指示執行緒是否為後臺執行緒。

9

IsThreadPoolThread

獲取一個值,指示執行緒是否屬於託管執行緒池。

10

ManagedThreadId

獲取當前託管執行緒的唯一識別符號。

11

Name

獲取或設定執行緒的名稱。

12

Priority

獲取或設定一個值,指示執行緒的排程優先順序。

13

ThreadState

獲取一個包含當前執行緒狀態的值。

下表顯示了Thread類的一些最常用的方法 -

序號 方法和描述
1

public void Abort()

在呼叫它的執行緒中引發 ThreadAbortException,以開始終止執行緒的過程。呼叫此方法通常會終止執行緒。

2

public static LocalDataStoreSlot AllocateDataSlot()

在所有執行緒上分配一個未命名的儲存槽。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

3

public static LocalDataStoreSlot AllocateNamedDataSlot(string name)

在所有執行緒上分配一個命名儲存槽。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

4

public static void BeginCriticalRegion()

通知主機執行即將進入程式碼區域,在此區域中,執行緒中止或未處理異常的影響可能會危及應用程式域中的其他任務。

5

public static void BeginThreadAffinity()

通知主機託管程式碼即將執行依賴於當前物理作業系統執行緒身份的指令。

6

public static void EndCriticalRegion()

通知主機執行即將進入程式碼區域,在此區域中,執行緒中止或未處理異常的影響僅限於當前任務。

7

public static void EndThreadAffinity()

通知主機託管程式碼已完成執行依賴於當前物理作業系統執行緒身份的指令。

8

public static void FreeNamedDataSlot(string name)

消除程序中所有執行緒的名稱和插槽之間的關聯。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

9

public static Object GetData(LocalDataStoreSlot slot)

檢索當前執行緒當前域中指定插槽中的值。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

10

public static AppDomain GetDomain()

返回當前執行緒正在執行的當前域。

11

public static AppDomain GetDomainID()

返回唯一的應用程式域識別符號。

12

public static LocalDataStoreSlot GetNamedDataSlot(string name)

查詢命名資料插槽。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

13

public void Interrupt()

中斷處於 WaitSleepJoin 執行緒狀態的執行緒。

14

public void Join()

阻塞呼叫執行緒,直到執行緒終止,同時繼續執行標準 COM 和 SendMessage 訊息迴圈。此方法具有不同的過載形式。

15

public static void MemoryBarrier()

同步記憶體訪問,如下所示:執行當前執行緒的處理器不能重新排序指令,以使在呼叫 MemoryBarrier 之前的記憶體訪問在呼叫 MemoryBarrier 之後的記憶體訪問之後執行。

16

public static void ResetAbort()

取消對當前執行緒請求的 Abort。

17

public static void SetData(LocalDataStoreSlot slot, Object data)

在當前執行執行緒的指定插槽中設定資料,用於該執行緒的當前域。為了獲得更好的效能,請改用標記有 ThreadStaticAttribute 屬性的欄位。

18

public void Start()

啟動一個執行緒。

19

public static void Sleep(int millisecondsTimeout)

使執行緒暫停一段時間。

20

public static void SpinWait(int iterations)

使執行緒等待由 iterations 引數定義的次數。

21

public static byte VolatileRead(ref byte address)

public static double VolatileRead(ref double address)

public static int VolatileRead(ref int address)

public static Object VolatileRead(ref Object address)

讀取欄位的值。該值是計算機中任何處理器最新寫入的值,無論處理器數量或處理器快取狀態如何。此方法具有不同的過載形式。上面只給出了一些。

22

public static void VolatileWrite(ref byte address,byte value)

public static void VolatileWrite(ref double address, double value)

public static void VolatileWrite(ref int address, int value)

public static void VolatileWrite(ref Object address, Object value)

立即將值寫入欄位,以便計算機中的所有處理器都能看到該值。此方法具有不同的過載形式。上面只給出了一些。

23

public static bool Yield()

導致呼叫執行緒將執行權讓給另一個已準備好線上程的當前處理器上執行的執行緒。作業系統選擇要讓出的執行緒。

建立執行緒

透過擴充套件 Thread 類來建立執行緒。擴充套件的 Thread 類然後呼叫 **Start()** 方法來開始子執行緒執行。

以下程式演示了這個概念:

using System;
using System.Threading;

namespace MultithreadingApplication {
   
   class ThreadCreationProgram {
      
      public static void CallToChildThread() {
         Console.WriteLine("Child thread starts");
      }
      
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         Thread childThread = new Thread(childref);
         childThread.Start();
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

In Main: Creating the Child thread
Child thread starts

管理執行緒

Thread 類提供了各種管理執行緒的方法。

以下示例演示了使用 **sleep()** 方法使執行緒暫停特定時間段。

using System;
using System.Threading;

namespace MultithreadingApplication {
   
   class ThreadCreationProgram {
      
      public static void CallToChildThread() {
         Console.WriteLine("Child thread starts");
         
         // the thread is paused for 5000 milliseconds
         int sleepfor = 5000; 
         
         Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000);
         Thread.Sleep(sleepfor);
         Console.WriteLine("Child thread resumes");
      }
      
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         Thread childThread = new Thread(childref);
         childThread.Start();
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

In Main: Creating the Child thread
Child thread starts
Child Thread Paused for 5 seconds
Child thread resumes

銷燬執行緒

**Abort()** 方法用於銷燬執行緒。

執行時透過丟擲 **ThreadAbortException** 來中止執行緒。此異常無法捕獲,控制權將傳送到 finally 塊(如果存在)。

以下程式說明了這一點:

using System;
using System.Threading;

namespace MultithreadingApplication {
   
   class ThreadCreationProgram {
      
      public static void CallToChildThread() {
         
         try {
            Console.WriteLine("Child thread starts");
            
            // do some work, like counting to 10
            for (int counter = 0; counter <= 10; counter++) {
               Thread.Sleep(500);
               Console.WriteLine(counter);
            }
            
            Console.WriteLine("Child Thread Completed");
         } catch (ThreadAbortException e) {
            Console.WriteLine("Thread Abort Exception");
         } finally {
            Console.WriteLine("Couldn't catch the Thread Exception");
         }
      }
      
      static void Main(string[] args) {
         ThreadStart childref = new ThreadStart(CallToChildThread);
         Console.WriteLine("In Main: Creating the Child thread");
         Thread childThread = new Thread(childref);
         childThread.Start();
         
         //stop the main thread for some time
         Thread.Sleep(2000);
         
         //now abort the child
         Console.WriteLine("In Main: Aborting the Child thread");
         
         childThread.Abort();
         Console.ReadKey();
      }
   }
}

當以上程式碼編譯並執行時,會產生以下結果:

In Main: Creating the Child thread
Child thread starts
0
1
2
In Main: Aborting the Child thread
Thread Abort Exception
Couldn't catch the Thread Exception 
廣告