C# 改善程式的50種方法
本文轉載連線: http://blog.csdn.net/hr541659660/article/details/51556563?locationNum=12&fps=1
目錄(?)[+]
為什麼程式已經可以正常工作了,我們還要改變它們呢?答案就是我們可以讓它們變得更好。我們常常會改變所使用的工具或者語言,因為新的工具或者語言更富生產力。如果固守舊有的習慣,我們將得不到期望的結果。對於C#這種和我們已經熟悉的語言(如C++或Java)有諸多共通之處的新語言,情況更是如此。人們很容易回到舊的習慣中去。當然,這些舊的習慣絕大多數都很好,C#語言的設計者們也確實希望我們能夠利用這些舊習慣下所獲取的知識。但是,為了讓C#和公共語言執行庫(Common Language Runtime,CLR)能夠更好地整合在一起,從而為面向元件的軟體開發提供更好的支援,這些設計者們不可避免地需要新增或者改變某些元素。本章將討論那些在C#中應該改變的舊習慣,以及對應的新的推薦做法。
條款1:使用屬性代替可訪問的資料成員
C#將屬性從其他語言中的一種特殊約定提升成為一種第一等(first-class)的語言特性。如果大家還在型別中定義公有的資料成員,或者還在手工新增get和set方法,請趕快停下來。屬性在使我們可以將資料成員暴露為公有介面的同時,還為我們提供了在面向物件環境中所期望的封裝。在C#中,屬性(property)是這樣一種語言元素:它們在被訪問的時候看起來好像是資料成員,但是它們卻是用方法實現的。
有時候,一些型別成員最好的表示形式就是資料,例如一個客戶的名字、一個點的x/y座標,或者上一年的收入。使用屬性我們可以建立一種特殊的介面——這種介面在行為上像資料訪問,但卻仍能獲得函式的全部好處。客戶程式碼
.NET框架假定我們會使用屬性來表達公有資料成員。事實上,.NET框架中的資料繫結類只支援屬性,而不支援公有資料成員。這些資料繫結類會將物件的屬性關聯到使用者介面控制元件(Web控制元件或者Windows Forms控制元件)上。其資料繫結機制事實上是使用反射來查詢一個型別中具有特定名稱的屬性。例如下面的程式碼:
textBoxCity.DataBindings.Add("Text",address, "City");
便是將textBoxCity控制元件的Text屬性和address物件的City屬性繫結在一起。(有關資料繫結的細節,參見條款38。)如果City是一個公有資料成員,這樣的資料繫結就不能正常工作。.NET框架類庫(Framework Class Library)的設計者們之所以不支援這樣的做法,是因為將資料成員直接暴露給外界不符合面向物件的設計原則。.NET框架類庫這樣的設計策略從某種意義上講也是在推動我們遵循面向物件的設計原則。對於C++和
當然,資料繫結所應用的類一般都要和使用者介面打交道。但這並不意味著屬性只在UI(使用者介面)邏輯中有用武之地。對於其他類和結構,我們也需要使用屬性。隨著時間的推移,新的需求或行為往往會影響原來型別的實現,採用屬性比較容易能夠應對這些變化。例如,我們可能很快就會發現Customer型別不能有一個空的Name。如果我們使用一個公用屬性來實現Name,那麼只需要在一個地方做更改即可:
public classCustomer
{
privatestring _name;
publicstring Name
{
get
{
return _name;
}
set
{
if (( value == null ) ||
( value.Length == 0 ))
throw new ArgumentException( "Name cannot be blank",
"Name" );
_name = value;
}
}
// ……
}
如果使用的是公有資料成員,我們就要尋找並修改所有設定Customer的Name的程式碼,那將花費大量的時間。
另外,由於屬性是採用方法來實現的,因此為它們新增多執行緒支援就更加容易——直接在get和set方法中提供同步資料訪問控制即可:
public stringName
{
get
{
lock( this )
{
return _name;
}
}
set
{
lock( this )
{
_name = value;
}
}
}
既然是採用方法來實現的,那麼屬性也就具有了方法所具有的全部功能。
比如,屬性可以實現為虛屬性:
public classCustomer
{
privatestring _name;
publicvirtual string Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
// 忽略其他實現程式碼。
}
自然,屬性也可以實現為抽象屬性,或者作為介面定義的一部分:
publicinterface INameValuePair
{
objectName
{
get;
}
objectValue
{
get;
set;
}
}
最後,我們還可以藉助屬性的特點來建立const和非const版本的介面:
publicinterface IConstNameValuePair
{
objectName
{
get;
}
objectValue
{
get;
}
}
publicinterface INameValuePair
{
objectValue
{
get;
set;
}
}
// 上述介面的應用:
public classStuff : IConstNameValuePair, INameValuePair
{
privatestring _name;
privateobject _value;
#regionIConstNameValuePair Members
publicobject Name
{
get
{
return _name;
}
}
objectIConstNameValuePair.Value
{
get
{
return _value;
}
}
#endregion
#regionINameValuePair Members
publicobject Value
{
get
{
return _value;
}
set
{
_value = value;
}
}
#endregion
}
屬性在C#中已經成為一項比較完善的、第一等的語言元素。我們可以針對成員函式做的任何事情,對於屬性也同樣適用。畢竟,屬性是對訪問/修改內部資料的方法的一種擴充套件。
我們知道,屬性訪問器在編譯後事實上是兩個分離的方法。在C# 2.0中,我們可以為一個屬性的get訪問器和set訪問器指定不同的訪問修飾符。這使得我們可以更好地控制屬性的可見性。
// 合法的C#2.0程式碼:
public classCustomer
{
privatestring _name;
publicvirtual string Name
{
get
{
return _name;
}
protected set
{
_name = value;
}
}
// 忽略其他實現程式碼。
}
C#的屬性語法擴充套件自簡單的資料欄位。如果型別介面需要包含一些索引資料項,則可以使用一種稱作索引器(indexer)的型別成員。索引器在C#中又稱含參屬性(parameterized property)。這種“使用屬性來返回一個序列中的資料項”的做法對於很多場合非常有用,下面的程式碼展示了這一用法:
public intthis [ int index ]
{
get
{
return _theValues [ index ] ;
}
set
{
_theValues[ index ] = value;
}
}
// 訪問索引器:
int val =MyObject[ i ];
索引器和一般的屬性(即支援單個數據項的屬性)在C#中有同樣的語言支援,它們都用方法實現,我們可以在其內部做任何校驗或者計算工作。索引器也可以為虛索引器,或者抽象索引器。它們可以宣告在介面中,也可以成為只讀索引器或者讀—寫索引器。以數值作為引數的“一維索引器”還可以參與資料繫結。使用非數值的索引器則可以用來定義map或者dictionary等資料結構:
public Addressthis [ string name ]
{
get
{
return _theValues[ name ] ;
}
set
{
_theValues[ name ] = value;
}
}
與C#中的多維陣列類似,我們也可以建立“多維索引器”——其每一維上的引數型別可以相同,也可以不同。
public intthis [ int x, int y ]
{
get
{
return ComputeValue( x, y );
}
}
public intthis[ int x, string name ]
{
get
{
return ComputeValue( x, name );
}
}
注意所有的索引器都使用this關鍵字來宣告。我們不能為索引器指定其他的名稱。因此,在每個型別中,對於同樣的引數列表,我們只能有一個索引器。
屬性顯然是一個好東西,相較於以前的各種訪問方式來講,它的確是一個進步。但是,有些讀者可能會有如下的想法:剛開始先使用資料成員,之後如果需要獲得屬性的好處時,再考慮將資料成員替換為屬性。這種做法聽起來似乎有道理,但實際上是錯的。讓我們來看下面一段程式碼:
// 使用公有資料成員,不推薦這種做法:
public classCustomer
{
publicstring Name;
// 忽略其他實現程式碼。
}
這段程式碼描述了一個Customer類,其內包含一個成員Name。我們可以使用成員訪問符來獲取/設定其Name的值:
string name =customerOne.Name;
customerOne.Name= "This Company, Inc.";
這段程式碼非常簡潔和直觀。有人據此就認為以後如果有需要,再將Customer類的資料成員Name替換為屬性就可以了,而使用Customer型別的程式碼無需做任何改變。這種說法從某種程度上來講是對的。
屬性在被訪問的時候和資料成員看起來沒有什麼差別。這正是C#引入新的屬性語法的一個目標。但屬性畢竟不是資料,訪問屬性和訪問資料產生的是不同的MSIL。前面那個Customer型別的Name欄位在編譯後將產生如下MSIL程式碼:
.field publicstring Name
而訪問該欄位的部分編譯後的MSIL程式碼如下:
ldloc.0
ldfld string NameSpace.Customer::Name
stloc.1
向該欄位儲存資料的部分編譯後的MSIL程式碼如下:
ldloc.0
ldstr "This Company, Inc."
stfld string NameSpace.Customer::Name
大家不必擔憂,我們不會整天圍繞著IL程式碼轉。為了讓大家清楚“在資料成員和屬性之間做改變會打破二進位制相容性”,在這裡展示一下IL程式碼還是很重要的。我們再來看下面的Customer型別實現,這次我們採用了屬性的方案:
public classCustomer
{
privatestring _name;
publicstring Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
// 忽略其他實現程式碼。
}
當我們在C#中訪問Name屬性時,使用的語法和前面訪問欄位的語法一模一樣。
string name =customerOne.Name;
customerOne.Name= "This Company, Inc.";
但是,C#編譯器對於兩段相同的C#程式碼產生的卻是完全不同的MSIL程式碼。我們來看新版Customer型別的Name屬性編譯後的MSIL:
.propertyinstance string Name()
{
.getinstance string NameSpace.Customer::get_Name()
.setinstance void NameSpace.Customer::set_Name(string)
} // 屬性Customer::Name結束。
.method publichidebysig specialname instance string
get_Name() cil managed
{
// 程式碼長度 11 (0xb)
.maxstack 1
.localsinit ([0] string CS$00000003$00000000)
IL_0000: ldarg.0
IL_0001: ldfld stringNameSpace.Customer::_name
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // 方法Customer::get_Name結束。
.method publichidebysig specialname instance void
set_Name(string 'value') cil managed
{
// 程式碼長度 8 (0x8)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldarg.1
IL_0002: stfld stringNameSpace.Customer::_name
IL_0007: ret
} // 方法Customer::set_Name結束。
在將屬性定義從C#程式碼轉換為MSIL的過程中,有兩點需要我們注意:首先,.property指示符定義了屬性的型別,以及實現屬性get訪問器和set訪問器的兩個函式。這兩個函式被標記為hidebysig和specialname。對我們來說,這兩個標記意味著它們所修飾的函式不能直接在C#原始碼中被呼叫,也不被認為是型別正式定義的一部分。要訪問它們,我們只能通過屬性。
當然,大家對於屬性定義產生不同的MSIL應該早有預期。更重要的是,對屬性所做的get和set訪問的客戶程式碼編譯出來的MSIL也不同:
// get
ldloc.0
callvirt instance string NameSpace.Customer::get_Name()
stloc.1
// set
ldloc.0
ldstr "This Company, Inc."
callvirt instance void NameSpace.Customer::set_Name(string)
大家看到了,同樣是訪問客戶(Customer)名稱(Name)的C#原始碼,由於所使用的Name成員不同——屬性或者資料成員,編譯後產生出的MSIL指令也不同。儘管訪問屬性和訪問資料成員使用的是同樣的C#原始碼,但是C#編譯器卻將它們轉換為不同的IL程式碼。
換句話說,雖然屬性和資料成員在原始碼層次上是相容的,但是在二進位制層次上卻不相容。這意味著如果將一個型別的公有資料成員改為公有屬性,那麼我們必須重新編譯所有使用該公有資料成員的C#程式碼。本書第4章“建立二進位制元件”討論了二進位制元件的相關細節,但是在此之前大家要清楚,將一個數據成員改為屬性會破壞二進位制相容性。如果這樣的程式集已經被部署,那麼升級它們的工作將變得非常麻煩。
看了屬性產生的IL程式碼之後,有讀者可能想知道使用屬性和使用資料成員在效能上有什麼差別。雖然使用屬性不會比使用資料成員的程式碼效率更快,但是它也不見得就會比使用資料成員的程式碼慢,因為JIT編譯器會對某些方法呼叫(包括屬性訪問器)進行內聯處理。如果JIT編譯器對屬性訪問器進行了內聯處理,那麼屬性和資料成員的效率將沒有任何差別。即使屬性訪問器沒有被內聯,實際的效率差別相對於函式呼叫的成本來講也是可以忽略不計的。只有在很少的一些情況下,這種差別才值得我們注意。
相關推薦
改善C#程式的50種方法
摘要:為什麼程式已經可以正常工作了,我們還要改變它們呢?答案就是我們可以讓它們變得更好。我們常常會改變所使用的工具或者語言,因為新的工具或者語言更富生產力。如果固守舊有的習慣,我們將得不到期望的結果。對於C#這種和我們已經熟悉的語言(如C++或Java)有諸多共通之處的新語言,情況更是如此。人
C# 改善程式的50種方法
本文轉載連線: http://blog.csdn.net/hr541659660/article/details/51556563?locationNum=12&fps=1 目錄(?)[+] 為什麼程式已經可以正常工作了,我們還要改變它們呢?答
C#調用非托管C++DLL的兩種方法
sso tro medium direction ive 之間 測試工程 win bug C#編寫的代碼屬於跨平臺的托管代碼,C++語言可以編寫托管(managed)和非托管(native)代碼。在C#與C++的混合編程中,經常會使用C#來調用native C++的DL
50種方法優化SQL Server數據庫查詢
commit 很多 ltp 內存不足 方式 else 嚴重 詳細 字段 新的關於數據庫的內容。查詢速度慢的原因很多,常見如下幾種: 1、沒有索引或者沒有用到索引(這是查詢慢最常見的問題,是程序設計的缺陷) 2、I/O吞吐量小,形成了瓶頸效應。 3、沒有創建計算列導致
PHP程序性能優化的50種方法
裝配 參數 c++ 例如 request glob pro 編譯 釋放內存 用單引號代替雙引號來包含字符串,這樣做會更快一些。因為 PHP 會在雙引號包圍的 字符串中搜尋變量,單引號則不會,註意:只有 echo 能這麽做,它是一種可以把多個字符 串當作參數的&ldquo
C++工作筆記-3種方法對資料型別進行拆分(可用於各種協議)
比如用Long Long存3個數據的內容。 這裡要知道大小端的知識點。 方法一是用位運算; 方法二是用指標; 方法三是結構體(本質上也是指標); 執行截圖如下: 原始碼如下: main.cpp #include <iostream> using
【LeetCode】11. Container With Most Water(盛最多水的容器)-C++實現的三種方法
本題是Bloomberg的面試題。 問題描述: 一、第一種方法-暴力解法 當我們在面試時想不到解題的方法時,不妨使用暴力解法,雙重遍歷陣列。 當 i = 0 時,使用指標 j 遍歷陣列,找到第一輪的最大值 area: 當i = 2 ,使用指標 j 遍歷
【LeetCode】1. Two Sum(兩數之和)-C++實現的兩種方法
本題是一下公司的面試題: 問題描述: 問題求解: 使用無序容器unorder_map實現: #include <iostream> #include <vector> #include <cassert> #inclu
Effective C++ 改善程式與設計的55個做法,總結筆記(上)
前言 最近在看《Effective C++》這本書,這部落格相當於是個濃縮版的總結吧。 在這裡你可以大致遊覽下在 C++ 開發中前人給了我們哪些建議,有機會我覺得最好還是可以看看原書,因為裡面會有不少具體的例子告訴你為什麼這麼做以及這麼做的好處。 一、讓自己習慣
Effective C++ 改善程式與設計的55個做法,總結筆記(下)
前言 六、繼承和麵向物件設計 32. 確定你的 public 繼承塑模出 is-a 關係 繼承是 is-a 關係,指 “是一個”,即父類的每條屬性和方法都應該適用於子類。 33. 避免遮掩繼承而來的名稱 對於變數和函式,子類的名稱會遮掩父類的名稱,即使函式是
C# WinForm程式退出的方法
1.this.Close(); 只是關閉當前視窗,若不是主窗體的話,是無法退出程式的,另外若有託管執行緒(非主執行緒),也無法乾淨地退出; 2.Application.Exit(); 強制所有訊息中止,退出所有的窗體,但是若有託管執行緒(非主執行緒),也無法乾淨地
PAT (Basic Level) Practice (中文) 1037 在霍格沃茨找零錢 (20 分)(C++)(兩種方法)
1037 在霍格沃茨找零錢 (20 分) 如果你是哈利·波特迷,你會知道魔法世界有它自己的貨幣系統 —— 就如海格告訴哈利的:“十七個銀西可(Sickle)兌一個加隆(Galleon),二十九個納特(Knut)兌一個西可,很容易。”現在,給定哈利應付的價錢 P 和他實付的錢 A,你的
c語言:2種方法;求兩個整數之中的較大者
方法一:程式:#include<stdio.h>int main(){int x,y,z; scanf ("%d %d",&x,&y);if(x>y){z=x;}el
Linux Ubuntu 下編譯Opencv c++專案的幾種方法
Table of Contents 4.瞭解 1.使用g++命令列 pkg-config引數方法 新建一個cpp檔案:main.cpp,功能是輸入一幅影象檔案的路徑並顯示該影象: #include<opencv2/opencv.hpp>
C# WinForm程式退出的方法(筆記)
1.this.Close(); 只是關閉當前視窗,若不是主窗體的話,是無法退出程式的,另外若有託管執行緒(非主執行緒),也無法乾淨地退出; 2.Application.Exit(); 強制所有訊息中止,退出所有的窗體,但是若有託管執行緒(非主執行緒),也無法乾淨地退出; 3.Application
C++ 分割字串兩種方法
字串切割的使用頻率還是挺高的,string本身沒有提供切割的方法,但可以使用stl提供的封裝進行實現或者通過c函式strtok()函式實現。1、通過stl實現涉及到string類的兩個函式find和substr: 1、find函式 原型:size_t find ( const
C#呼叫非託管C++DLL的兩種方法
C#編寫的程式碼屬於跨平臺的託管程式碼,C++語言可以編寫託管(managed)和非託管(native)程式碼。在C#與C++的混合程式設計中,經常會使用C#來呼叫native C++的DL
C#讀取Excel幾種方法的體會
(1) OleDb: 用這種方法讀取Excel速度還是非常的快的,但這種方式讀取資料的時候不太靈活,不過可以在 DataTable 中對資料進行一些刪減修改 這種方式將Excel作為一個數據源,直接用Sql語句獲取資料了。所以讀取之前要知道此次要讀取的Sheet(當然也可以用序號,類似dt.Row[0]
C#呼叫C++ dll的兩種方法
靜態呼叫 [DllImport(@"xxx.dll", EntryPoint = "TestMethod")] static extern string TestM
Python C/S 網路程式設計(一)之 三種方法實現天氣預報小程式
1. 首先明白下協議棧和庫的概念: 協議棧(Protocol Stack): 是指網路中各層協議的總和,其形象的反映了一個網路中檔案傳輸的過程:由上層協議到底層協議,再由底層協議到上層協議。 庫(Library):主要用來解析要使用的網路通訊協議,包含Python內建標準庫