owned this note
owned this note
Published
Linked with GitHub
---
tags: LinQ , C# , Projection Operators
---
# Projection Operators - Select
### [Select](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=netframework-4.8)
> Projects each element of a sequence into a new form.
上述參考使用了Projects一詞 , 代表我們可以根據資料來源序列中的項目 , 建立相對應的輸出序列. 換句話說就是 , 將集合中的每一個元素以新的形式輸出. 我覺得使用以前高中教的函數的概念可能會更好理解. 亦即
```C#
TResult y = selector(TSource x);
```
Select 會將 source 集合中的元素一一透過 selector() 這個委派去做轉換, 並在轉換後回傳結果.
### 使用時機
1. 調用一個物件與調用一個變數所消耗的成本是不同的. 假設我們只需要物件中的某幾項資訊 , 那麼我們其實可以僅取出我們需要的資訊來操作就好.
2. 我們需要對集合內全部元素做某項轉換後 , 再做操作.
### 多載形式
1. Projects each element of a sequence into a new form.
- Select<TSource,TResult>(this IEnumerable<TSource>, **Func<TSource,TResult>**)
2. Projects each element of a sequence into a new form by incorporating the element's **index**.
- Select<TSource,TResult>(this IEnumerable<TSource>, **Func<TSource,Int32,TResult>**)
##### 解釋
1. this IEnumerable<TSource>
- LinQ 的方法基本上都是在走訪集合元素的時候 , 對集合元素做出操作. 所以會對 IEnumerable<TSource> 進行擴充. 也就是透過 GetEnumerator() 與 iterator.MoveNext(), iterator.Current 來巡覽.
2. Func<TSource,TResult>
- 使用兩個泛型 , 分別代表投影前後的結果.
- Select會再走訪集合元素的時候 , 使用 Func 對元素做轉換.
3. Func<TSource,Int32,TResult>
- 多了一個 index 這個參數可以使用. 其意義就是集合內元素的 index.
- Select會再走訪集合元素的時候 , 使用 Func 對元素做轉換.
---
### Select 的用處
#### 範例 - 需要取出一群人的 BMI 數值 以及 Age
```C#
- 取出人群的函數
public static List<(string name, int age, int weight, int height)> GetPeople()
{
return new List<(string name, int age, int weight, int height)>()
{
("小王", 19, 75,172),
("小明", 29, 70,182),
("老黃", 69, 80,196),
};
}
```
##### 不使用 Select 可能會這麼寫 !?
```C#
- 取出 BMI
public static IEnumerable<double> GetBMI()
{
var people = GetPeople();
foreach (var (name, age, weight, height) in people)
{
yield return weight / Math.Pow(height / 100.0, 2);
}
}
```
```C#
- 取出 Age
public static IEnumerable<double> GetBMI()
{
var people = GetPeople();
foreach (var (name, age, weight, height) in people)
{
yield return age;
}
}
```
##### 使用 Select
```C#
- 取出 BMI
public static IEnumerable<double> GetBMI()
{
var people = GetPeople();
return people.Select(person => person.weight / Math.Pow(person.height / 100.0, 2));
}
```
```C#
- 取出 Age
public static IEnumerable<double> GetBMI()
{
var people = GetPeople();
return people.Select(person => person.age);
}
```
我們可以透過使用 Select 很輕易的從集合中**僅**取出我們所需要的資訊.
由上面是否使用 Select 的例子可以知道 , Select 替我們走訪了集合成員 , 並且透過傳入的委派 , 去對集合成員做出相對應的處理. 因此我們不必自行實作 foreach 的部分 , 僅需要傳入對應的委派即可.
### 簡單實作自己的Select
作法
1. MySelect : 負責對參數做出檢查或是判斷
2. MySelectIterator : 負責回傳 IEnumerable
將 Iterator 拆出到 MySelectIterator 去執行的原因是.
- yield return 會將函式標註為 iterator block , 也就是說會延遲執行. 但我們希望可以在設定查詢式的時候 , 立刻做出一些檢查或是判斷. e.g. 參數的檢查 , 集合Type 是 List or Array .
#### 沒有 index 的版本
```C#
public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
if (source is null || selector is null)
throw new Exception("null exception");
return MySelectIterator(source, selector);
}
private static IEnumerable<TResult> MySelectIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
foreach (var item in source)
{
yield return selector(item);
}
}
```
##### 測試程式
```C#
static void Main(string[] args)
{
List<int> nums = new List<int> { 5, 9, 8, 7 };
foreach (var num in nums.MySelect(num => num + 10))
{
Console.WriteLine(num);
}
Console.ReadKey();
}
```
##### 輸出結果
![OMJQevR.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/OMJQevR.png?raw=true)
#### 有 index 的版本
```C#
public static IEnumerable<TResult> MySelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, Int32, TResult> selector)
{
if (source is null || selector is null)
throw new Exception("null exception");
return MySelectIterator(source, selector);
}
private static IEnumerable<TResult> MySelectIterator<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, Int32, TResult> selector)
{
int index = -1;
foreach (var item in source)
{
checked //C# 關鍵字 , 用來檢查 index 是否 overflow
{
index++;
}
yield return selector(item, index);
}
}
```
##### 測試程式
```C#
static void Main(string[] args)
{
List<int> nums = new List<int> { 5, 9, 8, 7 };
var query = nums.MySelect((num, index) => num + index);
foreach (var num in query)
{
Console.WriteLine(num);
}
Console.ReadKey();
}
```
##### 輸出結果
![TuuB97D.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/TuuB97D.png?raw=true)
### 參考
[Select的原碼探險](https://ithelp.ithome.com.tw/articles/10194885)
[Source Code](https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Select.cs)
### 補充 - 使用匿名型別
#### 若是希望取出複數值?!
```C#
static void Main(string[] args)
{
List<int> nums = new List<int> { 5, 9, 8, 7 };
var query = nums.MySelect((num, index) => new { MyNum = num, MyIndex = index });
foreach (var num in query)
{
Console.WriteLine($"{num.MyIndex} - {num.MyNum}");
}
Console.ReadKey();
}
```
##### 輸出結果
![3vhJPx7.png](https://github.com/s0920832252/LinQ-Note/blob/master/Resources/3vhJPx7.png?raw=true)
---
### Thank you!
You can find me on
- [GitHub](https://github.com/s0920832252)
- [Facebook](https://www.facebook.com/fourtune.chen)
若有謬誤 , 煩請告知 , 新手發帖請多包涵
# :100: :muscle: :tada: :sheep: