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

2024年8月12日 星期一

[C#/Memory] managed記憶體快照裡的ClassName+<>c是什麼?

 前言:

有一次在記憶體快照中發現某個ClassName+<>c的物件,如下圖紅框處:



第一次看到有點不知所以,查了一下,原來是compiler建立用來執行lambda的class物件。

舉個例子,寫了如下圖的程式(用到lambda):













看了IL code,的確compiler在我的Program類別內多建了一個<>c類別(如下圖),
這個類別還包含:

  • 1個<>c型別的靜態唯讀field(<>9)
  • 1個Func委派的靜態的field(<>9__0_0)
  • 1個靜態的建構子
  • 1個預設建構子
  • 1個符合Func委派簽章的方法(b__0_0)










所以managed記憶體內才會出現Progarm+<>c的物件,但compiler為何需要這麼做?

原因是效能考量,將lambda的delegate宣告成static,免去重複建立instance的effort,

就像類別內的static method。


To be continued...


參考資料:

https://blog.jetbrains.com/dotnet/2019/01/23/c-classes-memory-snapshots/

2023年8月9日 星期三

[C#] 沒有取消訂閱(unsubscribe)事件(event)會導致memory leak?

 Publisher物件可提供事件供其他subscriber物件做訂閱,且一般也會在適當的時機點取消訂閱,

但如果只訂閱但沒有取消訂閱,會讓程式memory leak嗎?


我將情境分為以下4種:

1. Publisher生命週期較subscriber長,subscriber有取消訂閱。

2. Publisher生命週期較subscriber長,subscriber沒有取消訂閱。

3. Publisher生命週期較subscriber短,subscriber有取消訂閱。

4. Publisher生命週期較subscriber短,subscriber沒有取消訂閱。


Publisher使用下圖的Server類別:

Subscriber使用下圖的Log類別,Log訂閱Server的ServerConnected事件:


以下依序列出測試結果,這裡使用VS內建的記憶體快照來確認物件是否已GC:

1. Publisher生命週期較subscriber長,subscriber有取消訂閱:

    可以看到Log物件(subscriber)已經被GC了。

2. Publisher生命週期較subscriber長,subscriber沒有取消訂閱:

    可以看到Log物件(subscriber)還存在記憶體。

3. Publisher生命週期較subscriber短,subscriber有取消訂閱:

4. Publisher生命週期較subscriber短,subscriber沒有取消訂閱:

情境3跟4,都是Server物件先回收,Log物件還留著的情形,記憶體快照也是一樣結果,
代表publisher先回收的話,subscriber沒有取消事件訂閱,也沒有關係:


結論是,subscriber還是要在適當時機點取消事件訂閱,如同情境2的結果,訂閱長生命週期的事件,是會造成memory leak的。

2022年12月25日 星期日

[C#] 讓event handler method不因event多次觸發而重複執行

 前言:

最近遇到一個情境,物件的某個event trigger會多次,

但訂閱的event handler method只需要處理該event trigger的第一次,

該怎麼做呢?


作法:

如下圖的Document類別,有一個Loaded事件,當呼叫Open方法時會觸發Loaded事件。


client端訂閱該Loaded事件,但只需要針對第一次觸發做處理,也就是讓event handler method只執行一次。因為event是把delegate清單wrap起來,
因此再加入第2個delegate將上一個event handler移除事件訂閱。

可以看到雖然Loaded事件觸發2次,但訂閱的event handler method只執行1次。


2020年2月29日 星期六

[.Net/C#] C#中List與Array的關係(relationship between List and Array)

前言:
有一次聽到同事A跟同事B說,這裡不要用List,
用Array效能比較好,因為是直接存取記憶體。

當下覺得有點怪怪的,因為就我所知,
該程式是宣告固定長度的List,那跟宣告為Array不是一樣嗎?


從list.cs的Source code可以看到:
宣告固定長度的List,內部的確是用Array宣告。

正確的說,使用List其實就是在操作Array,
所以資料都是在記憶體上。

照這樣看來,宣告固定長度的List跟宣告Array,
在搜尋效能上,應該是一樣的?
簡單來測試一下:

結果跟我想的不一樣...


使用Array「大量」存取時,執行時間少了一半,
推測是因為List在存取Count跟使用indexer取得元素時,有做了一些檢查,
即使時間複雜度一樣,但step count多了一些,
這樣看來,如果是固定長度還是直接用Array,
會變動元素,當然就是List的強項了。


參考資料:

2019年5月24日 星期五

[.Net] 如何複製instance(deep clone)/Prototype Pattern(原型模式)

前言:
有時候需要將物件instance複製出來,
.Net有提供ICloneable介面,以實作複製的方法(實作Clone方法):




而object有提供一擴充方法MemberwiseClone,以執行淺層複製(shallow copy),
但通常要的不是淺層複製,因reference type會指向同一塊heap,
因此若需要完全獨立的instance,就要自己實作。


作法:
1. 自行回傳新建立的instance:
若物件屬性可在建構子就完全決定的話,直接回傳新的instance即可:



不過通常沒這麼簡單,因此可先呼叫MemberwiseClone得到淺層複製,
再自行建立其他reference type屬性的instance:



不過如果物件屬性很多、很複雜又很多層,可考慮下一個做法。

2. 使用Json的Serialize與Deserialize:



不過作法2的執行時間應該比作法1長?? 實際測試一下:


各執行5次,作法2執行時間都在7秒上下,而作法1只要140毫秒,
因此2種作法各有所長,看狀況使用了。


參考資料:
https://docs.microsoft.com/zh-tw/dotnet/api/system.icloneable?view=netframework-4.8
https://docs.microsoft.com/zh-tw/dotnet/api/system.object.memberwiseclone?view=netframework-4.8#System_Object_MemberwiseClone
https://stackoverflow.com/questions/78536/deep-cloning-objects

2018年3月31日 星期六

[.Net] HttpRuntime.Cache.Insert中absoluteExpiration與slidingExpiration可共存??

說明:
MSDN查HttpRuntime.Cache.Insert說明時,看到下面這段文字:

如果沒搞錯意思,應該是說在slidingExpiration設個參數(大於0),在absoluteExpiration設為DateTime.Now.Add(該參數),可以有如上說明的效果。

但不是說absoluteExpiration與slidingExpiration不能共存嗎???

有看沒有懂,實際測試看看(.Net framework 4.5.2 + MVC 5.2.3):


實際執行,出現ArgumentException,
看起來absoluteExpiration與slidingExpiration不能同時生效。
後來使用.Net framework4 + WebForm測試,也是一樣的Exception。


資料來源:
https://msdn.microsoft.com/en-us/library/4y13wyk9(v=vs.110).aspx

2017年5月30日 星期二

[.Net/C#] foreach如何存取集合(Iterator/IEnumerable/IEnumerator/yield return)

說明:
foreach是用來對集合元素做巡覽/列舉的動作,透過Iterator來實作,
而Iterator是一種模式,在不暴露集合類別下,提供對集合類別依序巡覽的方法,
在.Net裡,已經將Iterator包好了,實作IEnumerable與IEnumerator即可。


什麼是IEnumerable?看下圖可知實作此介面提供一方法GetEnumerator(),
讓我們可以取得集合元素。



那IEnumerator呢? 提供實作iterator所需的方法與屬性。



也就是使用foreach in,編譯器會去找要巡覽的集合類別裡的GetEnumerator(),
然後執行IEnumerator內的MoveNext(),若回傳True,再取Current的值,
直到MoveNext回傳為False。


C# 1.0實作:
想巡覽Book集合類別,需實作IEnumerable與IEnumerator。







C# 2.0提供IEnumerable與IEnumerator的泛型,
可傳入巡覽元素的型別,減少裝箱與型別轉換的問題,
(因Current非泛型版本回傳值為object)。





C#2.0更提供yield return免除自己實作IEnumerator的困擾,
編譯器看到yield return會自動建立IEnumerator類別並回傳IEnumerator型別。



最後一樣可巡覽集合類別。


2017年4月4日 星期二

[C#] 使用繼承與調用基底類別建構子與使用new隱藏的注意事項

說明:
建立一Engineer類別繼承一抽象類別Employee(範本模式)。




當Engineer調用Employee的建構子時, 程式的執行順序會是先進入Employee建構子的區塊,再進入Engineer的建構子。


使用new隱藏修飾子,多型建立instance,會執行基底類別的同名方法,如sbc.GetSalary()會執行Employee的GetSalary方法。

2017年2月28日 星期二

[C#/Modbus] 使用NModbus4讀取Modbus slave的數值(float)

說明:
讀取Modbus slave的input register,1個register為2 bytes,
因此要得到一個float數值,需2個registers。
而Modbus資料排列為big-endian格式,需轉換為little-endian以得到正確數值。




另IEEE-754下float有效位數為7-8位,若寫入slave數值超過有效位數,會造成誤差,
而C#的float有效數值為7位。


參考資料:
https://www.h-schmidt.net/FloatConverter/IEEE754.html
http://ftp.icpdas.com.tw/pub/cd/8000cd/napdos/modbus/nmodbus/nmodbus_api_manual_v1.2_cht.pdf
https://www.mesta-automation.com/modbus-with-c-sharp-libraries-examples/

2017年2月7日 星期二

[.Net/C#] Multi-Thread使用lock造成Deadlock的狀況

使用多執行緒時,常會使用lock,防止不同執行緒對共享參數存取造成非預期的結果。
如:
設備開關控制,thread A要對設備送出open指令,
此時thread B啟動,要對設備送出close指令,
但thread B先執行完畢,最後設備仍然是open的。

因此,可使用lock對設備狀態進行鎖定,使執行緒同步化,
但使用lock可能因兩個thread互相等待造成Deadlock。
如:
worker thread異動資料後,觸發事件後,由UI thread取得該資料來顯示,
就造成thread互相等待而死結。


另外,lock也需針對個別變數使用不同的object,避免單核心CPU進行context switch後造成死結情形。

2016年12月12日 星期一

[C#] 使用EventHandler<T>簡化事件的撰寫

說明:
傳統自行撰寫事件,需先定義委派,使用EventHanler<T>本身就是委派,又加入泛型(傳入事件資料的型別),使得定義方法簽章更為方便。


實作: