重學c#系列——linq(1) [二十七]

2022-12-12 06:00:25

前言

簡單介紹一下linq,linq很多人其實用的很熟練了,但是有些人不知道自己用的是linq。

正文

在介紹linq 之前,先介紹一下集合。

  public interface ICollection<T> : IEnumerable<T>, IEnumerable
  {
    int Count { get; }

    bool IsReadOnly { get; }

    void Add(T item);

    void Clear();

    bool Contains(T item);

    void CopyTo(T[] array, int arrayIndex);

    bool Remove(T item);
  }

什麼是集合呢?在c# 中擁有上面功能的就是集合。

這裡面可以看到集合繼承了IEnumerable 這個介面。

繼承這個介面意味集合可以列舉的,也就是簡單來說可以遍歷的。

那麼c# 中的集合是否一定要繼承ICollection。

這個就不一定,比如說字典,字典在現實生活中明顯是一組集合吧,那麼在c# 中也應該是即可。

為什麼字典不繼承ICollection,原因也很簡單,因為void Add(T item); 對字典來說沒有意義。

字典是key 和 value 這種模式,ICollection 是無法滿足很多集合需求的,因為集合的多樣性太多了(非常豐富)。

那麼在c# 中什麼樣的是集合呢? 繼承IEnumerable的就是集合。

如果要資料初始化的話,那麼要增加Add方法。

public class TestCollection<T> : IEnumerable<T>
{
	public void Add(T a)
	{
		
	}

	public void Add(List<T> a)
	{
		
	}

	public IEnumerator<T> GetEnumerator()
	{
		throw new NotImplementedException();
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return GetEnumerator();
	}
}

這裡有兩個add,一個增加T,一個增加T列表。

internal class Program
{
	static void Main(string[] args)
	{
		List<string> l = new List<string>() { "1", "2", "3"};
		TestCollection<string> a = new TestCollection<string>()
		{
			"123",
			l
		};
	}
}

這樣就ok了。

我們知道實現了Ienumerable的可以進行遍歷。

因為IEnumerable 返回了一個 IEnumerator GetEnumerator();。

迭代器就是while,然後MoveNext,然後再Current,獲取資料。

這裡有些人可能會有一個很小的問題,那就是為什麼不直接實現迭代器,而是有一個IEnumerable,裡面有一個IEnumerator GetEnumerator();。

原因很簡單,因為遍歷每次都是從頭開始,都是一個新的開始。

因為IEnumerator 繼承 IDisposable, 如果需要每次遍歷完做某些事情的話,可以放在IEnumerator的void Dispose();中實現。

所以foreach 實際上是幹了這樣一件事。

那麼foreach 是否一定要繼承IEnumerable 呢? 這個不一定。

foreach 採用一個duck typing的這種方式,duck typing是什麼意思呢?就是走起來想一隻鴨子,然後叫起來像一隻鴨子,那麼就是鴨子。

只要有GetEnumerator就行:

internal class Program
{
	static void Main(string[] args)
	{
		List<string> l = new List<string>() { "1", "2", "3"};
		TestForeach<string> a = new TestForeach<string>();
		foreach (var b in a)
		{
			
		}
	}
}

public class TestForeach<T> 
{
	public IEnumerator<T> GetEnumerator()
	{
		throw new NotImplementedException();
	}
}

在foreach 迴圈中不能進行賦值。

同樣在foreach 迴圈中不能對集合的個數進行修改。

無論是增加還是刪除list的version都會發生變化。

很多書裡面介紹了,為什麼不能複製和增加集合個數,這樣會使人頭腦不清醒。

因為這是遍歷,應該是一個原子,這樣的設計不會新增歧義性,單一職責邏輯清晰能減少不必要的bug。

一般來說都會繼承一下IEnumerable 這個介面。 為什麼呢?因為linq。

linq 是 language integated query,語言整合查詢。

也就是說定義了一套規範哈,對於我們來說其實不用太在意這個規範是什麼,大體看一下就行。

對IEnumerable而言,實現的就在system.IEnumberable.linq 上面,參照用就好。

然後linq有一個很大的特點,就是延遲執行。

然後來說下這個延遲執行是怎麼實現的哈。

比如我們的list 經過where 之後,其實在執行時就不是linq。

執行時是這個型別WhereListIterator。

就看個list的。

執行where 其實就是把list 給 WhereListIterator。

並沒有生成新的list,所以說不會執行where操作。

當遍歷的時候就會執行WhereListIterator的movenext。

遍歷的時候就會做條件判斷。

所以呢,如果進行多次遍歷後呢,其實一直都在執行你的where 語句。

如果需要遍歷多次,where 迴圈後,最好直接tolist轉換成一個list。

當然有興趣可以去看下其他的,這裡只是舉個例子,其實就是用包裝器模式實現的。

這樣經過層層包裝,人們就會想啊,如果where 語句按照順序執行,那麼不會很慢啊。

所以linq還可以並行實現,使用asParallel(),這個東西來實現。

這樣執行就是並行的, 先不管實現,並行篇會介紹,只需要知道官方幫我們實現了,這樣執行更快就可以了。

因為東西比較多,下一節是一些複雜的linq和匿名linq,下下節是查詢表示式。 linq 是實現其實很複雜,但是用起來是真的簡單。