LINQ 的設計初衷就是放在程式設計師日常都會用到的功能上,藉此節省程式碼的量,身為C# 工程師,我們在解搜尋、排序、過濾這些問題時,應該時時刻刻都要想到如果運用LINQ,是否會有更好的解法,
本系列文便是一個紀錄,我沒有定任何的週期發布時間,純屬遇到後,就記錄下來,也不代表是最好的解法,你知道,這世界上只有當下最佳解,如果你有更好的解法,歡迎分享 ^_^ ,若對這系列有興趣,可以追蹤本粉絲頁。
WMI 篇
WMI在Windows系統是很方便的機制,可以讓我們取得許多的硬體資訊或是系統資訊,例如硬碟、顯示卡、行程等資訊,以下是一個列出目前系統中所有磁碟大小的例子。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
Console.WriteLine($"Size: {item["Size"]}");
}
}
Console.ReadLine();
}
}
}
使用時記得加入System.Management這個Assembly做為Reference,這是一個可正常運作的例子。但你知道嗎?WMI所回傳的資訊有可能因作業系統的版本而有所差異(此例是不會),例如記憶體資訊在Windows 10和Windows 7回傳的屬性數量就不同了,
如果遭遇這種情況,WMI會毫不留情地拋出一個例外。
那麼怎麼避免這個例外,在找不到這個屬性時,給個預設值,讓程式免於Crash的窘境了?最簡單的方式就是try...catch。
foreach (var item in searcher.Get())
{
try
{
Console.WriteLine($"Size: {item["Size1"]}");
}
catch(ManagementException ex)
{
//do some log.
Console.WriteLine("Size: not found");
}
}
有效,雖然例外並不是什麼大事,但能避免的話是最好,因為畢竟例外會多耗費一些處理資源,除此之外,如果要處理的屬性多時,難免出現一堆的try...catch區段,運用Extension Method大概是常見的手法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp9
{
public static class ManagementHelper
{
public static object GetValueOrDefault(this ManagementBaseObject source, string key)
{
try
{
return source[key];
}
catch(ManagementException)
{
}
return null;
}
}
class Program
{
static void Main(string[] args)
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
Console.WriteLine($"Size: {item.GetValueOrDefault("Size") ?? "not found"}");
}
}
Console.ReadLine();
}
}
}
比起包一堆的try...catch,看起來好多了是吧?結案。 啊? 這跟LINQ有啥關係?喔,我忘了自己正在寫LINQ的主題.....................
好吧,假設如果能運用LINQ的Where及FirstOrDefault那不是很好,可惜,這兩個函式並不能用在這。
此時不妨深究一下item的型別。
接著看一下ManagementBaseObject的定義。
事實上,["key"]是Properties這個Collection屬性的捷徑,看一下PropetyDataCollection的定義。
只要看到ICollection、IEnumerable,那麼就代表著可能可以運用LINQ來解這個問題了,可是還是有問題,因為就算使用Properties,還是無法使用Where,因為Where、FirstOrDefault是IEnumerable<T>的Extension Method。
不過你應該看出端倪了,裡面出現了Cast,雖然Cast強制轉型可能不是很安全的做法,但是另一個OfType可是非常安全,因此可以寫下以下的程式碼。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
Console.WriteLine($@"Size: {item.Properties.OfType<PropertyData>().FirstOrDefault(
a => a.Name == "Size")?.Value ?? "not found"}");
}
}
Console.ReadLine();
}
}
}
這裡用了點小技巧,真實使用時,你可能還要處理轉型及Log的問題(找不到就是要Log),不過我們確實避開了這個例外,Cast與OfType是可以用在所有實作IEnumerable介面的物件上,活用他們可以把本來受限於非IEnumerable<T>的物件帶往LINQ豐富功能的領域。
當然,這個例子可能在大量使用時會有效能問題(例如你要列出一個以上的屬性),此時可以運用ToDictionary來處理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
var props = item.Properties.OfType<PropertyData>().ToDictionary(a => a.Name);
Console.WriteLine($"Size: { (props.ContainsKey("Size") ? props["Size"].Value : "not found") }");
}
}
Console.ReadLine();
}
}
}
美中不足的是這裡依然需要針對找不到鍵值的情況使用三元式,但比起上例每個屬性都where一次好太多了。
就效能上來說,如果屬性正確,最早的Exception處理效能最好,但是一旦出現例外,那就不一定了,寫程式就是在效能跟易用性上做抉擇,端看你要的是哪塊。