LINQ筆記20180623

LINQ筆記

※NoLINQ

如何改為最大可維護性

※NoLINQ與LINQ

效能差距

※迭代子

迭代子只往前走不往後走(foreach作法)

※LIKE & IN

※OrderBy&ThenBy

※First&Last

※Single

※ElementAt、ElementAtOrDefault

※取部分集合類函式

※Let

※Into

※DefaultIfEmpty

※Take & Skip

※TakeWhile & SkipWhile

※Except & Intersect 

※NoLINQ

思考:

如何改為最大可維護性

第一步:

Sub Function
/// <summary>
/// Sub Function
/// </summary>
/// <param name="datasource"></param>
/// <param name="findItem"></param>
/// <returns></returns>
static int Filter(int[] datasource, int findItem)
{
	foreach (var item in datasource)
	{
		return item;
	}

	return -1;
}

第二步:

泛型
/// <summary>
/// 第二步:泛型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datasource"></param>
/// <param name="findItem"></param>
/// <returns></returns>
static T Filter<T>(T[] datasource, T findItem)
{
	foreach (var item in datasource)
	{
		if (item.Equals(findItem))
		{
			return item;
		}
	}
	return default(T);
}

第三步:

Delegate
/// <summary>
/// 第三步:Delegate
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datasource"></param>
/// <param name="findItem"></param>
/// <returns></returns>
static void Filter<T>(T[] datasource, T findItem, Action<T> processor)
{
	foreach(var item in datasource)
	{
		if(item.Equals(findItem)){
			processor(item);
		}
	}
}

第四步:

抽離判斷式
static bool Prediction<T>(T item, Func<T, bool> func)
{
    return func(item);
}

第五步:

抽離迴圈 
/// <summary>
/// 第五步:抽離迴圈
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="datasource"></param>
/// <param name="findItem"></param>
/// <returns></returns>
static List<T> Filter<T>(List<T> datasource, Func<T, bool> func)
{
	var result = new List<T>();
	foreach(var item in datasource)
	{
		if (func(item))
		{
			result.Add(item);
		}
	}

	return result;
}

使用 設計模式:樣板方法模式(Template Method Pattern)

缺點 List<T> 占用記憶體

更改方式:使用 Enumerable 跟 Enumerator 方式可以解決這個問題

///// <summary>
///// 第六步:LIST<T>更改為IEnumerable
///// </summary>
///// <typeparam name="T"></typeparam>
///// <param name="datasource"></param>
///// <param name="findItem"></param>
///// <returns></returns>
public static IEnumerable<T> Filter<T>(T[] source, Func<T,bool> func)
{
	foreach (var item in source)
	{
		if (func(item))
		{
			yield return item;
		}
	}
}

Extension Method

1.Static Class

2.Static Method

3.使用端必須引用該 static 類別的 namespace 

public static class Filter
{
	public static IEnumerable<T> FilterMember<T>(this T[] source, Func<T, bool> func)
	{
		foreach (var item in source)
		{
			if (func(item))
			{
				yield return item;
			}
		}
	}
}

※NoLINQ & LINQ

NoLINQ與LINQ效能(差別在於呼叫Delegate的次數)

static void UseLinq()
{
	var data = new List<int>();
	Random r = new Random((int)DateTime.Now.Ticks);
	for (int i = 0; i < 100000; i++)
	{
		data.Add(r.Next(1, 1000));
	}
	data.Add(99999);
	Stopwatch sw = new Stopwatch();
	sw.Start();
	//會較慢的原因,是因為Delegate(被呼叫很多次)
	var result = data.Where((s) => s == 99999).FirstOrDefault();
	sw.Stop();
	Console.WriteLine($"{result} {sw.ElapsedMilliseconds}");
}

印出結果:99999 5

static void UseNonLinq()
{
	var data = new List<int>();
	Random r = new Random((int)DateTime.Now.Ticks);
	for (int i = 0; i < 100000; i++)
	{
		data.Add(r.Next(1, 1000));
	}
	data.Add(99999);
	Stopwatch sw = new Stopwatch();
	sw.Start();
	foreach(var item in data)
	{
		if(item == 99999)
		{
			sw.Stop();
			Console.WriteLine($"{item} {sw.ElapsedMilliseconds}");
			break;
		}
	}
}

印出結果:99999 0

## string[]實作的interface

////string[]實作IEnumerable
string[] strArray = new string[] { "aaaaa", "bbbbb" };
Type[] types = strArray.GetType().GetInterfaces();
foreach (var item in types)
{
	Console.WriteLine(item);
}

 

※迭代子

迭代子只往前走不往後走(foreach作法)

1.跑過的就可以丟掉,不佔用記憶體

2.後面可以不取

IEnumerator iterator = dataArray.GetEnumerator();
while (iterator.MoveNext())
{
	Console.WriteLine(iterator.Current);
}
 var dataArray = new int[] { 1, 2, 3, 5, 6, 8 }; 
 /// 兩個WHERE但只跑一個迴圈(Base On IEnumerator)
 var r = dataArray.Where(item => item > 2).Where(item => item > 9).FirstOrDefault();

LINQ Base on Enumerator

迭代子適用於未知大小清單、也適用於Delay loading(延遲載入)

在網路環境下,容易做到分批讀取(EX:分頁)

所有C#都會兩段式編譯

(C# 撰寫的原始程式碼會編譯成符合 CLI 規格的中繼語言 (IL))

※LIKE & IN

LIKE(是string的方法 Contains() )

string[] data = { "aaaa", "bbbb", "cccc" };
var result = from s1 in data
             where s1.Contains("b")
             select s1;
foreach (var item in result)
{
   Console.WriteLine(item);
}

IN(LINQ的擴充方法Contains())

string[] data = { "aaaa", "bbbb", "cccc" };
string[] infilter = { "bbbb", "cccc" };
var result = from item in data
			 where infilter.Contains(item)
			 select item;
foreach (var item in result)
{
	Console.WriteLine(item);
}

## LINQ是後面的語法向前面要資料

※OrderBy & ThenBy

OrderBy(迴圈,會先抓取所有元素比較再排序) => 會影響效能

OrderBy會判斷前後的LINQ

OrderBy再OrderBy(是兩個迴圈)

OrderBy再ThenBy(判斷後面要資料的是否為ThenBy=>若為ThenBy則會結合OrderBy為一個迴圈跑完)

※First & Last

FirstOrDefault & LastOrDefault(沒有資料會回傳有效值)

int實作List,因此會直接取得最後一筆,因實作List會找到最後索引值

​多數情況下會從前面找到後面,再取得最後一筆。

Last & LastOrDefault =>

Eentity Framework不支援,因為SQL Server 沒有Bottom這個指令可以使用

var data = new List<int>();
var r = new Random((int)DateTime.Now.Ticks);
for (int i = 0; i < 1000000; i++)
{
	data.Add(r.Next(1, 100));
}
Stopwatch sw = new Stopwatch();
sw.Start();
//IEnumerable
var result = data.Where(a => a > 3).LastOrDefault();
sw.Stop();
Console.WriteLine($"{result} {sw.ElapsedMilliseconds}");

印出結果:67 43

var data = new List<int>();
var r = new Random((int)DateTime.Now.Ticks);
for (int i = 0; i < 1000000; i++)
{
	data.Add(r.Next(1, 100));
}
var list = data.Where(a => a > 3).ToList();
Stopwatch sw = new Stopwatch();
sw.Start();
//List
var result = list.LastOrDefault();
sw.Stop();
Console.WriteLine($"{result} {sw.ElapsedMilliseconds}");

印出結果:64 0

※Single

取得單一

※ElementAt、ElementAtOrDefault

利用索引值取得資料

※取部分集合類函式
最常使用SELECT取得部分資料。

//可取出索引子
var result = dataArray.Select((s, index) => $"{s},{index}");
foreach (var item in result)
{
	Console.WriteLine(item);
}
//WHERE Index 從0開始
var result = dataArray.Where(item=>item > 3).Select((s, index) => $"{s},{index}");
foreach (var item in result)
{
	Console.WriteLine(item);
}

※SelectMany

若有巢狀迴圈值可思考是否可改成SelectMany

var dep1 = new Department()
{
	Name = "IT",
	Persons = new List<Person>()
	{
		new Person()
		{
			Name = "code6421",
			Age = 10
		},
		new Person()
		{
			Name = "mary",
			Age = 11
		}
	}
};
var dep2 = new Department()
{
	Name = "RD",
	Persons = new List<Person>()
	{
		new Person()
		{
			Name = "mark",
			Age = 12
		}
	}
};

List<Department> deps = new List<Department>() { dep1, dep2 };
foreach(var dev in deps)
{
	foreach(var person in dev.Persons)
	{
		Console.WriteLine($"{person.Name}");
	}
}

SelectMany可省一個迴圈

foreach (var item in deps.SelectMany(item => item.Persons))
{
	Console.WriteLine($"{item.Name}");
}

 

※Let

//Console中印出結果
foreach (var item in FindInFilesLinq("C:\\Windows","driver"))
{
	Console.WriteLine(item);
}

//直覺函式
static List<string> FindInFiles(string directory,string query)
{
	var result = new List<string>();
	foreach(var item in Directory.GetFiles(directory, "*.ini"))
	{
		var content = File.ReadAllText(item);
		if (content.Contains(query))
		{
			result.Add(item);
		}
	}
	return result;
}


//改為LINQ函式

static List<string> FindInFilesLinq(string directory, string query)
{
	var files = Directory.GetFiles(directory, "*.ini");
	var result = files
		.Where(item => File.ReadAllText(item).Contains(query));
	return result.ToList();
}

※Into

左邊的結果往右邊拋

※DefaultIfEmpty(LeftJoin)

當集合為空時,又需要右邊資料時將其塞空值丟回,其餘資料即可印出

※Take & Skip(可做分頁)

Skip & Take 是以Element為主,從"1"開始

※TakeWhile & SkipWhile(很少用)

※Except & Intersect(常用,EX:分析使用者流存率報表)

Except(排除相同元素)

Intersect(取得相同元素)