C# if foreach

減少程式中if,foreach疊加造成閱讀不易情形

劉仁竹 2020/12/21 18:40:04
1980

最近在維護程式,常常看到不斷在使用ifforeach疊加,造成整體程式像在爬山一樣,不知道何時才會到頂點

 

我會建議程式一個方法method中有ifforeach疊加不要超過三層,這裡提供幾個方法簡單減少狀況:

1.利用return

2.另外包成method

3.使用linQ

 

範例1

    protected void Page_Load(object sender, EventArgs e)
    {
        string alertMsg = "";
        string errorMsg = "";
        if (Request.Form["email"] != null)
        {
            string email = checkValue(Request.Form["email"], ref errorMsg);
            if (errorMsg == "")
            {
                
                List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
                if (messageData.Count > 0)
                {
                    alertMsg = messageData.Error;
                }
                else
                {
                    var parameters = new
                    {
                        Mail = email,
                    };
                    using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
                    {
                        if (memberData != null && errorMsg == "")
                        {
                            if (memberData.Count > 0)
                            {
                                //寄信
                                String mailResult = SendMail(...);
                                if (mailResult == "OK")
                                {
                                    alertMsg = "通信寄出";
                                }
                            }
                            else
                            {
                                alertMsg = "查無資料";
                            }
                        }
                        else
                        {
                            alertMsg = "查無資料";
                        }
                    }
                }
            }
            else
            {
                alertMsg = errorMsg;
            }
        }
    }
    private string checkValue(string inputValue, ref string errorMsg)
    {
        errorMsg = "";
        if (string.IsNullOrEmpty(inputValue))
        {
            errorMsg = "空白或null";
            return "";
        }
        return inputValue;
    }

    public List<T> GetDataFromSQLParam<T>(string msql, object parameters, ref string mmsg)
    {
        List<T> results = null;
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            mmsg = "";
            try
            {
                var ts = connection.Query<T>(msql, parameters);
                results = ts.ToList();
            }
            catch (Exception exception)
            {
                mmsg = "DB error:" + exception.Message;
            }
        }
        return results;
    }

當我們在思考流程撰寫程式時,上述的範例是大腦最直覺的寫法,這樣並沒有錯,只是後續在維護時,在閱讀第幾層if時會非常不易。

 

先看第一層if,這邊可利用return改寫成

    if (Request.Form["email"] == null)
	{
		return;
	}
	string email = checkValue(Request.Form["email"], ref errorMsg);
	if (errorMsg == "")
	...

並將原本包在第一層if裡面程式往外搬移,同理將剩下的相同的邏輯用這樣方式處理,可以得到結果為

        if (Request.Form["email"] == null)
        {
            return;
        }

        string email = checkValue(Request.Form["email"], ref errorMsg);
        if (errorMsg != "")
        {
            alertMsg = errorMsg;
            return;
        }

        List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
        if (messageData.Count > 0)
        {
            alertMsg = messageData.Error;
            return;
        }

        var parameters = new
        {
            Mail = email,
        };
        using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
        {
            if (memberData != null && errorMsg == "")
            {
                if (memberData.Count > 0)
                {
                    //寄信
                    String mailResult = SendMail(...);
                    if (mailResult == "OK")
                    {
                        alertMsg = "通知信寄出";
                    }
                }
                else
                {
                    alertMsg = "查無資料";
                }
            }
            else
            {
                alertMsg = "查無資料";
            }
        }

此時會碰到一個使用using的狀況,這邊就會建議將using底下另外包成method

    protected void Page_Load(object sender, EventArgs e)
    {
        string alertMsg = "";
        string errorMsg = "";
        if (Request.Form["email"] == null)
        {
            return;
        }

        string email = checkValue(Request.Form["email"], ref errorMsg);
        if (errorMsg != "")
        {
            alertMsg = errorMsg;
            return;
        }

        List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
        if (messageData.Count > 0)
        {
            alertMsg = messageData.Error;
            return;
        }

        var parameters = new
        {
            Mail = email,
        };
        using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
        {
            alertMsg = ProccessDataView(memberData, errorMsg);
        }

    }

    private string ProccessDataView(List<MemberData> memberData, string errorMsg)
    {
        string returnAlertMsg = "";
        if (memberData != null && errorMsg == "")
        {
            if (memberData.Count > 0)
            {
                //寄信
                String mailResult = SendMail(...);
                if (mailResult == "OK")
                {
                    returnAlertMsg = "通知信寄出";
                }
            }
            else
            {
                returnAlertMsg = "查無資料";
            }
        }
        else
        {
            returnAlertMsg = "查無資料";
        }
        return returnAlertMsg;
    }

接下來,繼續使用return方式處理ProccessDataView方法裡的參數,這邊有一個小細節可以處理,if判斷式中是有順序的由左至右讀取的,也就是說從左邊第一個判斷式可以得知truefalse時,就不會再讀剩下的判斷式

    protected void Page_Load(object sender, EventArgs e)
    {
        string alertMsg = "";
        string errorMsg = "";
        if (Request.Form["email"] == null)
        {
            return;
        }

        string email = checkValue(Request.Form["email"], ref errorMsg);
        if (errorMsg != "")
        {
            alertMsg = errorMsg;
            return;
        }

        List<MessageData> messageData = GetDataFromSQLParam<MessageData>("select ...", null, ref errorMsg);
        if (messageData.Count > 0)
        {
            alertMsg = messageData.Error;
            return;
        }

        var parameters = new
        {
            Mail = email,
        };
        using (List<MemberData> memberData = GetDataFromSQLParam<MemberData>("select ...", parameters, ref errorMsg))
        {
            alertMsg = ProccessDataView(memberData, errorMsg);
        }

    }

    
    private string ProccessDataView(List<MemberData> memberData, string errorMsg)
    {
        string returnAlertMsg = "";
        if (memberData == null || errorMsg != "" || memberData.Count == 0)
        {
            returnAlertMsg = "查無資料";
            return returnAlertMsg;
        }

        //寄信
        String mailResult = SendMail(...);
        if (mailResult == "OK")
        {
            returnAlertMsg = "通知信寄出";
        }

        return returnAlertMsg;
    }

這邊處理完後,你就會發現,萬一寄信失敗是不會有任何回傳的,但你從最開始的程式很難判斷有缺少錯誤回傳

 

範例2

    protected string LayoutTypeSot()
    {
        IDictionary<string, string> list = new Dictionary<string, string>();
        list.Add("A", "Apple");
        list.Add("B", "banana");
        list.Add("C", "Cherry");
        list.Add("D", "Date");
        list.Add("E", "");
        list.Add("", "");

        string layoutOther = "";
        string layoutA = "";
        foreach (var item in list)
        {
            if (item.Value != "")
            {
                if (item.Key != "A")
                {
                    layoutOther += layoutStringOther(item.Key, item.Value);
                }
                else
                {
                    layoutA += layoutStringA(item.Key, item.Value);
                }
            }
        }

        return layoutA  + layoutOther;
    }
    private string layoutStringOther(string key, string name)
    {
        return string.Format("{0} 不是A開頭水果:{1} \r\n", key, name);
    }

    private string layoutStringA(string key, string name)
    {
        return string.Format("{0} 開頭水果:{1} \r\n", key, name);
    }

回傳結果:

A 開頭水果:Apple 

B 不是A開頭水果:banana 

C 不是A開頭水果:Cherry 

D 不是A開頭水果:Date 

 

第二個範例是很常見到的情形,要在一串資料中區分不同種類並排除空值狀況,在這個情況下,我會建議使用linQ來做簡化的程式的動作,我們第一步也是處理第一個判斷式。

    var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));

	string layoutOther = "";
	string layoutA = "";
	foreach (var item in filterNullEmptys)
	{
		if (item.Key != "A")
		{
			layoutOther += layoutStringOther(item.Key, item.Value);
		}
		else
		{
			layoutA += layoutStringA(item.Key, item.Value); ;
		}
	}

利用linq where 條件方式過濾掉掉所有空值或null狀況,接下我們可以再利用where方式區分成A及非A狀況

    var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));
    var isADatas = filterNullEmptys.Where(x => x.Key == "A");
    var otherDatas = filterNullEmptys.Where(x => x.Key != "A");

這時候我們發現,需要處理的資料被拆成兩個,所以需要兩個迴圈處理

        var filterNullEmptys = list.Where(x => !string.IsNullOrEmpty(x.Key) && !string.IsNullOrEmpty(x.Value));
        var isADatas = filterNullEmptys.Where(x => x.Key == "A");
        var otherDatas = filterNullEmptys.Where(x => x.Key != "A");

        string layoutOther = "";
        string layoutA = "";

        foreach (var itemA in isADatas)
        {
            layoutA += layoutStringA(itemA.Key, itemA.Value); ;
        }

        foreach (var item in otherDatas)
        {
            layoutOther += layoutStringOther(item.Key, item.Value);
        }

從一開始的三層,最後處理完只剩一層,也省略掉使用if判斷式

上面兩個範例中if的else完全不見,在許多程式介紹書中,會強烈建議不要輕易地用else,除非你很確認你的else夠簡單

 

 

程式寫法有很多種,沒有絕對的對與錯,只是要依據專案而定,這邊在可允許下,將程式寫的方便易讀,最好的狀況就是一個月再回來看程式的時候,你還記得你寫了甚麼。

 

劉仁竹