善用語言特性與 LINQ

if elseswitch case 是最基本的邏輯判斷方式,但卻也是 複雜度 的元兇,實務上善用 C# 語言特性與 LINQ,可以降低複雜度,讓程式碼可讀性更高,也更容易維護。

Version


C# 7.2

IEnumerable.Contains()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Console.WriteLine(Func("Apple Watch"));
}

static bool Func(string product)
{

if (product == "iPhone" || product == "iPad" || product == "Apple Watch" || product == "Macbook")
{
return true;
}

return false;
}
}
}

常見的需求,|| 若其中一個條件就成立就回傳值。

complex005

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Console.WriteLine(Func("Apple Watch"));
}

static bool Func(string product)
{

var apples = new List<string>
{
"iPhone",
"iPad",
"Apple Watch",
"Macbook"
};

return apples.Contains(product);
}
}
}

IEnumerable.containes() 的原意是判斷 item 是否在 IEnumerable 中,若有則回傳 true,否則傳回 false

利用 contains() 這個特性,可將所有要判斷的項目改寫在 List 中,改用 contains() 判斷,不只清爽,可讀性也很高。

complex002

Guard Clause


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Func("iPad", 20);
}

static void Func(string product, int quantity)
{

var apples = new List<string>
{
"iPhone",
"iPad",
"Apple Watch",
"Macbook"
};

if (!String.IsNullOrEmpty(product))
{
if (apples.Contains(product))
{
Console.WriteLine($"{product} is Apple product");

if (quantity > 10)
{
Console.WriteLine("big quantity");
}
}
}
else
{
throw new Exception("No Apple product");
}
}
}
}

若都使用 正向判斷,可能會造成 nested if else 而難以維護。

complex006

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Func("iPad", 20);
}

static void Func(string product, int quantity)
{

var apples = new List<string>
{
"iPhone",
"iPad",
"Apple Watch",
"Macbook"
};

if (String.IsNullOrEmpty(product))
{
throw new Exception("No Apple product");
}

if (!apples.Contains(product))
{
return;
}

Console.WriteLine($"{product} is Apple product");

if (quantity > 10)
{
Console.WriteLine("big quantity");
}
}
}
}

可使用 Guard Clause 將反向邏輯提早 return,讓所有 if 判斷都扁平化只有一層,這也是為什麼有些 Lint 會警告你不能寫 else,因為使用 Guard Clause 之後,就不會再出現 else 了。

complex007

IEnumerable.All()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Console.WriteLine(Func("white"));
}

static bool Func(string color)
{

var apples = new Dictionary<string, string>
{
{"iPhone", "white"},
{"iPad", "black"},
{"Macbook", "silver"}
};

bool isSame = true;

foreach (var product in apples)
{
if (!isSame) break;

isSame = product.Key == color;
}

return isSame;
}
}
}

若要 全部條件 都成立,實務上我們也會將資料與條件全部先放在 Dictionary 中。

最直覺方式就是透過 foreach 判斷。

complex010

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Console.WriteLine(Func("white"));
}

static bool Func(string color)
{

var apples = new Dictionary<string, string>
{
{"iPhone", "white"},
{"iPad", "black"},
{"Macbook", "silver"}
};

return apples.All(x => x.Value == color);
}
}
}

若要 全部條件 都成立,可使用 IEnumerable.All(),則所有條件都為 true 才會回傳 true

complex000

IEnumerable.Any()


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Console.WriteLine(Func("white"));
}

static bool Func(string color)
{

var apples = new Dictionary<string, string>
{
{"iPhone", "white"},
{"iPad", "black"},
{"Macbook", "silver"}
};

return apples.Any(x => x.Value == color);
}
}
}

若只要 有一個條件 成立即可,可使用 IEnumerable.Any()

complex001

Dictionary


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Linq;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Func("white").ToList()
.ForEach(Console.WriteLine);
}

static string[] Func(string color)
{
switch (color)
{
case "white":
return new[]
{
"iPhone",
"iPad"
};
case "silver":
return new[]
{
"Macbook",
"Apple Watch"
};
case "black":
return new[]
{
"Apple TV",
"Mac Pro"
};
default:
return new string[0];
}
}
}
}

switch case 在實務上也難以避免。

complex003

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Func("white").ToList()
.ForEach(Console.WriteLine);
}

static string[] Func(string color)
{
var apples = new Dictionary<string, string[]>
{
{"white", new[] {"iPhone", "iPad"}},
{"silver", new[] {"Macbook", "Apple Watch"}},
{"black", new[] {"Apple TV", "Mac Pro"}}
};

apples.TryGetValue(color, out var value);
return value ?? new string[0];
}
}
}

將所有條件放進 Dictionary

valuenull,表示沒找到,則傳回 empty array。

TryGetValue() 可避免 Dictionary 找不到時拋出 KeyNotFoundException

complex004

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;

namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{

Func("white").ToList()
.ForEach(Console.WriteLine);
}

static IEnumerable<string> Func(string color)
{

var apples = new Dictionary<string, string>
{
{"iPhone", "white"},
{"iPad", "white"},
{"Macbook", "silver"},
{"Apple Watch", "silver"},
{"Apple TV", "black"},
{"Mac Pro", "black"}
};

return apples
.Where(x => x.Value == color)
.Select(x => x.Key);
}
}
}

另外一種使用 Dictionary 的方式,將所有條件都放在 Dictionary 中,然後使用 LINQ 的 Where()Select() 塞選資料。

complex009

Conclusion


  • 並不是所有的判斷都只能用 if elseswitch case,透過一些 C# 的語言特性與 LINQ,可以有效降低程式碼複雜度

Reference


Jecelyn Yeen, 5 Tips to Write Better Conditionals in JavaScript

2018-12-02