熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

微軟 .NET 在新的平台上編程

2022-06-13   來源: .NET編程 

  本文以及以後的系列文章將專門針對NET討論各種編程問題我將假設你已經熟悉面向對象的編程概念每一篇文章的內容都聚焦在非選定的特定公共語言運行時編程主題上所有NET開發人員必須知道這些主題

  當展示代碼例子時我必須在支持NET CLR 的多種語言中選擇一種我決定使用C#它是微軟設計的一種新語言

  真正的面向對象設計

  對於使用Win SDK的編程人員來說對大多數操作系統特性的訪問時通過一組從動態鏈接庫輸出的獨立函數實現的這些獨立的函數從諸如C這樣的非面向對象語言中非常容易調用但對於一個新的開發人員來說要面對上千個表面上看來毫無關系的獨立的函數是相當讓人畏懼的更為困難的是許多函數名是以單詞Get開始的(如GetCurrentProcess和GetStockObject)此外Win API已經歷數年並且微軟添加了新的函數這些新函數依舊的函數相比有相似的語義但提供的特性有些差異你常常能認出較新的函數因為它們的名字原來的函數名相似(象CreateWindow/CreateWindowExCreateTypeLib/CreateTypeLib以及我最喜歡的CreatePen/CreatePenIndirect/ExtCreatePen

  所有這些問題都使程序員覺得Windows開發很難隨著NET平台的出現溫柔終於為叫苦不迭的開發人員提供了一個完全面向對象的開發平台平台服務現在被分成為單獨的名字空間(如SystemCollectionsSystemDataSystemIOSystemSecuritySystemWeb等等)並且每一個名字空間包含一組允許訪問平台服務的相關類

  因為類方法可以重載行為差別不大的方法具有相同的名字並且只有從原型中才能看出差別來例如一個類可能提供三個不同版本的CreatePen方法所有方法都做相同的事情即創建一支筆但是每一個方法都有不同的參數集並且行為不太一樣將來微軟還要創建第四個CreatePen方法並且與前面的類方法配合默契

  因為所有的平台服務都通過這種面向對象的方式來實現所以軟件開發者應該對面向對象的編程有所理解面向對象的方法還帶來了其它的一些特點如使用繼承和多態性很容易創建專門版本的基類庫類型我再次強烈建議要熟練掌握這些概念這對於使用微軟的NET框架很重要

  SystemObject

NET中每一個對象都是從SystemObject派生而來也就是說下面的兩種類型定義(使用C#)是相同的

class Jeff { 



}



class Jeff : SystemObject {



}
    因為所有對象都是從SystemObject派生出來的從而可以保證每一個對象具有最小的功能集表一是SystemObject中的公共方法

  公共語言運行時需要所有的對象都要用new操作符創建(調用newobj IL指令)下列代碼示范了如何創建Jeff類型(已在前面聲明)的對象實例

  Jeff j = new Jeff(ConstructorParam

  new操作符根據指定的類型需要從堆中分配字節數來創建對象它初始化對象的開銷成員每一個對象都會有一些公共語言運行時用來管理對象的附加字節如對象的許表指針以及對同步快的引用

  調用類的構造函數時傳遞的參數在new語句中指定(例子中是串ConstructorParam注意大多數語言會編譯構造函數以便它們調用基類構造函數但這在公共語言運行時中是不需要的

  在new實現了所有我所提到的操作後它返回新創建對象的引用在例子代碼中這個引用被存儲在變量j中它的類型是Jeff

  另外new操作符沒有配對操作(delete)即沒有方法顯式地釋放或銷毀對象公共語言運行時提供自動地探測的垃圾回收環境當對象不再被使用或不再被訪問時自動地釋放和銷毀對象有關這個主題將在下次的討論中提出

  數據類型的強制轉換

  在編程過程當中對象從一個數據類型到另一個數據類型的強制類型轉換是十分常見的在這一部分我將討論對象的強制數據類型轉換規則為此先看下列代碼

  SystemObject o = new Jeff(ConstructorParam

  先前的代碼編譯通過並正確執行是因為有一個隱含的強制類型轉換new操作符返回Jeff的一個引用類型但o是一個SystemObject的引用類型因為所有的類型(包括Jeff類型)都能被強制轉換為SystemObject隱含的強制類型轉換是成功的但是如果執行下面的代碼就會有編譯器錯誤因為編譯器不提供基類型到派生類型的強制類型轉換

  Jeff j = o

  為了能通過編譯必須插入如下的顯式強制類型轉換

  Jeff j = (Jeff) o

  現在就可以編譯通過並成功執行

  再來看另外一個例子

  SystemObject o = new SystemObject()

  Jeff j = (Jeff) o

  第一行創建了一個SystemObject類型對象第二行代碼試圖將SystemObject引用類型轉換為Jeff引用類型兩行代碼都能編譯通過但是在執行的時候第二行代碼產生一個InvalidCastException異常如果捕獲不到這個異常將強制應用程序終止

  當第二行代碼執行時公共語言運行時查證o所指的對象就是Jeff類型對象(或任何Jeff派生類型)如果是則公共語言運行時允許強制類型轉換否則如果o所指的對象與Jeff類型無關或是一個Jeff的基類則公共語言運行時會預防這種不安全的強制類型轉換並產生InvalidCastException異常

  C# 使用as操作符提供另一種方法來實現強制類型轉換

Jeff j = new Jeff(); // 創建一個新的Jeff 對象

SystemObject o = j as SystemObject; // 強制轉換 j 為一個SystemObject對象

// 現在o 指Jeff 對象
as操作符試圖強制轉換一個對象為指定的類型但與通常的強制轉換不一樣如果對象的類型強制轉換不成功結果會是nullas操作符決不會擲出異常當引用有毛病的強制類型轉換發生時將產生NullReferenceException異常下列代碼示范了這種情況

SystemObject o = new SystemObject(); //創建一個新的Object 對象

Jeff j = o as Jeff; //強制轉換 o 為一個Jeff對象

// 上面的強制轉換失敗不會有異常擲出而j會被置為null

jToString(); // 訪問j時產生一個NullReferenceException 異常

除了as操作符以外C#還提供一個is操作符它檢查是否一個對象實例與給定的類型兼容並判斷結果是True或是FalseIs操作符不會產生異常

SystemObject o = new SystemObject();

SystemBoolean b = (o is SystemObject); // b 是 True

SystemBoolean b = (o is Jeff); // b 是 False

注意如果對象引用是nullis操作符總是返回False因為得不到對象來檢查其類型

為了肯定你理解了剛才所說的內容假設下列兩各類定義存在

class B {

int x;

}

class D : B {

int x;

}
    現在參見圖二看看哪一行代碼通過編譯並執行成功(ES)哪一行代碼導致編譯器錯誤(CE)哪一行代碼導致公共語言運行時錯誤(RE)

  集合與名字空間

  類型集可以被分組成集合(一個或多個文件集)並且被展開在一個集合中可以只存在單獨的名字空間對應用程序開發人員來說名字空間就像有關聯的類型的邏輯分組例如基本類庫集合包含許多名字空間System名字空間包括Object基類型ByteIntExceptionMath和Delegate之類的核心低級類型而SystemCollections名字空間包括的類型如AarryListBitAarryQueue和Stack

  對於編譯器來說名字空間只不過是名字較長的的類型名以及其唯一性是用句點分隔某些符號名來保證的對於編譯器而言System名字空間中的Object類型只不過是用一個叫做SystemObject的類型來表示同樣SystemCollections名字空間中的Queue類型簡單地用標示符SystemCollectionsQueue來表示

  運行時引擎不知道關於名字空間的任何信息當你訪問一個類型時公共語言運行時只需要知道完整的類型名字以及哪一個集合包含這個類型的定義以便公共語言運行時能正確加載集合從而找到要訪問的類型並處理之

  編程人員通常都想用最簡練的方法來表達算法但用完全限定名引用每一個類類型的話極其麻煩因此許多編程語言提供一條語句來指示編譯器添加各種前綴到類型名直到實現一個匹配當用C#編程時我經常在源代碼的最前面是用下面的語句

  using System

  當我在代碼中引用一個類型時編譯器需要保證這個類型被定義過並且我的代碼要以正確的方式訪問這個類型如果編譯器不能找到指定的類型它試圖將System添加到類型名並檢查產生的類型名字是否與現存的類型名匹配前面的代碼行允許我在代碼中使用Object並且編譯器將自動將名字展開為SystemObject我肯定你能輕松想象這樣省去了多少鍵盤輸入

  當進行類型定義的檢查時編譯器必須知道哪一個集合包含了這個類型以便這個集合的信息和類型信息能被送到結果文件中為了獲得集合信息你必須將定義了任何引用類型的集合傳給編譯器

  正如你所設想的一樣這種設計存在一些潛在的問題為了編程方便你應該避免創建名字沖突的類型但是在某些情況中它完全不可能NET鼓勵組件重用你的應用程序可以利用Microsoft所創建的組件同時你也可以用Richter創建的另一個組件這些公司的組件可能都提供了一個叫做FooBar的類型Microsoft的FooBar所做的事情與Richter的FooBar所做的事情完全不同在這種情況下你無法控制類類型的命名為了引用Microsoft的FooBar你使用MicrosoftFooBar為了引用Richter的FooBar你使用RichterFooBar

  在下列的代碼中對FooBar的引用是不明確的編譯器報告一個錯誤也就罷了但是實際上C#編譯器挑選FooBar類型的一種可能的情況直到運行時你才能發現問題

  using Microsoft;

using Richter;

class MyApp {

method void Hi() {

FooBar f = new FooBar(); // Ambiguous compiler picks

}

}

為了排除這種不明確的引用你必須顯式地告訴編譯器你想創建哪一個FooBar

using Microsoft;

using Richter;

class MyApp {

method void Hi() {

RichterFooBar f = new RichterFooBar(); // 明確引用

}

}

另一種語句形式是允許你為單獨類型創建別名如果你只使用名字空間中的幾種類型並且不想用所有的名字空間類型污染整個名字空間的話這種方法很方便下列代碼示范了另一種解決類型不明確問題的方法

// 定義RichterFooBar 為RichterFooBar的別名

using RichterFooBar = RichterFooBar;

class MyApp {

method void Hi() {

RichterFooBar f = new RichterFooBar(); // 不會出錯

}

}
    這種方法對於消除類型歧義有用但不盡人意的地方仍然存在假設澳大利亞的飛镖(Boomerang)公司(簡稱ABC)和阿拉斯加的船舶(Boat)公司(也簡稱ABC)兩家公司各自創建了一個類型可能兩家公司都創建了叫做ABC的名字空間在名字空間中包含一個叫做BuyProduct的類型任何試圖開發需購買飛镖和船舶應用程序的人將會陷入麻煩除非編程語言提供編程方法來區分兩家公司的集合而不僅僅是兩家公司的名字空間

  不幸的是C#語言只支持名字空間並不提供任何方式來詳細說明集合但實際上碰到這個問題的時候並不多屬於罕見問題如果是設計希望第三方使用的組件類型推薦在一個名字空間中定義類型以便編譯器輕松排除類型問題事實上應該使用公司全名(不是只取首字母)作為最高級名字空間名來降低沖突的可能性你能看到Microsoft使用Microsoft作為名字空間

  在代碼中寫一個名字空間聲明來創建名字空間是一件很簡單的事情就像下面這樣

  namespace CompanyName { // CompanyName

class A { // CompanyNameA

class B { } // CompanyNameAB

}

namespace X { // CompanyNameX

class C { } // CompanyNameXC

}

}

   注意名字空間是隱含的公共類型(public)不能通過任何訪問修飾符改變這一點但可以在內部的名字空間中定義類型(不能在集合外面使用)或者在公共的名字空間中定義類型(能被任何集合訪問)名字空間只表示邏輯上的限制策略可訪問性和包裝是通過將名字空間放入一個集合來完成的

  下一次的討論中我將闡述所有NET編程人員必須掌握的簡單數據類型引用類型和數值類型對於每一個NET程序透徹理解數值類型是非常重要的


From:http://tw.wingwit.com/Article/program/net/201311/12578.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.