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

2016年4月17日 星期日

[ASP.NET WebForm] 遞迴搭配多型實務應用

遞迴本身就是一種把問題切割成更小的問題求解

當問題切割到最小求得這個小問題的解之後

再將這些小問題的解傳回來組合成最終解答

一般經典的例子就是求費氏數列

遞迴也像俄羅斯娃娃一樣

一個娃娃裡面有一個娃娃,這個娃娃裡面又有一個娃娃…


實務上遇到一個問題

如果一個畫面的控制項某些一開始載入的時候就要設為ReadOnly

那麼應該怎麼做?

最直接的方法當然就是Page_Load()的時候開始寫

TextBox1.ReadOnly=true;
TextBox2.ReadOnly=true;
TextBox3.ReadOnly=true;
TextBox4.ReadOnly=true;

以上只有四個物件需要設定成ReadOnly

那如果有100個物件需要設定呢?

而且有不同的物件也要做不同處理呢?

當然不是真的去寫100行

身為程式設計師就要用程式設計師的方法去偷懶~

首先先聊聊WebForm的控制項特性

WebForm的控制項其實本身就是一種容器的概念

也就是一個控制項裡面可能還有其他控制像

其中的控制項裡面可能又有其他的控制項…

就像上面提到的俄羅斯娃娃一樣

所有控制項的父類別來自於Control

所以從多型(Polymorphism)的觀點來看

TextBox是Control

DropDownList也是Control

如果我們能夠取得控制項中的控制項

又能分辨出這個控制項是誰這問題就解決了

解法如下

private void SetDisable(ControlCollection Controls)
    {
        foreach (var obj in Controls)
        {
            if (obj is Control && (obj as Control).HasControls())
            {
                SetDisable((obj as Control).Controls);
            }
            if (obj is TextBox && (obj as TextBox).ID != txtMemo.ID && (obj as TextBox).ID != txtDocWord.ID)
            {
                (obj as TextBox).ReadOnly = true;
                continue;
            }
            if (obj is DropDownList && (obj as DropDownList).ID != ddlModifyCategory.ID)
            {
                (obj as DropDownList).Enabled = false;
                continue;
            }
        }
    }

SetDisable這個方法有一個傳入的引數型態就是ControlCollection

在Control類別底下就有Controls這個屬性

他代表這個物件中包含的其他控制項物件

ControlCollection就是代表了一個控制項的集合

所以我們能用foreach (var obj in Controls)去取得其中的全部子控制項物件

並且用C#的隱含型別去接從Controls得到的物件

當然也可以用Object型別去接

接下來這段也就是多型跟遞迴應用的部份

if (obj is Control && (obj as Control).HasControls())
{
    SetReadOnly((obj as Control).Controls);
}

obj is Control其中的is是C#用來檢查左邊的物件是否是右邊的型別

在此就是判斷說你取得的物件是否是Control類別的物件

如果他是Control類別的物件

那就代表他一定有Controls這個屬性

Controls本身也有提供HasControls()判斷裡面還有沒有控制項並回傳true或false

如果是Control類別的物件而且HasControls()為true就會進入該判斷式

並且再呼叫一次SetDisable傳入該物件的Controls集合

繼續檢查這個集合中的控制項是否還有子控制項

如果HasControls()為false表示沒有子控制項了

就繼續下面流程

下面流程主要也只是判斷這個物件是不是TextBox跟DropDownList

因為繼承Control的類別很多

但我們要設定的也只有TextBox跟DropDownList

因為在is的部份就已經確定是否為TextBox或DropDownList

所以後面使用as轉型一定不會失敗

之後依照型別做相對應的設定

2014年7月2日 星期三

[C#]Debugger會呼叫ToString()的問題

為了紀念一下我逝去的青春小鳥(浪費了我一整個下午)

來紀錄一下這次遇到的事件

簡單說我寫了一個組合SQL字串的class

其中裡面有InsertString、DeleteString、UpdateString、WhereString等StringBuilder的物件紀錄執行的動作

當中我override ToString()這個來自System.Object的方法

來輸出組合並輸出的字串

詭異的事情就此發生

在Visual Studio偵錯模式底下執行看程式變化時

有用過的人應該知道可以按F10或F11一步一步執行

其中你游標指到變數還可以看到變數的內容

後來我發現每指向變數一次變數(StringBuilder)的內容就會改變一次

而且用ToString()輸出後的確是有改變

之後我嘗試在ToString()中回傳前指派InsertString、DeleteString、UpdateString、WhereString都為null

沒想到居然會發生參照未指定物件的錯誤

但問題是我根本還沒呼叫ToString()這個方法

怎麼「看起來」好像有去呼叫ToString()

難道是我學藝不精?還是C#有什麼神奇魔力…?

找了半天也跟別人討論

最後才發現這個不曉得算不算bug的問題來源

一般偵錯模式底下執行時我們游標指到變數會顯示的是這個物件完整的class name

但其實這個內容是呼叫ToString()來的

可是ToString()又已經被我Override過了

所以就會發生我遇到明明沒呼叫ToString()卻會執行ToString()內容的靈異事件了

這個問題好像一直到Visual Studio 2012都還有

難道微軟不認為這是bug嗎?

後來我有嘗試方法用new但還是不行

他看來就是一定會去執行這個物件的ToString()不管你是override還是new

看起來他並非去copy出一個新物件

再去呼叫這個新物件的ToString()

所以要解決這個問題你只能取別的名字避免他莫名其妙偷偷去呼叫ToString()

可以參考以下兩篇也是苦主的文章

http://mocheng.wordpress.com/2009/06/06/%E5%88%AB%E8%AE%A9tostring%E5%87%BD%E6%95%B0%E6%94%B9%E5%8F%98%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%8A%B6%E6%80%81/
http://www.xuebuyuan.com/130963.html

2014年3月2日 星期日

[C#]將陣列切割成數個小陣列

最近又遇到一個奇妙的問題

就是對方提供的刪除資料的方法給我們

刪除小筆的資料(陣列)沒什麼問題

但是要刪除大筆資料的話就會出問題

在這種情況下最直觀的解決方法就是把原來的陣列切成數個小陣列

在一一丟進去給該方法刪除直到全部的內容刪除完畢

解法如下

int[] arr = new int[43];
Random rand=new Random(47);
for (int i = 0; i < arr.Length; i++)
{
    arr[i] = rand.Next()%100;
    Response.Write(arr[i]+" ");
}
Response.Write("<br>");

Array[] arr2 = null;

Response.Write("arr.Length=" + arr.Length + "<br>");

if (arr.Length % 20 == 0)
    arr2 = new Array[(arr.Length / 20)];
else
    arr2 = new Array[(arr.Length / 20) + 1];

for (int i = 0, index = 0; i < arr2.Length; i++)
{
    if (arr.Length - index >= 20)
    {
        arr2[i] = Array.CreateInstance(typeof(Int32), 20);
        Array.Copy(arr, index, arr2[i], 0, 20);
        index += 20;
    }
    else if (arr.Length - index > 0)
    {
        arr2[i] = Array.CreateInstance(typeof(Int32), arr.Length - index);
        Array.Copy(arr, index, arr2[i], 0, arr.Length - index);
    }
}
foreach (Array intArr in arr2)
{
    foreach (int delObj in intArr)
    {
        Response.Write(delObj+" ");
    }
    Response.Write("<br>");
}

程式在一開始亂數產生一個int陣列紀錄到arr陣列

arr2代表著新的陣列,使用Array類別當型別,代表著切割之後的每個reference指向一個一維陣列

每個新的陣列都有個長度(代表你每次傳進去刪除方法中的陣列有多長),這邊範例是20

當陣列在產生時會有兩種情況

第一你的陣列長度剛好可以被20整除,那表示你需要長度為n/20的陣列

第二你的陣列長度無法被20整除,那表示你需要長度為(n/20)+1的陣列

之後的迴圈代表將每筆資料copy至新陣列中

index代表目前指向舊陣列哪個索引

如果舊陣列長度減掉index大於等於20

那麼就用CreateInstance(typeof(Int32), 20)產生一個型態為Int32,長度為20的陣列(此時內容為空)給arr2[i]

之後用Array.Copy(arr, index, arr2[i], 0, 20);

複製arr陣列中從索引index開始到arr2[i] 中,長度為20的內容

完成後再將index加上20代表就陣列的索引往後移動20指到新的開頭

持續這個動作一直到arr.Length - index沒有大於等於20

則利用arr.Length - index判斷目前還剩下幾個元素未copy

測試範例如下


用這方法是真的有把問題解決了

但這其實不是最好的解法

其實我想的是能否直接將reference指到新的陣列而非用copy

畢竟用copy表示系統中會出現兩筆一模一樣的資料

copy的陣列越大消耗的系統資源也越大

也許是我對C#還不夠熟悉

如果有想到更好的解法再貼上來吧~