顯示具有 語言特性優勢 標籤的文章。 顯示所有文章
顯示具有 語言特性優勢 標籤的文章。 顯示所有文章

2015年7月15日 星期三

多國語言小數點符號處理

撰寫多國語系的軟體,必須注意到一些特殊處理事項
本篇所針對的數值呈現問題,將可能造成很嚴重的後果

首先,先明白數值表示法這件事情,在某些歐洲或美洲、東南亞地區數值的小數點符號,並不是「.」,而是「,」,所以當你的程式輸出檔案或者顯示在主控台的時候,會出現312.566被顯示成312,566 這種情形。如下圖的 fValue




這樣的狀況並不只是看起來怪怪的而已,更嚴重的是,如果你要下SQL指令,而且你的指令並不是透過SqlCommand物件的正規使用方式來進行,而是自己串出SQL指令的話,就會讓指令失敗,由於你的數值如果帶有小數點,會變成逗號,這會讓SQL指令被誤解析。

比方
sb.Append("INSERT INTO TB_RECORD_RAW VALUES (").Append(intIoId).Append(",").Append(douValue).Append(");”).
原本預期輸出
INSERT INTO TB_RECORD_RAW VALUES ( 123, 194.566 );
但結果會輸出
INSERT INTO TB_RECORD_RAW VALUES ( 123, 194,566 );
這會讓SQL誤判有三個參數要Insert,但是實際上只有兩個。結果要不就是填錯欄位,要不就會新增失敗(也許欄位不夠多)。


解決這個問題有兩種方式,要不就是透過SqlCommand物件的正規使用方式來進行。要不就是透過修改數值顯示方式(各國有各種特定的表示方式,可以在程式中強制統一),底下針對修改數值顯示方式來介紹。

CultureInfo TheCulture = new CultureInfo("en-US", false);
NumberFormatInfo NumFmtInf = TheCulture.NumberFormat;
NumFmtInf.NumberDecimalSeparator = ".";
NumFmtInf.NumberDecimalDigits = 10;

Double TestDot_ Double = 123.4567;
String Str_Result = String.Format(TheCulture, "{0:F}", TestDot_Double);

透過CultureInfo所建立的物件,會存放著作業系統的文化語言相關設定,指定為"en-US"是美國 “zh-TW“則是台灣所有語言列表於http://www.csharp-examples.net/culture-names/


也可以客制化的修改小數點符號,也可指定浮點數一律顯示到小數點後第幾位,這邊稍微要注意的是,當你在此指定了小數點後第幾位,則不足位數會補零,比方123.4567000000。如果不設定呢,則預設會顯示小數點兩位,進行四捨五入。

2011年12月20日 星期二

C#的委派與事件的使用

委派跟事件,一直是蠻難真正搞懂的一個概念,但是對UI寫作的人或是開發控制項的人來說,卻又經常用到,我想寫系統的人或許也有機會碰到吧,過去一段時間,我一直沒辦法完全搞懂,甚至只是很皮毛的瞭解就拿來用了。為了讓自己更有概念一點,這次花了些時間學習,記錄如下。




◆什麼時候會用到委派?

有兩種狀況:
1.你要寫一個Class或Interface,其中有個函式,會讓別的Class呼叫(注意,是Class,不是Instance喔)。也就是在撰寫類別的時候,就能夠呼叫非實體的函式。


2.你要寫一個Class或Interface,其中有個函式,會達成某種特別功能,但是,寫的時候,你並不知道這個功能會怎麼完成,好比說,你這個Class是個登入的介面或Model,但是密碼驗證的部分,是你提供方法出來,給要使用你這個Class的人,到時候要實作函式內容的(將委派綁定函式)。

當你撰寫一個Class裡面需要呼叫該Class無法呼叫到的函式(當然,就不是這個Class宣告的函式囉)的時候,就會需要用到委派,而什麼情況下Class無法呼叫某個函式呢?我們先看看,什麼情況下,Class可以呼叫非Class自己的函式呢?比方有兩個Class,一個是ClassA一個是ClassB,如果在ClassA中宣告產生了ClassB,那麼ClassB的函式就可以被ClassA呼叫(如果宣告成Public或是有封裝出來),這是物件導向的基本概念,相信大家都懂,但是,如果ClassB想呼叫ClassA的函式呢?那就做不到了。程式如下:


Class ClassA
{
     public void RunClassA()
    {
        ClassB MyClassB = new ClassB();
         MyClassB.MethodB();
    }

    public void MethodA()
   {
        Console.WriteLine( "MethodA被呼叫了" );
    }
}



Class ClassB
{
     public void RunClassB()
    {
        ClassA.MethodA();
    }

    public void MethodB()
   {
        Console.WriteLine( "MethodB被呼叫了" );
    }
}




上述紅底的那一行,就是無法做到的,因為ClassB沒有宣告ClassA,對ClassB來說,他不曉得ClassA是什麼,那麼,如果在ClassB也宣告一個ClassA呢?那麼A中有B,B中又有A,A中又有B,無限循環下去記憶體會吃光光,這種怪程式應該無法跑不下去,有解決方法嗎?有啊,除了用Static來做,或是把ClassA傳進來給ClassB......但是,有時候ClassA不是你寫的,你沒辦法預期人家怎麼做,但是卻又有這種需求,這時候就需要委派了。



◆委派怎麼做呢?
先不要講程式,免得把人搞昏了,概念上,就是ClassB這邊,宣告一個叫做「委派」的東西,這個委派,可以讓ClassB被別人宣告使用的時候,也同時可以使用這個委派,透過特殊的語法,在ClassA把委派跟某個函式綁在一起,這樣只要有人(不論ClassB或ClassA都可以)叫這個委派做事,這個委派就會去找到這個函式,然後執行,這樣ClassB就可以呼叫到ClassA了,聽起來似乎落落長,實際上不難,我們來看看怎麼做。


再稍微細節一點來看,這個作法,就很像是把ClassA的函式的記憶體位址告訴ClassB的委派,而委派大概這麼使用的,這個程式語法跟C#原本語法概念不太相同,不好理解,當成公式吧,要用的時候查就好了。


在ClassB裡頭,先宣告委派

        public delegate int DelegateDefine( String MyPara );

好複雜難懂得一行,不急,這邊指示先宣告委派型別,目的是定義好你這個委派會呼叫什麼樣的函式,那個函式有怎樣的參數,又是怎樣的回傳型態。比方說,這裡是宣告說,我有一個委派叫做DelegateDefine,他到時候要呼叫一個「會傳入一個String的函式,然後這函式的回傳值,是int,就這樣而已。


接下來,把這個委派產生成物件


        public DelegateDefine DelegateObj;


接下來ClassA就可以開始把委派跟函式綁在一起,當然,先決條件就是這個委派宣告的函式型態,可得跟ClassA要綁定的函式一樣喔,比方,函式如下


        public int ClassA_Method( String Paras )
        {
            return 1;
        }



然後開始綁定,可以把綁定寫在ClassA的建構子,也可以寫在某個函式裡面,只要能寫在某件事情之前,什麼事情?就是要叫用那個ClassB的委派之前就行了。綁定的語法也是比較特殊,就是把委派物件跟函式綁在一起就是了。



            My_ClassB.DelegateObj = new ClassB.DelegateDefine( ClassA_Method );




看起來有點複雜,有沒有簡化的寫法?有,有,有,經過簡化過後你只需要這樣寫就行了:
            My_ClassB.DelegateObj = ClassA_Method;








之後,不論ClassA或ClassB都可以用DelegateObj.Invoke( "xxx" );來呼叫函式ClassA_Method 了(xxx就是看你到時候要傳什麼當參數囉),就這樣,很簡單吧?






我寫了一個實際的小範例,大概是這樣的,有一個Class是使用者控制項,用來設計UI放元件的,叫做UC_Child,另外,有兩個Class是Form,也是用來設計UI的,分別叫做Form_Mother與Form_Father,UC_Child設計好了之後,會被Form宣告使用,很顯然的,Form就可以呼叫並使用UC_Child寫好的函式。但,這裡我需要讓UC_Child反向來呼叫Form這邊的函式呢?比方UC_Child這邊按下某個按鈕,或是完成收送之後,要通知Form來更新畫面呢?這個更新畫面,當然是只能寫在Form這邊,而無法寫在UC_Child,這時候,就要透過委派了。


這個範例,除了很簡單的做出委派之外,也包含了幾個委派的特性


1.委派可以同時綁兩個以上的函式
2.委派可以讓很多Parent宣告使用
3.委派綁定了函式之後,當呼叫函式時,並不會在遇到Return的時候就走回去,而是會把整個函式跑完的。
4.委派產生之後,不但Child自己可以Invoke,連Parent也可以呼叫Invoke,只不過,Parent用起來就沒什麼太大的實質意義了。
5.委派同時綁兩個以上的函式之後,會每個函式都跑過了,才會回到委派的下一行來。所以在Child裡面的Btn_Delegate_Click()裡,呼叫了DelegateObj.Invoke()之後,即便他綁了兩個函式,也不會回到這邊兩次,只會最後回到這邊來,然後帶著最後一個回傳值喔。




範例放在這邊   跟這邊



那麼,什麼是事件呢?其實大家對事件的接觸跟瞭解應該反而比委派多,就我目前瞭解,委派跟事件幾乎一樣,用法差不多,反而事件還比較複雜一點,據說,事件有多一些保護機制,但是在我瞭解之前,我也不亂多說,在上面的Link當中有Delegate跟Event兩個範例,差異實在很小。


1.原本在Child要把委派產生個實體出來,要用 事件 的方式的話,就不用,變成用以下的特殊語法,產生一個符合那個委派的事件,然後在Father跟Mother改成用事件跟函式綁定


public event DelegateDefine On_Env_Obj;


2.在Father跟Mother原本也可以叫用的Child的委派,現在改用事件的話,無法叫用Child的 事件 喔,也就是程式裡遮掉的那行
  //My_Child.On_Env_Obj.Invoke 不可以





2011年9月7日 星期三

C#記憶體與資源的釋放與管理

研讀了好多好多文章,然後得到一些心得
如果有人看了覺得不對,麻煩歡迎您告訴我


首先,看看記憶體回收機制
這兩篇文章中提到


從 Managed 堆積 (Heap) 為該物件配置記憶體。只要在 Managed 堆積中有位址空間可用,Runtime 就會繼續為新物件配置記憶體。但是,記憶體畢竟不是無限的。到最後,記憶體回收行程還是必須進行回收以釋放某些記憶體。記憶體回收行程的最佳化引擎會依據所做的配置決定進行回收的最佳時機。當記憶體回收行程進行回收時,它會檢查 Managed 堆積中應用程式已不再使用的物件,並且執行必要作業以回收它們的記憶體。

在 Managed 執行階段環境中,記憶體回收堆積 (Heap) 會管理所有類別物件。這個堆積會監視物件的存留期 (Lifetime),並且只有在程式的任何部分都不參考物件時才加以釋出。這樣可以確保這些物件不會遺漏 (Leak) 記憶體,並可確保對物件的參考永遠有效。


這樣我的心得是只要程式當中的變數或成員在他生命週期結束的時候,在GC(Garbage Collection,C#的資源回收車)定時巡邏的時候,就有機會被釋放出來(據說不會很快釋放,要巡邏好幾趟確認)。而有別於C++的delete指令可以直接把記憶體釋放,C#以這個資源回收機制,來達到安全、效能、並且讓開發人員無須擔憂記憶體管理。

但我們經常在網路上看到不少文章,要我們對資源進行釋放


using 陳述式是一個非常基本的 C# 語法,相信在實務開發上經常會使用到,如果沒使用過的人可能代表你對 .NET 的記憶體管理的 Sense 不太夠,很有可能寫出資源耗盡的 .NET 程式碼。
雖然 .NET 有內建強大的記憶體管理機制(GC),但開發人員還是不能完全依賴 .NET 來處理一些無法釋放的資源,例如:Handles, Unmanaged Resources, …。

C#提供了與C++一樣語法的解構子,當object消失時,.Net framework 會執行解構子,我們可於此時加入程式進行資源回收,與Java相同的是,什麼時候object會消失? 這由VM決定,並非在object變成null時,VM即會立刻執行解構子並回收object。


看了都會覺得害怕,擔心自己寫的程式會不斷的耗盡資源,而且還真的有發生。但是詳細看一下,他們所指的,大多是指:無法釋放的資源(Unmanaged Resources),好,再來看看是否真的如此。先從解構子來看起吧,根據官方文件說法,是這樣的:



程式設計人員無法控制呼叫解構函式的時間,因為這是由記憶體回收行程所決定的。記憶體回收行程會檢查不再被應用程式使用的物件。如果它將物件視為符合解構資格,便會呼叫解構函式 (如果存在),並且回收用來儲存該物件的記憶體。當程式結束時,解構函式也會被呼叫。

☆解構函式是用來解構類別的執行個體。
☆結構中無法定義解構函式。它們只能與類別一起使用。
☆一個類別只能有一個解構函式。
☆解構函式不能被繼承或多載。
☆解構函式不能被呼叫。它們會被自動叫用。
☆解構函式不使用修飾詞或參數。
☆解構函式會隱含呼叫物件之基底類別 (Base Class) 上的 Finalize


也就是說,不是在你把物件設定為NULL或是生命週期一結束,就會呼叫解構子,要等GC認定符合資格才行,那該信任GC嗎?我們會不會很快就把資源耗盡?我們繼續參考以下非官方文章:



基本的原則是當該變數「不再有效」時,便會被視為garbage,具體的情況則包括超出該變數的有效範圍(ex:離開了對應的大括號的區域變數)、 將變數指定為null、重新指向其他物件(而原先指向的物件已無法被取得)、重新初始化…等,這時原先變數佔有的空間都會被CLR視為garbage而等待回收。若變數為數值型別,則當其超出有效範圍時,CLR會直接回收它在Stack上所佔用的空間;若變數為參考型別,則CLR會先回收它在Stack上佔用的空間,而將Heap上的空間視為garbage,等待GC回收。若參考型別的變數在其有效範圍內重新初始化,則原先所指向的物件亦會被視為garbage。然而,被視為garbage的變數,並不是馬上就被GC回收,而是根據GC內的演算法,依變數被判定的generation而有不同。在Managed Heap上的物件會被CLR分為三個generation:0、1、2,數字越大表示存活時間越長。這樣的設計乃是建構在「只壓縮部份的Heap會比一次壓縮整個Heap來得有效率」的事實上,因此將物件分成不同的層級,在回收時針對不同層級做回收,是.NET Framework考量效率後所採用的方法。程式中常會應用到的區域物件與暫時物件因都屬於gen 0,故可確保被回收的頻率最高,對一般不會用到大量暫時物件的程式而言,不需擔心資源的浪費。


也就是說一般大多情況我們是該相信GC的,但是如果我們的程式需要用到很多資源呢?繼續看下去


「若是程式員希望在某個時間點確保Managed資源被回收,應該怎麼做?」「若是類別中含有Unmanaged資源,又該如何釋放?」對於這二個問題的回答,在這裡便開始要提到程式員如何撰寫明確釋放資源的函式了。在C++中,程式員撰寫類別的Destructor來達成類別資源的釋放,而在C#中,同樣有類似效果的函式有Finalize()與Dispose()。Dispose()與Finalize()最大的不同,在於Dispose()是明確地在「程式員呼叫」與「using語句區塊結束時」二種情況下被呼叫的。也就是說,當程式員想確保某個類別中的Unamaged資源會在某個特定位置被釋放掉時,他便可以實作IDisposable介面中的Dispose()函式;而為了提供程式員在操作常用的暫時物件上不會忘記呼叫Dispose(),C#中提供了using陳述式,確保在小括弧中的變數,在離開using區塊時,其Dispose()會被明確地呼叫。當一個物件的Dispose()被呼叫後,亦即標示了這個物件為無效,則在第一次的GC回收行程中便會被回收到。值得一提的是,即使某個物件的Dispose()已經被呼叫過了,該物件的Finalize()仍然有可能再被GC給呼叫到,這是為了避免當Dispose()失敗時可能產生的資源浪費。然而,若Dispose()成功地執行了,則應該避免再讓GC去呼叫Finalize(),否則會造成程式效率的低落。


也就是說資源的釋放,可以交給GC,如果要自己來釋放,就要實作Dispose()
而關於Dispose()的實作方式,就參考這兩篇文章了




把Unmanaged Resources,或是你想要早點自己釋放的Managed Resources寫在Dispose(),就行了。



綜合心得:

1.如果僅用了Managed資源,GC可以自動幫我們釋放記憶體,如果我們需要自己提早釋放,則可以實作出Dispose()函式。
2.如果使用了Unmanaged資源,一定要寫Dispose()函式,才能自己釋放,否則只能等程式停止才會釋放。
3.官方說寫Destructor()但是裡面如果是空的,對Managed Resource來說是沒有幫助的,反而減低效能,所以要寫出Destructor(),就要把裡面的內容實作出來才有意義。
4.Dispose()的內容,參考MSDN的作法,寫一個Dispose( bool )的函式,如果參數是true,是call by program (程式設計師),就釋放Unmanaged Resources與Managed Resources。而如果是false,這是call by C#(C#機制自動呼叫的),這時候,因為Managed Resources已經被釋放了,所以就避開,不要再次呼叫Managed Resources的Dispose(),不然會多耗費效能的。