C#知識結(jié)構(gòu)
對于一個工作多年的程序員而言,接口、反射、索引器、事件、委托這些耳熟能詳?shù)脑~匯,提起來別說多簡單了,但是讓老司機(jī)坐在那一個人拿起一支筆,把腦海中對C#知識結(jié)構(gòu)進(jìn)行梳理一下,大抵是寫不了多內(nèi)容的,原因是什么呢,是遺忘?當(dāng)然不是,每天面對代碼的老司機(jī)當(dāng)然不會遺忘。
根本的原因是知識沒有網(wǎng)格化。
知識結(jié)構(gòu)網(wǎng)格化,對于現(xiàn)在頻繁變化的技術(shù)格局來說,是勢在必行且可以安身立命的根本
這個方法不僅使用編程菜鳥,同樣使用編程老司機(jī)。
接下來我們進(jìn)入正題,先來看一副圖
原圖,可通過本人的Github下載,連接如下
https://github.com/yuyue5945/Blog
在導(dǎo)圖上列出如下幾個基礎(chǔ)的問題,這幾個基礎(chǔ)問題,會一直伴隨著我們的編程生涯
什么是類?
什么是對象?
關(guān)于類與對象的關(guān)系
類和接口之間的關(guān)系
什么是封裝,繼承,多態(tài)
接下來,我們就走進(jìn)C#基礎(chǔ)結(jié)構(gòu)的世界,從新梳理一下我們的知識結(jié)構(gòu)吧
- C#知識結(jié)構(gòu)
- 基礎(chǔ)概念
- 高級應(yīng)用
基礎(chǔ)概念
類-繼承、封裝、多態(tài)
在 C# 語言中創(chuàng)建的任何項(xiàng)目都有類的存在,通過類能很好地體現(xiàn)面向?qū)ο笳Z言中封裝、繼承、多態(tài)的特性。本節(jié)將講解 C# 中的類和定義類方式。
在前面的學(xué)習(xí)中已經(jīng)多次使用過類,類定義的語法形式并不復(fù)雜,請記住 class 關(guān)鍵字,它是定義類的關(guān)鍵字。
類定義的具體語法形式如下。
類的訪問修飾符 修飾符 類名
{
類的成員
}
其中:
類的訪問修飾符:用于設(shè)定對類的訪問限制,包括 public、internal 或者不寫,用 internal 或者不寫時代表只能在當(dāng)前項(xiàng)目中訪問類;public 則代表可以在任何項(xiàng)目中訪問類。
修飾符:修飾符是對類本身特點(diǎn)的描述,包括 abstract、sealed 和 static。abstract 是抽象的意思,使用它修飾符的類不能被實(shí)例化;sealed 修飾的類是密封類,不能 被繼承;static 修飾的類是靜態(tài)類,不能被實(shí)例化。
類名:類名用于描述類的功能,因此在定義類名時最好是具有實(shí)際意義,這樣方便用戶理解類中描述的內(nèi)容。在同一個命名空間下類名必須是唯一的。
類的成員:在類中能定義的元素,主要包括字段、屬性、方法。
封裝
封裝涉及到類定義、類聲明、類成員
類成員包括字段、屬性、方法、事件。
函數(shù)成員包括構(gòu)造器、析構(gòu)器、屬性、方法體、方法頭等
繼承
繼承是面向?qū)ο蟪绦蛟O(shè)計(jì)中最重要的概念之一。繼承允許我們根據(jù)一個類來定義另一個類,這使得創(chuàng)建和維護(hù)應(yīng)用程序變得更容易。同時也有利于重用代碼和節(jié)省開發(fā)時間。
當(dāng)創(chuàng)建一個類時,程序員不需要完全重新編寫新的數(shù)據(jù)成員和成員函數(shù),只需要設(shè)計(jì)一個新的類,繼承了已有的類的成員即可。這個已有的類被稱為的基類,這個新的類被稱為派生類。
繼承的思想實(shí)現(xiàn)了 屬于(IS-A) 關(guān)系。例如,哺乳動物 屬于(IS-A) 動物,狗 屬于(IS-A) 哺乳動物,因此狗 屬于(IS-A) 動物。
基類和派生類
一個類可以派生自多個類或接口,這意味著它可以從多個基類或接口繼承數(shù)據(jù)和函數(shù)。
類定義關(guān)鍵字
繼承講的是基類和派生類,存在單繼承、多重繼承兩種情況
基類在派生類初始化之前自動進(jìn)行初始化
構(gòu)造函數(shù)和析構(gòu)函數(shù)不能被繼承
若類被標(biāo)注了abstract則強(qiáng)制派生類覆蓋基類的方法,目的為了對外提供統(tǒng)一的方法簽名。
若類被標(biāo)注了sealed則說明該類偽密封類,不可被繼承
若類被標(biāo)注了partial則說明該類被分割在幾個文件中
類調(diào)用關(guān)鍵字
Base表示調(diào)用基類方法、或者被重寫的方法
New 表示重寫同名法法、隱藏父類方法
Override表示 重載父類方法
還存在組合方法的形式,不過不推薦
override--virtual
new--virtual
C# 中創(chuàng)建派生類的語法如下:
<訪問修飾符符> class <基類>
{
...
}
class <派生類> : <基類>
{
...
}
假設(shè),有一個基類 Shape,它的派生類是 Rectangle:
實(shí)例
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;
}
// 派生類
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);
// 打印對象的面積
Console.WriteLine("總面積: {0}", Rect.getArea());
Console.ReadKey();
}
}
}
當(dāng)上面的代碼被編譯和執(zhí)行時,它會產(chǎn)生下列結(jié)果:
總面積: 35
動態(tài)
方法重載就是一種多態(tài)
接口(Interface)
接口定義了所有類繼承接口時應(yīng)遵循的語法合同。接口定義了語法合同 "是什么" 部分,派生類定義了語法合同 "怎么做" 部分。
接口定義了屬性、方法和事件,這些都是接口的成員。接口只包含了成員的聲明。成員的定義是派生類的責(zé)任。接口提供了派生類應(yīng)遵循的標(biāo)準(zhǔn)結(jié)構(gòu)。
接口使得實(shí)現(xiàn)接口的類或結(jié)構(gòu)在形式上保持一致。
抽象類在某種程度上與接口類似,但是,它們大多只是用在當(dāng)只有少數(shù)方法由基類聲明由派生類實(shí)現(xiàn)時。
定義接口: MyInterface.cs
接口使用 interface 關(guān)鍵字聲明,它與類的聲明類似。接口聲明默認(rèn)是 public 的。下面是一個接口聲明的實(shí)例:
interface IMyInterface
{
void MethodToImplement();
}
以上代碼定義了接口 IMyInterface。通常接口命令以 I 字母開頭,這個接口只有一個方法 MethodToImplement(),沒有參數(shù)和返回值,當(dāng)然我們可以按照需求設(shè)置參數(shù)和返回值。
值得注意的是,該方法并沒有具體的實(shí)現(xiàn)。
接下來我們來實(shí)現(xiàn)以上接口:InterfaceImplementer.cs
實(shí)例
using System;
interface IMyInterface
{
// 接口成員
void MethodToImplement();
}
class InterfaceImplementer : IMyInterface
{
static void Main()
{
InterfaceImplementer iImp = new InterfaceImplementer();
iImp.MethodToImplement();
}
public void MethodToImplement()
{
Console.WriteLine("MethodToImplement() called.");
}
}
InterfaceImplementer 類實(shí)現(xiàn)了 IMyInterface 接口,接口的實(shí)現(xiàn)與類的繼承語法格式類似:
class InterfaceImplementer : IMyInterface
繼承接口后,我們需要實(shí)現(xiàn)接口的方法 MethodToImplement() , 方法名必須與接口定義的方法名一致。
命名空間(Namespace)
命名空間的設(shè)計(jì)目的是提供一種讓一組名稱與其他名稱分隔開的方式。在一個命名空間中聲明的類的名稱與另一個命名空間中聲明的相同的類的名稱不沖突。
我們舉一個計(jì)算機(jī)系統(tǒng)中的例子,一個文件夾(目錄)中可以包含多個文件夾,每個文件夾中不能有相同的文件名,但不同文件夾中的文件可以重名。
定義命名空間
命名空間的定義是以關(guān)鍵字 namespace 開始,后跟命名空間的名稱,如下所示:
namespace namespace_name
{
// 代碼聲明
}
為了調(diào)用支持命名空間版本的函數(shù)或變量,會把命名空間的名稱置于前面,如下所示:
namespace_name.item_name;
下面的程序演示了命名空間的用法:
實(shí)例
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();
}
}
當(dāng)上面的代碼被編譯和執(zhí)行時,它會產(chǎn)生下列結(jié)果:
Inside first_space
Inside second_space
using 關(guān)鍵字
using 關(guān)鍵字表明程序使用的是給定命名空間中的名稱。例如,我們在程序中使用 System 命名空間,其中定義了類 Console。我們可以只寫:
Console.WriteLine ("Hello there");
我們可以寫完全限定名稱,如下:
System.Console.WriteLine("Hello there");
高級應(yīng)用
特性(Attribute)
特性(Attribute)是用于在運(yùn)行時傳遞程序中各種元素(比如類、方法、結(jié)構(gòu)、枚舉、組件等)的行為信息的聲明性標(biāo)簽。您可以通過使用特性向程序添加聲明性信息。一個聲明性標(biāo)簽是通過放置在它所應(yīng)用的元素前面的方括號([ ])來描述的。
特性(Attribute)用于添加元數(shù)據(jù),如編譯器指令和注釋、描述、方法、類等其他信息。.Net 框架提供了兩種類型的特性:預(yù)定義特性和自定義特性。
規(guī)定特性(Attribute)
規(guī)定特性(Attribute)的語法如下:
[attribute(positional_parameters, name_parameter = value, ...)]
element
特性(Attribute)的名稱和值是在方括號內(nèi)規(guī)定的,放置在它所應(yīng)用的元素之前。positional_parameters 規(guī)定必需的信息,name_parameter 規(guī)定可選的信息。
預(yù)定義特性(Attribute)
.Net 框架提供了三種預(yù)定義特性:
- AttributeUsage
- Conditional
- Obsolete
反射(Reflection)
反射指程序可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力。
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。
您可以使用反射動態(tài)地創(chuàng)建類型的實(shí)例,將類型綁定到現(xiàn)有對象,或從現(xiàn)有對象中獲取類型。然后,可以調(diào)用類型的方法或訪問其字段和屬性。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
1、反射提高了程序的靈活性和擴(kuò)展性。
2、降低耦合性,提高自適應(yīng)能力。
3、它允許程序創(chuàng)建和控制任何類的對象,無需提前硬編碼目標(biāo)類。
缺點(diǎn):
1、性能問題:使用反射基本上是一種解釋操作,用于字段和方法接入時要遠(yuǎn)慢于直接代碼。因此反射機(jī)制主要應(yīng)用在對靈活性和拓展性要求很高的系統(tǒng)框架上,普通程序不建議使用。
2、使用反射會模糊程序內(nèi)部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術(shù),因而會帶來維護(hù)的問題,反射代碼比相應(yīng)的直接代碼更復(fù)雜。
反射(Reflection)的用途
反射(Reflection)有下列用途:
- 它允許在運(yùn)行時查看特性(attribute)信息。
- 它允許審查集合中的各種類型,以及實(shí)例化這些類型。
- 它允許延遲綁定的方法和屬性(property)。
- 它允許在運(yùn)行時創(chuàng)建新類型,然后使用這些類型執(zhí)行一些任務(wù)。
屬性(Property)
屬性(Property) 是類(class)、結(jié)構(gòu)(structure)和接口(interface)的命名(named)成員。類或結(jié)構(gòu)中的成員變量或方法稱為 域(Field)。屬性(Property)是域(Field)的擴(kuò)展,且可使用相同的語法來訪問。它們使用 訪問器(accessors) 讓私有域的值可被讀寫或操作。
屬性(Property)不會確定存儲位置。相反,它們具有可讀寫或計(jì)算它們值的 訪問器(accessors)。
例如,有一個名為 Student 的類,帶有 age、name 和 code 的私有域。我們不能在類的范圍以外直接訪問這些域,但是我們可以擁有訪問這些私有域的屬性。
訪問器(Accessors)
屬性(Property)的訪問器(accessor)包含有助于獲?。ㄗx取或計(jì)算)或設(shè)置(寫入)屬性的可執(zhí)行語句。訪問器(accessor)聲明可包含一個 get 訪問器、一個 set 訪問器,或者同時包含二者。例如:
// 聲明類型為 string 的 Code 屬性
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// 聲明類型為 string 的 Name 屬性
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 聲明類型為 int 的 Age 屬性
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
索引器(Indexer)
索引器(Indexer) 允許一個對象可以像數(shù)組一樣使用下標(biāo)的方式來訪問。
當(dāng)您為類定義一個索引器時,該類的行為就會像一個 虛擬數(shù)組(virtual array) 一樣。您可以使用數(shù)組訪問運(yùn)算符 [ ] 來訪問該類的的成員。
語法
一維索引器的語法如下:
element-type this[int index]
{
// get 訪問器
get
{
// 返回 index 指定的值
}
// set 訪問器
set
{
// 設(shè)置 index 指定的值
}
}
索引器(Indexer)的用途
索引器的行為的聲明在某種程度上類似于屬性(property)。就像屬性(property),您可使用 get 和 set 訪問器來定義索引器。但是,屬性返回或設(shè)置一個特定的數(shù)據(jù)成員,而索引器返回或設(shè)置對象實(shí)例的一個特定值。換句話說,它把實(shí)例數(shù)據(jù)分為更小的部分,并索引每個部分,獲取或設(shè)置每個部分。
定義一個屬性(property)包括提供屬性名稱。索引器定義的時候不帶有名稱,但帶有 this 關(guān)鍵字,它指向?qū)ο髮?shí)例。
委托(Delegate)
C# 中的委托(Delegate)類似于 C 或 C++ 中函數(shù)的指針。委托(Delegate) 是存有對某個方法的引用的一種引用類型變量。引用可在運(yùn)行時被改變。
委托(Delegate)特別用于實(shí)現(xiàn)事件和回調(diào)方法。所有的委托(Delegate)都派生自 System.Delegate 類。
聲明委托(Delegate)
委托聲明決定了可由該委托引用的方法。委托可指向一個與其具有相同標(biāo)簽的方法。
例如,假設(shè)有一個委托:
public delegate int MyDelegate (string s);
上面的委托可被用于引用任何一個帶有一個單一的 string 參數(shù)的方法,并返回一個 int 類型變量。
聲明委托的語法如下:
delegate <return type> <delegate-name> <parameter list>
實(shí)例化委托(Delegate)
一旦聲明了委托類型,委托對象必須使用 new 關(guān)鍵字來創(chuàng)建,且與一個特定的方法有關(guān)。當(dāng)創(chuàng)建委托時,傳遞到 new 語句的參數(shù)就像方法調(diào)用一樣書寫,但是不帶有參數(shù)。例如:
public delegate void printString(string s);
...
printString ps1 = new printString(WriteToScreen);
printString ps2 = new printString(WriteToFile);
事件(Event)
事件(Event) 基本上說是一個用戶操作,如按鍵、點(diǎn)擊、鼠標(biāo)移動等等,或者是一些提示信息,如系統(tǒng)生成的通知。應(yīng)用程序需要在事件發(fā)生時響應(yīng)事件。例如,中斷。
C# 中使用事件機(jī)制實(shí)現(xiàn)線程間的通信。
通過事件使用委托
事件在類中聲明且生成,且通過使用同一個類或其他類中的委托與事件處理程序關(guān)聯(lián)。包含事件的類用于發(fā)布事件。這被稱為 發(fā)布器(publisher) 類。其他接受該事件的類被稱為 訂閱器(subscriber) 類。事件使用 發(fā)布-訂閱(publisher-subscriber) 模型。
發(fā)布器(publisher) 是一個包含事件和委托定義的對象。事件和委托之間的聯(lián)系也定義在這個對象中。發(fā)布器(publisher)類的對象調(diào)用這個事件,并通知其他的對象。
訂閱器(subscriber) 是一個接受事件并提供事件處理程序的對象。在發(fā)布器(publisher)類中的委托調(diào)用訂閱器(subscriber)類中的方法(事件處理程序)。
聲明事件(Event)
在類的內(nèi)部聲明事件,首先必須聲明該事件的委托類型。例如:
public delegate void BoilerLogHandler(string status);
然后,聲明事件本身,使用 event 關(guān)鍵字:
// 基于上面的委托定義事件
public event BoilerLogHandler BoilerEventLog;
集合(Collection)
集合(Collection)類是專門用于數(shù)據(jù)存儲和檢索的類。這些類提供了對棧(stack)、隊(duì)列(queue)、列表(list)和哈希表(hash table)的支持。大多數(shù)集合類實(shí)現(xiàn)了相同的接口。
集合(Collection)類服務(wù)于不同的目的,如為元素動態(tài)分配內(nèi)存,基于索引訪問列表項(xiàng)等等。這些類創(chuàng)建 Object 類的對象的集合。在 C# 中,Object 類是所有數(shù)據(jù)類型的基類。
泛型(Generic)
泛型(Generic) 允許您延遲編寫類或方法中的編程元素的數(shù)據(jù)類型的規(guī)范,直到實(shí)際在程序中使用它的時候。換句話說,泛型允許您編寫一個可以與任何數(shù)據(jù)類型一起工作的類或方法。
您可以通過數(shù)據(jù)類型的替代參數(shù)編寫類或方法的規(guī)范。當(dāng)編譯器遇到類的構(gòu)造函數(shù)或方法的函數(shù)調(diào)用時,它會生成代碼來處理指定的數(shù)據(jù)類型。
泛型(Generic)的特性
使用泛型是一種增強(qiáng)程序功能的技術(shù),具體表現(xiàn)在以下幾個方面:
- 它有助于您最大限度地重用代碼、保護(hù)類型的安全以及提高性能。
- 您可以創(chuàng)建泛型集合類。.NET 框架類庫在 System.Collections.Generic 命名空間中包含了一些新的泛型集合類。您可以使用這些泛型集合類來替代 System.Collections 中的集合類。
- 您可以創(chuàng)建自己的泛型接口、泛型類、泛型方法、泛型事件和泛型委托。
- 您可以對泛型類進(jìn)行約束以訪問特定數(shù)據(jù)類型的方法。
- 關(guān)于泛型數(shù)據(jù)類型中使用的類型的信息可在運(yùn)行時通過使用反射獲取。
匿名方法
我們已經(jīng)提到過,委托是用于引用與其具有相同標(biāo)簽的方法。換句話說,您可以使用委托對象調(diào)用可由委托引用的方法。
匿名方法(Anonymous methods) 提供了一種傳遞代碼塊作為委托參數(shù)的技術(shù)。匿名方法是沒有名稱只有主體的方法。
在匿名方法中您不需要指定返回類型,它是從方法主體內(nèi)的 return 語句推斷的。
編寫匿名方法的語法
匿名方法是通過使用 delegate 關(guān)鍵字創(chuàng)建委托實(shí)例來聲明的。例如:
delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
Console.WriteLine("Anonymous Method: {0}", x);
};
代碼塊 Console.WriteLine("Anonymous Method: {0}", x); 是匿名方法的主體。
委托可以通過匿名方法調(diào)用,也可以通過命名方法調(diào)用,即,通過向委托對象傳遞方法參數(shù)。
注意: 匿名方法的主體后面需要一個 ;
托管與非托管
當(dāng)一個代碼塊使用 unsafe 修飾符標(biāo)記時,C# 允許在函數(shù)中使用指針變量。不安全代碼或非托管代碼是指使用了指針變量的代碼塊。
指針變量
指針 是值為另一個變量的地址的變量,即,內(nèi)存位置的直接地址。就像其他變量或常量,您必須在使用指針存儲其他變量地址之前聲明指針。
指針變量聲明的一般形式為:
type* var-name;
多線程
線程 被定義為程序的執(zhí)行路徑。每個線程都定義了一個獨(dú)特的控制流。如果您的應(yīng)用程序涉及到復(fù)雜的和耗時的操作,那么設(shè)置不同的線程執(zhí)行路徑往往是有益的,每個線程執(zhí)行特定的工作。
線程是輕量級進(jìn)程。一個使用線程的常見實(shí)例是現(xiàn)代操作系統(tǒng)中并行編程的實(shí)現(xiàn)。使用線程節(jié)省了 CPU 周期的浪費(fèi),同時提高了應(yīng)用程序的效率。
到目前為止我們編寫的程序是一個單線程作為應(yīng)用程序的運(yùn)行實(shí)例的單一的過程運(yùn)行的。但是,這樣子應(yīng)用程序同時只能執(zhí)行一個任務(wù)。為了同時執(zhí)行多個任務(wù),它可以被劃分為更小的線程。
線程生命周期
線程生命周期開始于 System.Threading.Thread 類的對象被創(chuàng)建時,結(jié)束于線程被終止或完成執(zhí)行時。
下面列出了線程生命周期中的各種狀態(tài):
- 未啟動狀態(tài):當(dāng)線程實(shí)例被創(chuàng)建但 Start 方法未被調(diào)用時的狀況。
- 就緒狀態(tài):當(dāng)線程準(zhǔn)備好運(yùn)行并等待 CPU 周期時的狀況。
- 不可運(yùn)行狀態(tài):下面的幾種情況下線程是不可運(yùn)行的:
- 已經(jīng)調(diào)用 Sleep 方法
- 已經(jīng)調(diào)用 Wait 方法
- 通過 I/O 操作阻塞
- 死亡狀態(tài):當(dāng)線程已完成執(zhí)行或已中止時的狀況。
主線程
在 C# 中,System.Threading.Thread 類用于線程的工作。它允許創(chuàng)建并訪問多線程應(yīng)用程序中的單個線程。進(jìn)程中第一個被執(zhí)行的線程稱為主線程。
當(dāng) C# 程序開始執(zhí)行時,主線程自動創(chuàng)建。使用 Thread 類創(chuàng)建的線程被主線程的子線程調(diào)用。您可以使用 Thread 類的 CurrentThread 屬性訪問線程。
下面的程序演示了主線程的執(zhí)行:
實(shí)例
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();
}
}
}
當(dāng)上面的代碼被編譯和執(zhí)行時,它會產(chǎn)生下列結(jié)果:
This is MainThread