2020年12月30日 星期三

C# 物件的複製

 C# 物件的複製

  • Shallow Copy - 使用 MemberwiseClone():

public Person ShallowCopy()
{
return this.MemberwiseClone() as Person;
}
  1. If a field is a value type, a bit-by-bit copy of the field is performed
  2. If a field is a reference type, the reference is copied but the referred object is not


  • Deep Clone - 使用 BinaryFormatter() 序列化(Serialize) 
        public Person DeepClone()
{
using (var memory = new MemoryStream())
{
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(memory, this);
memory.Seek(0, SeekOrigin.Begin); // Set the position to the beginning of the stream.
return formatter.Deserialize(memory) as Person;
}
}


reference: https://dotblogs.com.tw/yc421206/2012/05/25/72390

2017年8月15日 星期二

Set PowerShell Execution Policy

透過 C# 執行某個 *.psl 時,有時候會遇到下列錯誤訊息:
"*.psl cannot be loaded because the execution of scripts is disabled on this system"
此時, 必須 set execution policy to unregistered, 如下:
"Set-ExecutionPolicy Unrestricted"


使用C# code設定 Execution Policy:
** PowerShell 有2 個版本 (x64 與 x86)。
1). 當C# 應用程式為64-bit時,這裡的Set Execution Policy 會設定到 PowerShell x64
2). 當C# 應用程式為32-bit時,這裡的Set Execution Policy 會設定到 PowerShell x86

** 設定完ExecutionPolicy 之後, 可以在Windows Registry看到:
1). (PowerShell x64)
HKEY_Local_Machine\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.Powershell\ExecutionPolicy 為 unrestricted
2).(PowerShell x86)  HKEY_Local_Machine\SOFTWARE\Wow6432Node\Microsoft\PowerShell\1\ShellIds\Microsoft.Powershell\ExecutionPolicy  為 unrestricted



Reference:
1). Set-Execution Policy from process <Link>
2). Powershell Get-ExecutionPolicy returns different values <Link>

2017年7月26日 星期三

HTTP v.s. SOAP

HTTP 可以提供各種服務 - HTML, images, sound, video, etc.
SOAP 是在 HTTP 上面, 專門提供 XML 傳送的服務.

SOAP 的request 如下,HTTP headers在上面, SOAP message 在下面:

---------  HTTP portion of the message ------
POST /InStock HTTP/1.1
Host: www.example.org
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn

---------  SOAP portion of the message ------
<?xml version="1.0"?>
<soap:Envelope
xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">

<soap:Body xmlns:m="http://www.example.org/stock">
  <m:GetStockPrice>
    <m:StockName>IBM</m:StockName>
  </m:GetStockPrice>
</soap:Body>

</soap:Envelope>

reference:
Difference between SOAP and HTTP protocol <Link>

2017年7月20日 星期四

HTTP Request methods - Get and Post methods

HTTP Request methods 有下列8種方法:

  1. GET
  2. POST
  3. OPTIONS
  4. HEAD
  5. PUT
  6. DELETE
  7. TRACE
  8. CONNECT
先介紹最常用的 GET 與 POST 方法:

  1. GET:取得我們想要的資料。把查詢資訊放在URI上, 「取得」想要的資訊。 
    • Ex: http://xxx.toright.com/?id=010101
  2. POST:新增一項資料(不會覆蓋舊資料)。要求Server接受的資訊是附在請求本體(Body), 而不是在URI上

Reference:
  1. 常見的HTTP METHOD的不同性質分析 <Link>
  2. 重新認識HTTP請求方法 <Link>
  3. 淺談 HTTP Method:表單中的 GET 與 POST 有什麼差別 <Link>

2017年7月3日 星期一

OOD 原則 - SOLID

OOD 原則 - SOLID

物件導向設計 5 大原則:
1. (SRP) 單一職責:
    一個類別只負責一件事情

2. (OCP) 開放/封閉原則:
    開放擴充, 關閉修改

3. (LSP) Liskov替換:
    子類別應該可以替換掉父類別而不會影響程式架構
    子類別可以擴充父類別的功能, 但不能改變父類別原有的功能。子類別不要覆蓋父類別的方法!
    -->設計上應該少用繼承, 多用composite/aggregation 達到re-use目的 (i.e., 做一個wrapper 包住 base class)
    --> CARD - 先思考 Composition/Aggregation 的 Reuse ,真的沒辦法才思考 Derivation

4. (ISP) 介面隔離:
     使用多個專門介面, 比使用單一總介面要好! 把不同的函數從介面中分離出來
    --> 不同的函數應該屬於不同的介面

5. (DIP) 依賴反轉
    高階模組不應依賴低階模組,兩個都應該依賴在抽象概念上
    抽象不應該依賴於細節, 細節應該依賴於抽象!
    --> 針對介面寫程式
    --> Dependency Injection 是實作 DIP 的手法 (例如: 通過Constructor注入依賴)
    --> 若一個Constructor注入太多的依賴,則此Class 可能違反了SRP

SRP 與 OCP 都只是一個方向, 不是教義! 寫程式要專注一個主要的核心任務, class要盡可能簡單, 但是不是教義!

2017年5月18日 星期四

Callback 的意義

引用http://www.programmer-club.com.tw/showSameTitleN/c/20869.html

callback,字面上的解釋就是「回呼」,這牽涉到多工作業系統中兩個同時執行﹝concurrent﹞的不同模組。

一種情形是,A 模組給 B 模組一個 function pointer,請 B 在處理完某項工作後,或是在適當時機,使用這個 function pointer 來呼叫該 function 函式。
例如,A 模組裡面寫了一個 function 叫做 CallMeIfDone,然後它啟動了 B 模組,並且把 CallMeIfDone 的 pointer 傳給 B 模組。A 模組繼續執行它的工作,B 模組也同時在處理它的事情,等到 B 模組忙完了,它就會呼叫 CallMeIfDone,但是這個函式是寫在 A 模組裡面的,所以實際上是跑回來 A 模組的地盤執行 CallMeIfDone,因此就稱為「回呼,callback」。

另一種情形是,B 模組是一個獨立執行的模組,專門處理使用者輸入,每當使用者敲一下鍵盤,或是動一下滑鼠,它就會產生一個事件,需要處理這些事件的其他模組必須向 B 模組登記 callback,例如 A 模組向 B 模組登記了 KeyboardEvents 的 callback,那麼 B 模組在偵測到鍵盤動作時,就會去呼叫這個callback了,當然這個callback也是寫在 A 模組裡面的。

2017年1月3日 星期二

MVC 架構 - 以 Hyena 為例

MVC 架構以 M 最為重要! 說明如下:
  • Model: 存放資料,當資料改變時,透過 C 來更新V
  • View: 只是視覺化Model
  • Controller: 當 M 改變時,用來更新V
為了達到 "當 M 改變,透過 C 來更新V" 的目的,Controller 要能夠看得到Model 與View:


其中,Controller 的 RegisterView() 如下:

如此便可以把 M 的改變 呈現在 V  上面。

MVC的優缺點如下:
** 優點:
  • 可以用不同的View 呈現相同的Model
  • 容易對 Controller 進行測試 (Testability => mockup Model for testing),適合TDD

**缺點:
  • 採用Event-driven,是一種複雜的UI design pattern
  • 需要透過Controller 來更新View,很耗資源 



另外,請參考Hyena的Class diagram,Model 也用來執行真正的Business:


MVC 不是一種技術, 而是一種設計理念!

2016年8月29日 星期一

什麼是多型 (polymorphism)

所謂的"多型",指的是 runtime 時期執行的內容。
在C#當中,有下列四種多型的意義,其中最常提及的就是OO的繼承式多型:
1). 繼承式多型 (inclusion)
在design time,可以用父類別型態容納子類別的物件,在 run time 進行函數呼叫時會呼叫到子類別的函數,重寫 (override) 父類方法

2). 泛型
List<T> 中的 T 就是參數型別,依據參數的型別決定實作的內容

3). 運算子多載 (overloading)
C#可以多載運算子 (ex: 可以重寫 == 的行為)

4). 強制同型 (coercions)
自動將型別轉換,ex: 在 run time 時 int 轉成double



"多型"與"動態繫結(Dynamic Binding)"是相關的, dynamic binding可利用(virtual function)來達。

1). 晚期繫結也稱之動態繫結(dynamic binding)。
簡單的說,就是物件的行為並不是在編譯時期 (compileir-time,就已經決定了。而是在程式執行時期於(晚期)(run-time)才動態地決定 的。如何動態地決定。就看物件當時的狀態(state)而定,物件封裝了所有可能的狀態處理 方法,並且根據外邊送來的訊息做出適當的反應。這也就是晚期連結的意義,這是物件導向 一個很重要的精神。

2).繫結
是將程式中所使用到的各名稱(包括程式名稱及變數名稱),分配到適當的記憶體位置。
其中在編譯過程中即完成連結的稱為靜態繫結(Static Binding),又稱為早期繫結(Early Binding)。如果是在程式執行過程中才完成繫結的,則稱為動態繫結(Dynamic Binding),又稱為延後繫結(Late Binding)。

2016年8月28日 星期日

專業主義

大概在今年6月底收到MTD team的需求,希望開發一個Tool,把所有machine test reports 做一個資料分析,彙整成一份報告(csv格式)。

以下是一開始從MTD team收到的資訊:

  • 目前大約有200台machines, 都放置在 ~\machine_data 資料夾裡面
  • 每個machine有60個test report要分析
  • 每個test report 依據test name,存放在~\mXXXX\test_results\YS@prepship\Test
  • 每個test report的xml格式都很類似


時程估算方面,依據之前工程師用matlab開發MAS的經驗,一個test report大約一個小時,所以粗估60個test reports大約要7天。

然後,我就開始做了...災難也開始了...

首先,根據MTD team的說法,由於我先前已經做了一個parsing tool,所以"只要修改一下"原本的parsing tool,便可以符合他們的需求。
這個認知整慘我了...
因為 input 與 output已經不同了,我幾乎是重寫整個parsing tool,這部分大概花4天,絕對不是"只要修改一下"

再來,MTD team給我的資訊只有部分正確,詳述如下:
  1. 目前大約有200台machines, 都放置在 ~\machine_data 資料夾裡面
    • 錯! 200台machines的資料放置在 ~\machine_data 與\\172.xxx.xxx.xxx\d 資料夾裡面
    •  ~\machine_data 不需要 Id/Password ,但是\\172.xxx.xxx.xxx\d 需要Id/Password才可連線,這部分大概花了1天
  2. 每個machine有60個test report要分析
    • 錯! 60個test report是200/250的machine,後來又新增加了12個350的test reports
    • 這12個新增加的test reports是全新格式,這部分大概花2天
  3. 每個test report 依據test name,存放在~\mXXXX\test_results\YS@prepship\Test
    • 錯!~\mXXXX\test_results\YS@prepship\Test 這個資料夾結構是人工後續手動輸入,因此有很多錯誤,造成test report 找不到
    • 因此,只好加入log,針對找不到的test report手動把資料夾結構調整成正確
    • 手動把資料夾結構調整再加上log機制,就花了3天
  4. 每個test report的xml格式都很類似
    • 錯!xml schema只要有一點點不同就必須"特別處理" (special handle)。幾乎每個test report都要個別處理,共通性的test report少之又少...
    • "特別處理"意味著需要額外的effort,大概花了6天
另外,最後彙整產出的csv檔案還需要加上:

  • 顯示machine type (從CM_Options.xml讀取)
  • 顯示單位(從HTML而來)
  • test report (xml) 與CS 習慣閱讀的HTML,KPI顯示的字串不同,必須一個一個檢查

這些零零總總又多花了3天...

總結上述天數是19天,是原本粗估(7天)的2.7倍...
在開始專案之前,我應該要審慎評估的,這才是專業!



2016年5月8日 星期日

使用16進位(hexadecimals)的時機與好處


寫程式的時候,有各種理由使用 16 進位。
其中一個理由是提醒工程師 - 我們要處理bits(用電腦世界的觀點),而不是處理數字(用人類世界的觀點)。使用 16 進位代表我們要處理相對低階的問題 (例如:記憶體資料的型式是必須被考慮)。

以下2個例子說明 16 進位使用時機:
[範例1]
 使用 bit shift (x << y) 比使用"乘法"更為清楚:
 1 << 10 = KB, 1 << 20 = MB, 1 << 30 = GB
 建立一個 16 KB 的array: var buffer = new byte[16 << 10]

[範例2]
 在 enum 當中, 項目是從 0 開始,而且依序加 1:
 使用 16 進位可以避免'負數'產生
 使用 16 進位可以使用bitwise AND (&) and OR (|) operators

 相關連結:
Why are flag enums usually defined with hexadecimal values: stackoverflow
使用16進位的好處是?? ptt

2016年5月4日 星期三

Implementation Patterns - Kent Beck (Chapter 6 State (狀態))


根據書上的建議,狀態的管理應該:
  1. 把相似的狀態放一起
  2. 把不同的狀態分離
要判斷兩個狀態(或是變數)的相似性,可以用下列方式:
  1. 他們在同一個 method 被用到
  2. 他們出現和消滅的時間相同
就以 MaterdiskAutoTool 的設計來當作例子看看設計...

[Case 1]:
MasterdiskAutoTool 會讀取 CM_Options.xml,來作為建立 Steps 的判斷。設計如下:


可以發現,Form 直接讀取 CM_Options.xml,然後再把 CM_Options.xml 的內容用參數的方式傳遞給其他的class (紅色框框的地方, 以及每個 Step)。
這種設計有下列缺點:
1.) 參數的傳遞路徑太長了,參數也太多了
2.) 這些參數透過Step的constructor傳進去變成step的全域變數,然後被Step裡面的 method 所使用,造成 method 裡面會有'全域變數'與'區域變數'交雜的情況,不容易閱讀
3.) 本質來說,Step 物件本身並不需要擁有這些參數,只有 Step 的 method 需要這些參數。'全域變數'與'區域變數'在此 method 的生命週期不一樣 (違反狀態管理原則)
4.) 因為Step 擁有CM_Options.xml的參數,當Step要增減這些參數時,必須修改很多地方  (:ex: constructor, 全域變數...)
5.) 不容易寫unit test (因為要準備太多參數當作input)

[Case 2]:
比較好的做法,是把讀取CM_Options.xml 的內容交給某個CMOptions class 處理 (橘色框框),然後物件向CMOptions class 要內容。
也就是說,CMOptions class只會出現在Step裡面的 method,Step 不會有CM_Options.xml 的內容變成全域變數 (生命週期一樣)。

修改後的設計如下:


這種設計可以避免上列的缺點,而且還有下列優點:
1.) CMOptions class只只需要出現在Step裡面的 method,Step不會有CM_Options.xml 的內容是全域變數 (因此 method 內的變數生命週期一樣)
2.) 一旦 CM_Options.xml 有變化,則不需要改變 MasterdiskAutoTool 的架構

[Case 3]:
由上面Case 2可以發現,幾乎所有Steps都必須使用 CMOptions class,造成關係太複雜。
可以再進一步改善一下: 讓 BaseStep 使用 CMOptions class,這樣每個Steps 就可以擁有 CMOptions class,免除每個Step自己使用CMOptions class。






Event 的意義

一個物件可以有三種東西:
  1. Property (ex: public string Name = "Peter";)
  2. Method (ex: public void Convert() {...} )
  3. Event (ex: public event EventHandler Click;)
一個物件擁有event, 它便可以通知其他物件"某件事情已經發生了"!
其中,一個 event 可以有多個 handlers 。 利用 event - handler 語法,我們可以建立一個 Notification system 。[註1] [註2]

若是要利用 event - handler 設計一個 Notification 機制,必須先確定下列3件事:
1. 誰有event,並且如何raise這個event
2. 誰handle這個event (也就是誰實作 callback function)
3. 兩者如何連結

以下用 Button 與 Form 做個例子:
1. Button 有 Clicked event,並且 user 透過 UI 來 raise 這個 event
2. Form 會 handle這個 Clicked event
3. Form 的 Constructor 實作把 button.Clicked 與 callback function 作連結 ( += )



再舉個 Queue 與 Producer 的例子:
當 Queue 有空位的時候,Queue物件 '通知' Producer物件進行 enqueue。(Queue 發出Event 給 Producer)
仿照 Button 與 Form 的設計如下:



1. Queue 有 NotifyToEnqueu 的 event,並且實作 OnNotifyToEnqueue() 讓 user (client code) 來raise 這個event,如下:

        public event EventHandler NotifyToEnqueue;

        protected virtual void OnNotifyToEnqueue()
        {
            if (NotifyToEnqueue != null) // null means no subscribe
            {
                NotifyToEnqueue(this, EventArgs.Empty); // raise event
                
                // Or, you can write like:
                // NotifyToEnqueue.Invoke(this, EventArgs.Empty);
            }
        }

2. Producer 會 handle 這個 NotifyToEnqueu event,如下:

        private void Queue_NotifyToEnqueue(object sender, EventArgs e)
        {
            m_MyQueue.Enqueue("aaa");
        }

3. Producer 的 Constructor 實作把 queue.NotifyToEnqueue 與 Queue_NotifyToEnqueue 作連結 (+= ), 如下:

public Producer(MyQueue queue)
        {
            m_MyQueue = queue;
            m_MyQueue.NotifyToEnqueue += Queue_NotifyToEnqueue; // subscribe
        }

所以,當呼叫 OnNotifyToEnqueue() 的時候, Producer物件就會進行 enqueue

[註1]
event 這個物件(ex: NotifyToEnqueue)的 invocations list 可用來儲存多種方法 (ex: Queue_NotifyToEnqueue方法)
要使用這些方法,只要呼叫 Invoke() 即可。 ex: NotifyToEnqueue.Invoke();

[註2]
An Event declaration adds a layer of abstraction and protection on the delegate instance.
This protection prevents clients of the delegate from resetting the delegate and its invocation list
and only allows adding or removing targets from the invocation list.

2016年5月1日 星期日

PlatformTarget 不同而造成的 Build warning

[Problem description]
在 Build C# 的過程當中,當某個 Project A 有 reference 到 Project B,如果這兩個 Projects 的 PlatformTarget 不同,則 MS-Build 結果有可能出現下列 Warning 訊息: (此 warning 不會出現在 VS-Build )

"warning MSB3270: There was a mismatch between the processor architecture of the project being build "MSIL" and the processor architecture of the reference "[internal C# dll]", "x86"."


[Root cause]
這是因為 兩個 Projects 的 Debug platform (或是 Release platform) 不相同所造成。如下圖所示:
MasterDiskAutoTool.Test.csproj 是一個 Unit test project,它會 reference 到 AutoTool.csproj。
然而,MasterDiskAutoTool.Test.csproj 的 release build platform 為 Any CPU,與 AutoTool.csproj 的 release build platform 的 x86 不相同。
因此,MS-Build 的結果有可能產生 Warning



[Solution]
把 MasterDiskAutoTool.Test.csproj 的 release build platform 改為 x86 (與 AutoTool.csproj 一樣),然後 MS-Build 就沒有Warning 了。
如下圖所示:




**PS: 另外一個因為Platforms 不同所造成的執行時期錯誤,請參閱 這裡


相關連結:
VS compile warning - “mismatch between processor architecture” stack overflow

2016年4月28日 星期四

Functional requirement & Non-Functional requirement

要寫好一份'軟體需求規格文件' (SRS) 不容易。 '使用者需求'是它的Input,'軟體設計文件' 是它的Output。
[User Requirements] --> [SRS] --> [SW Design (High Level Design + Detailed Level Design)]

什麼樣的SRS才是好的? 下列四點是個不錯的判斷依據:
1. 提供回饋給user。最好的回饋方式就是讓User看到! 比如UI、Output files, Tools
2. 用軟體的觀點來看問題,把問題分解成幾個SW components。What (SW components, Tools) should be enhanced/changed/updated/removed/created...
3. SRS是SW design 的Input。SRS 只需定義'規格' (What),它是一份合約。實作方式則定義在SW Design (How)。
4. SRS 必須用'外顯'、'可量測' 的角度來撰寫,好讓Tester判斷正確性。

看了許多的 SRS, 節錄出下列幾個重要方向:
Functional requirement: 定義系統要做的事 (What the system does)
• Business Rules
• External Interfaces (ex: UI, output files...)
• Audit Tracking (ex: Logging)
• Transaction corrections, adjustments and cancellations
• Administrative functions
• Authentication
• Authorization levels
• Certification Requirements
• Reporting Requirements
• Historical Data
• Legal or Regulatory Requirements

Non-functional requirements: 定義系統的能力 (What the system's capability - the power to do it)
• Reliability - 可以依據既定規格而持續運行的能力
• Serviceability
• Availability
• Performance – for example Response Time, Throughput, Utilization, Static Volumetric
• Scalability -  To handle a growing amount of work (系統處理工作量的成長)
• Capacity - 系統可以產出(或提供服務)的最大量
• Recoverability
• Maintainability: 維持原有的形式,萬一發生錯誤也可以回復到原有的形式
• Security
• Regulatory
• Manageability
• Environmental
• Data Integrity
• Usability
• Interoperability


Non-functional 影響層面有:
  • Reliability: Improvement in number of hits [hits/ syst yr] (Hit 次數)
  • Serviceability: Improvement on Maintenance and in MTTR (diagnostics, access, repair and/or recovery) repairing of system [hrs] (維修, 回復 時數)
  • Availability: Improvement in hours of downtime both USD (Unscheduled) as well as SD (Scheduled) [hrs/syst yr] (Downtime 時數)
  • Manufacturability: Improvement in CT (Cycle Time) or A-time in production [hrs/ syst]
  • Safety: Improvement of probability of incidents occurring
  • Usability: Functional improvement of machine for operator
  • Machine Performance: Non-functional improvement of customer requirements



2016年4月18日 星期一

The Benefits of Isolating Applications (隔離應用程式的優點)

本文先說明應用程式'隔離'(isolation)的概念, 接著再說明.NET 實作'隔離'的方式。

[應用程式'隔離'的概念]
在過去,Process boundaries 被用來達到 app 隔離的效果。App 必須被隔離是因為'記憶體位址' (memory address) 是 process 相關 (process-relative)。也就是說,這個 process 使用的 memory address 不能被另一個 process 使用。
因為 memory address 不能共用,因此,兩個 processes 之間也不能直接互相呼叫,也就是不能直接存取程式碼 (必須使用 proxies, 例如 Inter-process communication)。

而現在,managed code 在被執行之前,必須通過'權限驗證'(permission verification)才能被執行 (除非  administrator 已經授權 skip此驗證 )。權限驗證主要是驗證下列兩件事情:
  1. 程式碼是否可能嘗試存取無效的memory address
  2. 程式碼是否可能無法正常運行
如果上述的驗證通過了,此 managed code 稱為 型別安全 (type-safe) [** 注意: 這裡指的 type safe,指的是memory type safety (參閱 這裡),而不是廣義的type safe定義 (例如 integer 與 string 的型別)]。這種'權限驗證'提供了 CLR 具有與過去Process boundaries 相同的app 隔離效果,而且效率更好!

上述的說明,概念上解釋了隔離的重要性。接下來,說明.NET 如何實作隔離

[.NET實作'隔離'的方式]
在.NET中,使用 AppDomain 可以讓 CLR 提供 app 的隔離效果。CLR 允許單一個process裡面執行多個AppDomains,也就是說,單一個processe可以執行多個 apps,如此便增加了 server 端的 scalability。

AppDomain 具有下列優點:
  1). 在某一應用程式中的錯誤不會影響其他應用程式。由於 type-safe 程式碼不會導致記憶體錯誤,所以使用 AppDomain 可確保在某一Domain中執行的程式碼不會影響處理序(process)裡面的其他應用程式。

  2). 可以停止個別應用程式而不需停止整個處理序(process)。使用 AppDomain 可以卸載在單一個 app 中執行的程式碼。[** 注意: 這裡指的是 "卸載一個完整的AppDomain",而不是卸載一個DLL) (卸載AppDomain的方法, 參閱 這裡 ]

  3). 可以防止不同 AppDomain 中對於物件的呼叫。在不同定義域之間傳遞的物件必須用'複製方式傳遞'或由 'Proxy' 存取。
    3-1). 如果物件是複製的,那麼對該物件的呼叫就是區域呼叫。也就是說,呼叫端(caller)和被參考的物件是在同一個AppDomain。
    3-2). 如果物件是透過 Proxy 存取,那麼對物件的呼叫便是遠端呼叫(call remotely)。在這種情況下,呼叫端(caller)和被參考的物件(referenced object)是在不同的應用程式定義域中。跨定義域呼叫(Cross-domain call)是使用與兩個處理序(process)或兩部電腦之間呼叫相同的'遠端呼叫基礎結構'(remote call infrastructure)。因此,所參考之物件的中繼資料 (Metadata) 必須在這兩個AppDomain 中都能使用,才能讓該方法呼叫被 JIT 適當地編譯。如果caller 無權存取所呼叫之物件的中繼資料(Metadata),則編譯可能會失敗,並擲回型別 System.IO.FileNotFound 的例外狀況。

4). 程式碼的行為範圍(behavior scope)是由該應用程式決定。換言之,AppDomain會提供 app 版本原則、它所存取的遠端組件的位置,以及有關哪裡可以載入組件的資訊。



相關連結:
The Benefits of Isolating Applications: MSDN

2016年4月17日 星期日

Regex Tester (Regular Expressions 線上debug pattern)

發現一個不錯的網站, 可以直接在線上 debug 與 test Regular Expressions的 pattern,而且它還有解釋 pattern 的每個意義:

  regular expressions 101



先關連結:
Regular Expression的學習筆記

什麼是 AppDomain (Application domains, 應用程式定義域)

OS 和 CLR 提供了某種 '隔離' (isolatation) 的概念來確保 process 和 process 之間不會相互影響。Application domains 就是這種 '隔離' (isolatation) 的概念。有了AppDomain,CLR便可以提供組件的安全性 (security)、可靠性 (reliability) 、 版本控制(versioning) 以及 卸載組件(unloading assemblies) 。

AppDomain 是由runtime host 所建立。Runtime host 啟動 CLR之後, 然後再 開始執行 app 。也就是說,Runtime host 會為這個 app 建立一個AppDomain。

有關什麼是 AppDomain的詳細說明, 請參閱 這裡。


相關連結:
Application Domains : MSDN

2016年4月13日 星期三

NUnit gets wrong configuration file (NUnit test 讀不到 app.config)

[Problem description]
要讀取應用程式的app.config,若使用下列的 API,在 NUnit 環境下可能會讀取到錯誤的 config:

if (File.Exists(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile))
{
    // do something
}

雖然使用此 API,在執行應用程式時可以正確回傳應用程式的config 檔案 (ex: <MyAppName>.exe.config),可是在 NUnit 環境下,使用此API 卻會回傳另外的 config 檔案!
例如:
  • 使用 VS2010 + Resharper (v6.1) 會回傳 ~\UnitTest\bin\Debug 目錄下的 UnitTest.dll.config 
  • 使用 VS2015 + Resharper (v10.0) 會回傳 %temp% 目錄下某個隨機子目錄的 config 檔案
這種不確定性會造成 unit test 不夠 robust, 而且上述兩種環境都無法正確讀取到<MyAppName>.exe.config。

[Root cause]
AppDomain.CurrentDomain 可以用來取得目前應用程式的 domain 。
  1. 對於應用程式來說, AppDomain.CurrentDomain 指的是 exe 所在的目錄,一般是位於~\bin\Debug 
  2. 對於 NUnit  來說,它沒有 exe 檔案,所以指的是 UnitTest.dll 所在的目錄: ~\UnitTest\bin\Debug
  3. 然而, 使用 VS2015 + Resharper (v10.0) 卻指向 %temp% ,這個應該是設定上的問題。
既然在使用NUnit 上透過 AppDomain.CurrentDomain 只能取得 NUnit 自己的 domain ,無法取得我們想要的應用程式 domain ,因此無法透過此API 取得 <MyAppName>.exe.config

[Solution]
有下列幾種 work around:
  1). 改用應用程式的 Settings.setting 取代 <MyAppName>.exe.config
  2). 移除 app.config 與 MyAppName 的直接相依性, 改用 dependency injection 來建立 app.config 與 MyAppName的關係,如此便很方便 Mock 這個 app.config 在NUnit的程式碼當中




相關連結:
app.config 與 Settings.setting 的差異: MSDN forums, StackOverflow, User settings - MSDN
什麼是 AppDomain
AppDomain.CurrentDomain 屬性
Application 與 AppDomain.CurrentDomain 的差異

2016年4月11日 星期一

IsHandleCreated, InvokeRequired 與 Invoke 的關係

要存取 Windows Forms Controls 就必須考慮 Thread-Safe的問題 (參閱: 這裡)。如果 InvokeRequired 是 True,就必須使用 Invoke method 來保證 Thread-Safe。

然而,要使用 Invoke method,必須確定到這個 Form 或是 Control 已經擁有 '視窗控制代碼' (window handle)。
在下列的情況下,Handle 才會存在:
  • Form/Control 已經實體化( instantiated )且初始化 (initialized, first shown)
  • Form 還沒被 closed (i.e., handle 還沒被 disposed)
** 要注意的是,使用InvokeRequired 判斷是否為 cross-thread 並不正確! 因為 InvokeRequired 在下列2種情況會回傳 False:
  1. 相同 thread (表示不需要 Invoke)
  2. Form/Control 沒有 handle (沒有 handle 就不可能 Invoke)
也就是說,InvokeRequired  只能判斷要不要 Invoke!

若要使用InvokeRequired 判斷 cross-thread,必須排除 handle 不存在所造成的影響。
比較好的做法是: 使用 InvokeRequired 之前,先判斷 IsHandleCreated 為True。

[例如]:
private delegate void SetTextDelegate(string text, Color color, Font font);

private void SetText(string text, Color color, Font font)
{
            if (IsHandleCreated)
            {
                 // 已經排除沒有 handle 的情況
                 // 所以可用InvokeRequired 判斷是否為 cross-thread
                if (InvokeRequired)                
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    Invoke(d, new object[] { text, color, font });
                }
                else // use the RichTextBox control directly
                {
                    txtLogging.AppendText(text);
                    txtLogging.SelectionColor = color;
                    txtLogging.SelectionFont = font;
                 }
            }
}

相關連結:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created


Cross-thread operation not valid on Windows Form Control

Windows Forms Controls 的存取不是安全執行緒(Thread-Safe)。 如果有兩個以上的執行緒在管理 Control 狀態,則可能會造成 Control 進入不一致的狀態, 也可能發生其他與執行緒相關的錯誤 - 例如競爭情況 (race conditions) 和死結 (deadlock)。 因此,必須確定 Control 存取是以Thread-Safe的方式執行。

最常發生的錯誤情況是,建立 Control 的執行緒 (i.e., Windows Form, creating thread) 與 存取Control 的執行緒 (i.e., 某個calling thread) 並不是同一個Thread,而造成 Cross-thread operation not valid 錯誤。此時,使用 Invoke method 可以解決這個問題。
(** 所謂的 Invoke method,其實只是把 delegate 與 input arguments 傳進去, 讓  Windows Form 去執行此 delegate。 參閱: Control.Invoke方法)

對 Windows Forms Controls 進行Thread-Safe 存取的作法如下:
  1. 查詢 Controls 的 InvokeRequired 屬性。( InvokeRequired 用來 compares the thread ID )
  2. 如果 InvokeRequired 回傳 true ,不同 thread 要使用 Invoke;
  3. 不然的話,相同thread 則直接存取Control 。 
[例如]:
private delegate void SetTextDelegate(string text, Color color, Font font);

private void SetText(string text, Color color, Font font)
{
                 if (InvokeRequired)
                {
                    SetTextCallback d = new SetTextCallback(SetText);
                    Invoke(d, new object[] { text, color, font });
                }
                else // use the RichTextBox control directly
                {
                    txtLogging.AppendText(text);
                    txtLogging.SelectionColor = color;
                    txtLogging.SelectionFont = font;
                 }
}


利用VS 2015執行應用程式時,如果VS 2015引發 InvalidOperationException,並且會有訊息:"Control 'control name' accessed from a thread other than the thread it was created on",這就表示此 Control 正在被 none Thread-Safe 的行為存取, 此時必須用Invoke method 來解決。


See the following Q&A (from https://bbs.csdn.net/topics/390581976)
1. 為什麼更新某個 Textbox 的狀態, 不能夠直接寫 Textbox="123" 就好了?
  • 為了Thread-Safe 。微軟建議你在創建 Control 的那個 Thread 裡面去更新 Control 。如果只是 Textbox="123", CLR怎麼知道你是想做 Thread-Safe 的操作? 還是 none Thread-Safe 的操作呢?

 

2. 為什麼一定要搞個 delegate 要我們去寫的這麼複雜?
  • delegate 與 multi-thread 更新 Control 沒有什麼必然關係, 它只是指定了你更新 Control 的時候執行的具體動作