顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

2014年1月23日 星期四

[Programming] 傳值(pass by value) / 傳參考(pass by reference)

我們在呼叫一些方法(method)的時候,常常會要傳一些參數。然而,傳參數的方法不一樣,對記憶體的操作就不一樣,出來的結果也會有很大的機率是意料之外的不一樣。
.NET所提供傳參數的方式有兩種:

  1. 傳值 (passing by value)
  2. 傳參考 (passing by reference)

 Visual Studio 2010裡,傳值和傳參考的變數型態顏色會不一樣。

傳值:

傳值是很簡單的概念:在進入method之前,程式會在記憶體裡多宣告一塊與參數同樣資料型態的記憶體大小,然後把值複製過去
這塊多出來的記憶體就是這個method專屬使用,method跑完之後就會像垃圾一樣始亂終棄地丟掉了。
不過,並不一定在method跑完之後,這塊記憶體就會馬上被釋放掉。.NET的虛擬機器裡有自己的Garbage Collection機制來處理這些記憶體,理論上而言是不用太擔心這些問題。不過很多系統使用時間一久效能就會由等比級數般的速度變差,不曉得和這部分有沒有關係。


傳參考:

要理解傳參考,得先知道這裡的"參考"是什麼玩意兒。這也是一個我們天天都會用到的東西。
基本上,我們在宣告變數的時候就是在弄一個參考。在沒給值之前這個參考就是指到null,有物件或實值給它的時候它才會把自己到該記憶體的位址上。



知道這個以後,傳參考就變成一件很簡單的概念:它就是把參數的參考複製一份起來給method使用,用完就丟掉。
因為參考實際存的是記憶體位址,所以在method裡操作的參數和呼叫前的物件是同一塊記憶體位址。在Method裡對參數有任何value的變化,都代表著該物件的值是徹底的變了,影響範圍是會擴大到上一層的程式去。



在.NET裡,除了像是int, string, decimal....等諸如此類的最基本的原生資料型態是以傳值方式在做之外,所有物件都是傳參考,包括.NET內建的物件。如果沒掌握好,參數傳一傳很容易出現一些意料之外的bug。
所以如果曾經發生過在傳物件的時候到某某方法去之後值就變了的靈異事件,其實是有科學根據的,沒這麼靈異。


--分隔線--

Q:.NET在傳參考的部分有兩種寫法,如下圖。有什麼差別?


A:
第一種傳參考,編譯器會告訴你在傳參數之前要先經過初始化,不允許傳沒有指到實體物件的"空參考"。
第二種傳參考允許傳空參考進去method,但在離開method之前一定要有給值,不然編譯器會不給過。
除此之外,我不知道有沒有其它實質上的不一樣。



Q:傳參考和C/C++的傳址(Call by Address)有何不同?
A:傳參考是把參考複製一個讓method使用;傳址是更直接地把原本存記憶體位址的那個參考直接拿去給method用傳址是更直接地把原本存記憶體位址直接拿去給method用,而沒有多複製一個新的。

--分隔線--


其實這個觀念我講得有點心虛,當初在學指標的時候也沒有學得很好。如果有觀念上的錯誤請不吝指教。


ref:
[1] 深入淺出C#
[2] C# ref/out 關鍵字與傳遞參考型別參數

2013年2月7日 星期四

[ .NET ] 讓DataTable自己過濾資料


前陣子接到一個需求,那個情境整個讓我整個大發火:允許使用者輸入查詢條件,針對已查詢出來的結果再進行查詢

聽起來是一個很好用的功能,如果查出來的資料有30頁分頁,用這個方式來過濾資料確實是對使用者來說相當方便。
真正讓我發火的是:那個頁面是40多種查詢項目共用的,不同的查詢項目,GridView上產生的欄位會不一樣,而聽到這個需求的時候,我還在考慮是要從資料庫重新拉資料,還是只是把已經抓出來的資料藏在什麼地方,要的時候再拿出來。更重要的是,我聽到這個需求的時候,隔天就要驗收,而我還一點頭緒也沒有。
順便宣達一下:在開發功能的時候,真的要有人隨時檢視合約和工作項目之間的關聯,真的不要到驗收前才發現有一些該完成的項目沒列在工作項目裡。

--該冷靜一下的分隔線--

時程的關係,當時已經沒有時間再去改寫DAO,讓使用者在操作介面的時候讓系統重新跟資料庫不斷地拉少量資料,所以決定在頁面上動手腳。
不知道從哪時候開始就有聽說過.NET的伺服器元件裡有一個地方可以讓人寫類似SQL的過濾句,又隱約看過DataTable裡有個Select之類的東西,就打算從這方面開始著手。

查了一下又試了一下,發現有點不太容易使用。
DataTable.Select(string expression)中,Select裡可以塞一段類似SQL裡的WHERE條件句的字串,把DataTable裡的資料再做一次過濾(參考expression的寫法)。壞就壞在他回傳的是DataRow陣列,而且還是這個DataTable自己的DataRow,不是另外Create一塊新的DataRow來放,所以也不能把這些DataRow再加到其它的DataTable裡,我要怎麼顯示在介面上都不太對,這個東西就變成練習用的玩具,不算產出。

後來survey各路名門文獻來尋找靈感,就這麼無意間讓我知道由DataTable.DefaultView回傳回來的一個DataView,裡面除了裝著DataTable的資料之外,官方還說可以進行資料的篩選和排序。

是我要的篩選

於是我就在這個DataView.RowFilter裡加那些原本寫在DataTable.Select裡的expression,再把GridView和DataTable重新Bind一下,就可以得到我要的結果了!

    private void SelectDataTable(string keyword) {
        DataTable dt = (DataTable)ViewState["dtSource"];

        if (keyword == "")
        {
            dt.DefaultView.RowFilter = "";
            ViewState["RowFilter"] = "";
        }
        else
        {
            StringBuilder expWhere = new StringBuilder();
            foreach (DataColumn col in dt.Columns)
            {
                if (col.ColumnName == "SeqID") { continue; }
                if (col.DataType != typeof(string)) { continue; }
                expWhere.Append(string.Format("{0} LIKE '%{1}%' OR ", col.ColumnName, keyword));
            }
            dt.DefaultView.RowFilter = ((ViewState["RowFilter"].ToString() != "") ? ViewState["RowFilter"] + " AND " : "") + "(" + expWhere.ToString().Remove(expWhere.Length - 3, 3) + ")";
            ViewState["RowFilter"] = dt.DefaultView.RowFilter;
        }
        ViewState["dtSource"] = dt;
        SetGridView(dt);
    }



老天保佑,It's work!


下面是畫面圖。為保護當事人已馬賽克(?)處理

圖1. 查出來的資料很多,有8頁

 圖2. 透過RowFilter來過濾資料

圖3. 再濾一次。這個功能會讓關鍵字查詢用AND的方式把前後的查詢一次一次濾掉,不過這裡看不出來....


Ref:

2012年9月27日 星期四

[狗大便] 不容易被發現哪裡出問題的問題 (2) - Int to StringFormat

只要扯到字串,事情就會變得很麻煩。
有學過C就知道,我們看到的字串(string)都是由一組字元(char)陣列組合而成的,而字串最後還有一個'/0'的符號作為字串的結尾。至於要怎麼去操作字串,加大或縮小,複製或貼上,都得用控制陣列的index和動態記憶體配置來實現,而在約耳的書裡也提到,用程式來調配記憶體是最麻煩的一件事。你就知道以前在搞字串的時候有多麻煩。

然而,現在許多高階語言(.NET, Java, ....等)都有字串的原生資料類別,還有武功高強的virtual mechine在OS之上、程式碼之下運作。
那真是一項偉大的發明。不僅減少了開發人員許多麻煩,讓開發應用程式的人越來越不需要考慮太多字元與字元間和記憶體的問題,同時也減少了開發人員靈活動腦的機會(越來越笨囉)


總而言之,這次的問題就是跟字串有關。我相信這雖然似乎在我的部落格中是第一篇和字串有關而且容易debug沒留意到的問題,但不會是最後一篇。因為字串太麻煩了。


--廢話分隔線--


一般人的邏輯裡沒有什麼"整數"和"字串"之間的分別,嘴巴裡講出來的數字能加能減能排序都非常的直覺。但是在寫程式的時候,編輯器可沒這麼聰明。一開始宣告他是哪一種資料型態(data type),它就是該型態,沒給它做特別的動作的話,它就不會變態 (喂)。

今天的問題是什麼呢?反正就是跟資料型態有關。

需求:
我希望在開一張新訂單的時候,訂單編號能夠由系統自己產生,格式為兩位數的西元年份加三位數該年度的訂單數量+1,中間用dash隔開(yy-ccc),如:12-001

聽起來很容易吧?而且需求也非常的明確,應該是不會有什麼大問題?
問題都藏在開發者內心的空隙,也就是粗心啦!

這個簡單的需求分了兩部分:兩位數的西元年份和三位數的訂單數量。我們一個一個來看。

1. 西元年份
.NET的DateTime直接呼叫Now,就會自己回傳本機的系統時間。我們取其中的年(預設為西元年),再取後兩位,就可以得到我們要的部份。
想法很簡單,但是那是聰明如我們才這麼覺得。沒做什麼特別的編程,程式是不會做任何跳躍式的思考的。所以,我們在中間還得再考慮資料型態的問題。

直接用DateTime.Now,取出來的系統時間,資料型態為DateTime。
再拿其中的年,可以得到以int來表示的西元年。
我們要拿西元年的後兩位,必須先把取得的int西元年用ToString()轉成字串以後,再用Substring(2, 2)來取後兩位。
看見沒有?短短一個「兩位數的西元年份」,做了幾次的轉型?串起來的結果就像這樣…

  string strYear = DateTime.Now.Year.ToString().Substring(2, 2);

不過這個問題倒還好。反正中間如果轉型沒轉好,在IDE那裡按下play的時候也不會給過。


2. 訂單數量
訂單數量要從Database裡去得到明確的值,所以現在已假設有一個DAO能回傳正確的當年度訂單數量,我們得到這個值之後再加一,就是我們要的值了。


  string strCount = (dao.getCount() + 1).ToString();


就這麼簡單,就把我們要的搞定了。

事情結束了嗎?



--還沒結束的分隔線--




還沒結束是因為輸出並不如預期。

  string strOutput = strYear + "-" + strCount;

就這樣輸出,結果會變成 12-1, 12-2, 12-3...
關鍵就在於數量被轉成字串的時候沒有特別給他既定的格式,所以直接輸出成沒有補0或其它格式的樣子。

數量被轉成字串之後也來不及再做轉型了。你可能會直覺地去改輸出格式:
  string strOutput = string.Format("{0:00}-{1:000}", strYear, strCount);
輸出結果還是12-1, 12-2, 12-3...
因為字串資料不會因為在format裡面給了哪些pattern而格式特別的變化。


所以,要輸出像是12-001, 12-002這樣的組合字串,要嘛就是在數量被轉成字串之前就先給他pattern
  string strCount = string.Format("0:000", dao.getCount() + 1);
要嘛就是最後輸出的時候再轉成字串。
  int iCount = dao.getCount() + 1;
  string strOutput = string.Format("{0}-{1:000}", strYear, iCount);


這個東西不難,寫過點.NET的學生其實都會。就怕常常一時粗心,這樣的問題卡了三、四百塊

2012年7月3日 星期二

[狗大便] 不容易被發現哪裡出問題的問題 (1) - DateTime

去你媽的這篇文居然在電腦跑很慢的時候被吃掉了,害我又得再重po一次,幹你媽的!

--幹你媽的分隔線--

問題背景:
以下是source code。
這段method的主要動作,是檢查兩個實體物件的內容是否完全相同。
像這種左右對稱的寫法,看起來好像沒什麼問題,常理來說應該可以達到我預期的目的。

但是實際在運作的時候,就是會有時候和預期中的運作結果不一樣。有時候可以很正常,有時候就會錯,搞得全等都不全等了。
那麼,到底是哪裡出了問題?















--解答在此--

資料型態的轉換,中間會有一個讓人頭疼的風險:資料的失真,尤其是失真的過程還讓人不知不覺的時候。

那像這種資料,在什麼情況下會失真?

實驗的結果為:把從C#程式裡取得的DateTime寫入SQL Server的DateTime的過程
DateTime是一種用來表示日期和時間的類別,在寫應用程式的時候十分的泛用。但是各家的定義為何,就不太一定了。

大方向上當然是不會有什麼問題,問題是出在Ticks這種微小精細的東西上。.NET的Ticks定義為從0001-01-01 00:00開始的計算100奈秒的差距量;而SQL Server的Ticks則是1970-01-01 00:00開始。定義上有差,跑出來的數字當然就不一樣。

想當然爾,如果把一個從.NET取得的DateTime存進SQL Server,再原封不動地拿出來和原本的值做全等比較(==),自然就會有讓人感受不出來的差異。 

所以,我把程式稍做修改,世界就和平了~

ref:
[1] http://hi.baidu.com/raybook/blog/item/6702d32a98ce88315343c18b.html
[2] http://seesharper.wordpress.com/2008/07/08/sql-server-datetime-vs-net-datetime-battle-of-accuracy/

2011年8月17日 星期三

[ C# ] BeginTransaction, Rollback and Commit

C#指令版
http://blog.yam.com/kosaten/article/13648132
http://my.so-net.net.tw/idealist/CS/Basic/transaction.html
SQL指令版
http://msdn.microsoft.com/zh-tw/library/ms181299.aspx
不曉得你們看不看得到這些連結
簡單來說,SQL裡有一個概念叫Rollback,大陸那裡好像翻作「回滾」,這個動作能夠讓資料庫的狀態回到被定義開始交易之前。
SQL有這個指令,C#也有類似的元件可以操作。詳細的操作方式連結裡面都有。
我的想法是,在進行大量修改和寫入(UPDATE和INSERT)的時候,為了避開伺服器中斷而導致輸入資料不齊全的問題,有必要加上這個機制來躲過這個危機。SELECT並不會更動到資料表的狀態,所以不需要。那只有一筆的寫入或更新,我還在考慮要不要使用這個動作,因為沒有經驗和資訊指出這個動作會不會對資料庫的效能和負擔造成影響。
另外,我還不太會寫SP,所以這裡應該會是在C#來實現這個概念。

如果看不到上述的連結,請跟我說一下,我把裡面的資料摘錄下來再另外公佈。

=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:


好了,我說明一下我這裡測試的方法和結論。
方法:
1. 試著把新資料寫進資料表裡,然後再rollback回去,看能不能回到交易前的狀態。結論是可以。
2. 試著把新資料寫進資料表裡,然後再commit。看交易結束之後資料有沒有留著。結論是也可以。
3. 寫入資料之後的指令加入中斷點,看看rollback前和rollback後的結果。結果是發現,我在中斷點的時候去開SQL Server management studio直接敲select指令抓資料,是沒辦法抓的,指令會一直跑跑跑沒有回應。我想應該是資料庫把狀態鎖起來了,除了該連結之外,其它連結不被允許進入資料表進行存取。
4. 試著把新資料寫進資料表裡,然後把SQL server的服務徹底關掉,程式完成之後再打開,看會發生什麼事。因為沒有用try-catch,所以瀏覽器上是會顯示錯誤畫面,但是資料庫的狀態是在交易開始之前,也就是資料沒有被寫入。

結論:
這個方法確實可以達到我們一開始的想法:避開在進行資料大量寫入或修改的時候,資料庫伺服器當機或中斷所引發的資料有多有少的情況。
適度地使用try-catch-finally,就可以實現這個概念。

SqlConnection sqlConn = DB.Conn();
sqlConn.Open();
SqlTransaction sqlTrans = sqlConn.BeginTransaction();
SqlCommand sqlComm.Connection = sqlConn;
sqlComm.Transcation = sqlTrans;

sqlConn = DB.Conn();
sqlConn.Open();
sqlTrans = sqlConn.BeginTransaction();
sqlComm.Connection = sqlConn;
sqlComm.Transcation = sqlTrans;
try{
	for(;;){
		strInsertion = "INSERT INTO xxx (a, b, c, d) VALUE ('A', 'B', 'C', 'D')";
		sqlComm.CommandText = strInsertion;
		sqlComm.ExcuteNonQuery();
	}
	sqlTrans.Commit();
}
catch{
	sqlTrans.Rollback();
}
finally{
	sqlConn.Close();
}

=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:

2011/08/20 新增關於BeginTransaction, Rollback, Commit的測試:
問題:在寫入資料表的時候關掉網頁,對資料表的狀態?
測試方式:
寫一隻無限迴圈程式,不斷讓程式對資料表寫不同的資料,然後在執行的過程中關掉網頁。
測試結果:
一開始寫程式的時候沒有用try-catch-finally,所以直接把網頁關掉,資料表會被lock起來,強制把IDE的模擬Server程式關掉後才得以解開。資料沒有寫進去。
後來加入try-catch-finally後,執行的過程把網頁關掉,再去查詢資料表,是可以運作的。資料也沒有寫進去,
由測試結果的推論:
try-catch-finally,是去執行try裡面的動作,遇到Exception的時候再強制跳到catch,無論try完或是catch完,最後都執行finally。直接把網頁關掉,也能夠用這個方式catch出來。
SQL的做法應該是在先把資料表的狀態lock住,然後對資料表的修改和寫入都是寫到一個暫存的地方,等到下commit指令以後,才把結果一次倒進資料表裡。
總之,今天討論到,如果程式執行很久,使用者在不知情(或是手賤)的情況下關掉網頁,以這種做法來說,不用擔心資料表會被持續lock住。