用 C# 堆砌程式:API 範例集


LINQ

LINQ 全文是 Language Integrated Query,意思是把查詢功能整合到語言裡面。

Microsoft 用兩種途徑,將查詢功能整合進語言裡:

一、.NET 的 API,無論哪一種語言都能使用。
二、C# 的語法:查詢語句。更簡潔而順手,但並未涵蓋 API 所有功能。

有了這套查詢功能,無論要操作的資料來源是陣列、集合、資料庫、XML,都可以用一樣的寫法,而不是各自不同的 API 😳


Enumerable

Enumerable.Aggregate()將資料串處理成一筆資料
Enumerable.All()資料是否全符合條件
Enumerable.Any()是否有資料符合條件
Enumerable.Append()添加一筆資料到尾端
Enumerable.AsEnumerable()轉為 Enumerable 狀態
Enumerable.Average()平均值
Enumerable.Cast<型別>()轉換資料的型別
Enumerable.Concat()加入一串資料
Enumerable.Contains()是否包含資料
Enumerable.Count()資料筆數
Enumerable.DefaultIfEmpty()如果是空的就傳回預設資料
Enumerable.Distinct()去除重複的資料
Enumerable.ElementAt()取得某一筆資料
Enumerable.ElementAtOrDefault()若沒資料則跳過不報錯
Enumerable.Empty<T>()‎產生空的資料串
Enumerable.Except()取得兩個 IEnumerable 差異的部分
Enumerable.First()第一筆資料
Enumerable.FirstOrDefault()第一筆資料或傳回預設值
Enumerable.GroupBy()群組資料串
Enumerable.GroupJoin()關聯並群組資料串
Enumerable.Intersect()取得兩個 IEnumerable 相同的部分
Enumerable.Join()依關聯性將外來資料串加入資料串
Enumerable.Last()最後一筆資料
Enumerable.LastOrDefault()最後一筆資料或傳回預設值
Enumerable.LongCount()以 Int64 傳回資料筆數
Enumerable.Max()最大值
Enumerable.Min()最小值
Enumerable.OfType<T>()選出 T 型別的資料
Enumerable.OrderBy()遞增排序
Enumerable.OrderByDescending()遞減排序
Enumerable.Prepend()添加資料到前端
Enumerable.Range()產生在指定之範圍內的整數序列
Enumerable.Repeat()填入筆數資料
Enumerable.Reverse()反轉資料順序
Enumerable.Select())依條件選出資料
Enumerable.SelectMany()從多個資料串中選出資料
Enumerable.SequenceEqual()兩資料串是否相等
Enumerable.Single()傳回只有一筆資料的資料串
Enumerable.SingleOrDefault()傳回只有一筆資料的資料串或預設值
Enumerable.Skip()跳過前幾筆資料
Enumerable.SkipWhile()取得符合條件之後的資料
Enumerable.Sum()資料總和
Enumerable.Take()取得前幾筆資料
Enumerable.TakeWhile()取得符合條件之前的資料
Enumerable.ThenBy()以遞增順序處理資料
Enumerable.ThenByDescending()以遞減順序處理資料
Enumerable.ToArray()轉為陣列
Enumerable.ToDictionary()轉為 Dictionary 物件
Enumerable.ToHashSet()轉為 HashSet 物件
Enumerable.ToList()轉為 List 物件
Enumerable.ToLookup()轉為 Lookup 物件
Enumerable.Union()加入兩資料串的差異部分
Enumerable.Where()依條件過濾資料
Enumerable.Zip()併聯兩資料串

綜合範例


13579
246810
135
79246810
True
True

List.ForEach() 的委派和匿名函式


13579
13579
13579
13579

ParallelEnumerable.ForAll() 多執行緒查詢


04628 每次執行結果,順序不一定。適合用來求值,例如總和。


集合

自從 C# 新增 LINQ,集合的部分用途很多被取代。

但兩者應用場合不同,集合本質上不是資料的操作,而是資料的來源,如果要對同一批資料進行反覆的操作,使用集合物件比較有可讀性,或者要對某個資料體進行增刪的動作,使用集合物件比較有效率。

LINQ 其實就像進一步操作集合物件的語法工具,所以我們應該隨上下文的語意切換 LINQ 和集合,而不是只認定其中一種、否定另外一種 🤗


ArrayList 和 Hashtable

在 C# 還不支援泛型時,.NET Framework 最主要的資料結構類別是 System.Collections 的 ArrayList 和 Hashtable。

最常用的就這兩個,其它還會用到的大概就 Queue 和 Stack 吧?

後來引進 LINQ 時,這些類別並未實作其功能,只在新的 System.Collections.Generic 類別實作,因此漸漸用 List 和 Dictionary 取而代之,ArrayList 和 Hashtable 有種走入歷史的感覺~

寫 C# 程式很難抗拒 LINQ 蜜糖般的誘惑,空有 ArrayList 和 Hashtable 資料結構,沒有 LINQ 接手各式演算法,就不像在寫 C# 程式了。


List

串列資料結構,適合讀取資料。

Capacity
Count
Add()
AddRange()
AsReadOnly()
BinarySearch()
Clear()
Contains()
ConvertAll()
CopyTo()
Exists()
Find()
FindAll()
FindIndex()
FindLast()
FindLastIndex()
ForEach()
GetEnumerator()
GetRange()
IndexOf()
Insert()
InsertRange()
IsFixedSize()
IsReadOnly()
IsSynchronized()
LastIndexOf()
Remove()
RemoveAll()
RemoveAt()
RemoveRange()
Reverse()
Sort()
SyncRoot()
ToArray()
TrimExcess()
TrueForAll()


LinkedList

雙向鏈結串列資料結構,適合寫入資料。

Count
First
Last
AddAfter()
AddBefore()
AddFirst()
AddLast()
Clear()
Contains()
CopyTo()
Find()
FindLast()
GetEnumerator()
GetObjectData()
OnDeserialization()
Remove()
RemoveFirst()
RemoveLast()


SortedSet

資料不重複的有序集合。

Comparer
Count
Max
Min
Add()
Clear()
Contains()
CopyTo()
CreateSetComparer()
ExceptWith()
GetEnumerator()
GetObjectData()
GetViewBetween()
IntersectWith()
IsProperSubsetOf()
IsProperSupersetOf()
IsSubsetOf()
IsSupersetOf()
OnDeserialization()
Overlaps()
Remove()
RemoveWhere()
Reverse()
SetEquals()
SymmetricExceptWith()
TryGetValue()
UnionWith()


HashSet

資料不重複的無序集合。

Comparer
Count
Add()
Clear()
Contains()
CopyTo()
CreateSetComparer()
EnsureCapacity()
ExceptWith()
GetEnumerator()
GetObjectData()
IntersectWith()
IsProperSubsetOf()
IsProperSupersetOf()
IsSubsetOf()
IsSupersetOf()
OnDeserialization()
Overlaps()
Remove()
RemoveWhere()
SetEquals()
SymmetricExceptWith()
TrimExcess()
TryGetValue()
UnionWith()


Queue

佇列,先進先出資料結構。

Count
Clear()
Contains()
CopyTo()
Dequeue()
Enqueue()
GetEnumerator()
Peek()
IsSynchronized()
SyncRoot()
ToArray()
TrimExcess()
TryDequeue()
TryPeek()


Stack

堆疊,後進先出資料結構。

Count
Clear()
Contains()
CopyTo()
GetEnumerator()
IsSynchronized()
Peek()
Pop()
Push()
SyncRoot()
ToArray()
TrimExcess()
TryPeek()
TryPop()


Dictionary

無序鍵值對資料結構。

Comparer
Count
Keys
Values
Add()
Clear()
ContainsKey()
Contains()
ContainsValue()
CopyTo()
EnsureCapacity()
GetEnumerator()
GetObjectData()
IsFixedSize()
IsReadOnly()
IsSynchronized()
OnDeserialization()
Remove()
SyncRoot()
TrimExcess()
TryAdd()
TryGetValue()

基本用法


1:AAA
2:BBB
3:CCC

依值排序

這要用 LINQ 來完成:


[3, Cleric][2, Fighter][1, Hero][4, Mage]

ForEach()

這可以用 C# 的「擴充」來實現:


AAA
BBB
CCC

別忘了 LinqExtensions 可以單獨寫在一個 *.cs 檔案,C# 原本就能將類別分開寫的關係,Launch 無須 using 就能載入使用。


SortedDictionary

無索引值的有序鍵值對資料結構。

Comparer
Count
Item[]
Keys
Values
Add()
Clear()
ContainsKey()
ContainsValue()
CopyTo()
GetEnumerator()
Remove()
TryGetValue()


SortedList

有索引值的有序鍵值對資料結構。

Capacity
Comparer
Count
Item[]
Keys
Values
Add()
Clear()
ContainsKey()
ContainsValue()
GetEnumerator()
IndexOfKey()
IndexOfValue()
Remove()
RemoveAt()
TrimExcess()
TryGetValue()


IEnumerable

IEnumerable.GetEnumerator()


IEnumerator

IEnumerator.Current
IEnumerator.Dispose()
IEnumerator.MoveNext()
IEnumerator.Reset()


語法糖

new List<型態>() { 資料, 資料, ... };
new Dictionary<型態, 型態>() { {鍵, 值}, {鍵, 值}, ... };


打亂資料


做法一:用 LINQ 的 OrderBy()


999
333
(每次執行結果不一樣)


做法二:為 IList 介面擴充 Shuffle()


DCAEBEDBAC(每次執行結果不一樣)


做法三:打亂集合的 Sort()


ECADB(每次執行結果不一樣)


Random 的建議用法


問題

如果建立 Random 類別時,沒有在建構式設定 seed 值,那麼 .NET 會傳入電腦時間做為 seed 值來產生亂數表。

換句話說,在相同時間建立的 Random 物件,其亂數表是一樣的!如果你在同一時間內多次建立 Random 物件來呼叫 Next(),會發現得到的數值是重複的、固定的,而不是打亂的、變動的:

Random r1 = new Random();  假設獲得亂數表:3 → 5 → 1 → 4 → 2 …
Random r2 = new Random();  假設獲得亂數表:3 → 5 → 1 → 4 → 2 …
Random r3 = new Random();  假設獲得亂數表:3 → 5 → 1 → 4 → 2 …

r1.Next();          傳回 r1 亂數表第一個數字:3
r2.Next();          傳回 r2 亂數表第一個數字:3
r3.Next();          傳回 r3 亂數表第一個數字:3

看似呼叫三次 Next(),其實是對一模一樣的三張亂數表,各取出第一個數字,結果通通都是 3。

由於電腦每秒可以執行百萬指令,所以區區數行 new Random(),幾乎用的都是同一時間值做 seed 😐


建議

由於 Random 並沒有打亂 seed 的方法,只能在建構式 Random(Int32) 設定 seed,所以建議的用法,是早早就建立一個 Random 物件來重複使用,讓 Next() 照同一張亂數表的順序走:

Random r = new Random();   假設獲得亂數表:3 → 5 → 1 → 4 → 2 …

r.Next();          傳回亂數表第一個數字:3
r.Next();          傳回亂數表第二個數字:5
r.Next();          傳回亂數表第三個數字:1

還要注意的是,new Random() 不要放在迴圈內,也不要放在函式內。迴圈等於短時間內大量產生同樣亂數表的 Random 物件,函式經常會在短時間被重複呼叫,這樣一來裡面的 Random 其實也是用同樣時間值產生亂數表。這些都不叫建立一個 Random 物件來重複使用,等於 new 一堆 Random 物件來使用。宣告為類別的屬性來重複使用也有這樣陷阱,短時間內 new 一堆這個類別的話,就跟宣告在函式裡面的意思一樣~

如果真的要建立多個 Random 物件,可以在每個物件之間呼叫 System.Threading 的 Thread.Sleep(1),這樣時間就會錯開,變相用不一樣的時間值產生亂數表。


其他

雖然照建議的用法操作 Random 物件能正常獲得亂數,但它依然不是真正的亂數,順序依然是固定的、有規則可循的。


操作檔案


資料夾\檔案 1.dat
資料夾\檔案 2.dat
資料夾\檔案 3.dat
資料夾\檔案 4.dat

File.Create() 會傳回 FileStream,以便讀寫檔案,因此隨後用 Close() 關閉檔案,才能對該檔案進行增刪的動作,否則等於檔案正在使用中。

Directory.Delete() 可以 true 第二個參數,刪除不是空的資料夾。

除了無須建立物件即可直接調用的 Directory 和 File,還有必須建立物件的 DirectoryInfo 和 FileInfo,以便對同一資料夾或檔案進行多次操作


讀寫檔案資料


使用 File 簡易讀寫


ABC
ABC
如果資料是 String 陣列或 List 串列,可以用 WriteAllLines 一次寫入新的檔案,用 AppendAllLines 一次附加資料到檔案,且每筆資料附加換行字元。


使用 StreamWriter 和 StreamReader 分批讀寫


AAA
BBB
CCC

AAA
BBB
CCC


使用 FileStream 隨機存取


猜猜
我是誰

是誰


使用 BinaryWriter 和 BinaryReader 讀寫數值資料


356


XML


新建 XML 文件


<?xml version="1.0" encoding="UTF-8"?>
<html>
  <body style="color:blue">
    <p style="color:red">Hello</p>
  </body>
</html>


讀取 XML 文件

sample.xml

launch.cs


Paragraph 1
Paragraph 2
Paragraph 3
color:blue

<?xml version="1.0" encoding="UTF-8"?>
<html>
  <body style="color:red">
    <h1>Homepage</h1>
    <p>NULL</p>
    <p>Paragraph 2</p>
    <p>Paragraph 3</p>
  </body>
</html>


XPath(本文轉貼自 MSDN

./author
目前內容中的所有 <author> 項目。請注意,其等同於下一資料列的運算式。

author
目前內容中的所有 <author> 項目。

first.name
目前內容中的所有 <first.name> 項目。

/bookstore
這份文件的文件項目 (<bookstore>)。

//author
文件中的所有 <author> 項目。

book[/bookstore/@specialty=@style]
所有 <book> 項目,其 style 屬性值等於文件根上之 <bookstore> 項目的 specialty 屬性值。

author/first-name
當作 <author> 項目子系的所有 <first-name> 項目。

bookstore//title
位於 <bookstore> 項目一或多層深的所有 <title> 項目 (任意子代)。請注意,其不同於下一資料列的運算式。

bookstore/*/title
當作 <bookstore> 項目子系的所有 <title> 項目。

bookstore//book/excerpt//emph
所有 <emph> 項目,其位於 <book> 項目的 <excerpt> 子系之任何位置,以及 <bookstore> 項目的任何位置。

.//title
位於目前內容中一或多層深的所有 <title> 項目。請注意,唯有在此情況下,才需要使用句點標記法。

author/*
當作 <author> 項目子系的所有項目。

book/*/last-name
當作 <book> 項目子系的所有 <last-name> 項目。

*/*
目前內容的所有孫代項目。

*[@specialty]
含 specialty 屬性的所有項目。

@style
目前內容的 style 屬性。

price/@exchange
目前內容中所有 <price> 項目的 exchange 屬性。

price/@exchange/total
傳回空節點集,因為屬性不含項目子系。XML 路徑語言 (XPath) 文法允許這個運算式,但其效果不大。

book[@style]
內含目前內容之 style 屬性的所有 <book> 項目。

book/@style
目前內容之所有 <book> 項目的 style 屬性。

@*
目前項目內容的所有屬性。

./first-name
目前內容節點中的所有 <first-name> 項目。請注意,其等同於下一資料列的運算式。

first-name
目前內容節點中的所有 <first-name> 項目。

author[1]
目前內容節點中的第一個 <author> 項目。

author[first-name][3]
具有 <first-name> 子系的第三個 <author> 項目。

my:book
my 命名空間的 <book> 項目。

my:*
my 命名空間的所有項目。

@my:*
my 命名空間的所有屬性 (不含 my 命名空間項目上不合格的屬性)。
請注意,索引與父代是相對的。請參考下列資料:
<x>
  <y/>
  <y/>
</x>
<x>
  <y/>
  <y/>
</x>

x/y[1]
每一個 <x> 的第一個 <y> 子系。其等同於下一列的運算式。

x/y[position() = 1]
每一個 <x> 的第一個 <y> 子系。

(x/y)[1]
來自 <x> 項目整組 <y> 子系的第一個 <y>。

x[1]/y[2]
第一個 <x> 的第二個 <y> 子系。
如需其他範例,請參考<XPath 的 XML 檔範例>。

book[last()]
目前內容節點的最後一個 <book> 項目。

book/author[last()]
目前內容節點中每一個 <book> 項目的最後一個 <author> 子系。

(book/author)[last()]
最後一個 <author> 項目,其來自目前內容節點之 <book> 項目的整組 <author> 子系。

book[excerpt]
至少包含一個 <excerpt> 項目子系的所有 <book> 項目。

book[excerpt]/title
當作 <book> 項目子系,且至少包含一個 <excerpt> 項目子系的所有 <title> 項目。

book[excerpt]/author[degree]
至少包含一個 <degree> 項目子系,且當作至少包含一個 <excerpt> 項目之 <book> 項目子系的所有 <author> 項目。

book[author/degree]
包含 <author> 子系 (至少包含一個 <degree> 子系) 的所有 <book> 項目。

author[degree][award]
至少包含一個 <degree> 項目子系和至少一個 <award> 項目子系的所有 <author> 項目。

author[degree and award]
至少包含一個 <degree> 項目子系和至少一個 <award> 項目子系的所有 <author> 項目。

author[(degree or award) and publication]
至少包含一個 <degree> 或 <award> 和至少一個 <publication> 當作子系的所有 <author> 項目。

author[degree and not(publication)]
至少包含一個 <degree> 項目子系,且不包含 <publication> 項目子系的所有 <author> 項目。

author[not(degree or award) and publication]
至少包含一個 <publication> 項目子系,且不包含 <degree> 也不包含 <award> 項目子系的所有 <author> 項目。

author[last-name = "Bob"]
至少包含一個 <last-name> 項目子系 (包含值 Bob) 的所有 <author> 項目。

author[last-name[1] = "Bob"]
其中第一個 <last-name> 子項目之值為 Bob 的所有 <author> 項目。請注意,其等同於下一資料列的運算式。

author[last-name [position()=1]= "Bob"]
其中第一個 <last-name> 子項目之值為 Bob 的所有 <author> 項目。

degree[@from != "Harvard"]
其中 from 屬性不等於 "Harvard" 的所有 <degree> 項目。

author[. = "Matthew Bob"]
其值為 Matthew Bob 的所有 <author> 項目。

author[last-name = "Bob" and ../price>50]
內含 <last-name> 子項目 (其值為 Bob) 和 <price> 同層級項目 (其值大於 50) 的所有 <author> 項目。

book[position() <= 3]
前三本書 (1、2、3)。

author[not(last-name = "Bob")]
不包含 <last-name> 子項目 (包含值 Bob) 的所有 <author> 項目。

author[first-name = "Bob"]
至少包含一個 <first-name> 子系 (包含值 Bob) 的所有 <author> 項目。

author[* = "Bob"]
所有包含值為 Bob 之任何子項目的所有 author 項目。

author[last-name = "Bob" and first-name = "Joe"]
內含 <last-name> 子項目 (含值 Bob) 和 <first-name> 子項目 (其值為 Joe) 的所有 <author> 項目。

price[@intl = "Canada"]
內容節點中 intl 屬性等於 "Canada" 的所有 <price> 項目。

degree[position()<3]
內容節點子系的前兩個 <degree> 項目。

p/text()[2]
內容節點中每個 <p> 項目的第二個文字節點。

ancestor::book[1]
內容節點中最接近的 <book> 上階。

ancestor::book[author][1]
內容節點的最接近 <book> 上階,且這個 <book> 項目將 <author> 項目當作子系。

ancestor::author[parent::book][1]
目前內容中最接近的 <author> 上階,並且這個 <author> 項目是 <book> 項目的子系。

x | y/x
聯集運算式。在下列 XML 檔中,選取值為 green 或 blue 的所有 <x> 元素:

XML 檔 (data1.xml)

XSLT 檔 (union.xsl)

Formatted Output

green, blue, blue, green

Processor Output

<?xml version="1.0" encoding="UTF-16"?>green, blue, blue, green


SQLite

由於下 SQL 處理資料的效率往往會有驚人的表現,當傳統的資料結構與演算法都解決不了問題時,不妨掛上 SQLite 的組件,改用 SQL 解決看看1


Microsoft.Data.Sqlite

.NET Framework 並未內建 SQLite 功能 😤

請至 NuGet Gallery 下載 Microsoft.Data.Sqlite,再到 SQLite 官網下載 Precompiled Binaries for Windows 的 64-bit DLL (x64) for SQLite(不要下載到 32-bit),解開後將 sqlite3.dll 與 Microsoft.Data.Sqlite.dll 放在一起。

範例程式如下:


csc /r:Microsoft.Data.Sqlite.dll *.cs
Hello, SQLite!


System.Data.SQLite

另外還有 System.Data.SQLite,它不需要另外下載 sqlite3.dll,而是內建在 System.Data.SQLite.dll 裡面!請下載 Precompiled Binaries for 64-bit Windows (.NET Framework 4.6) 的 sqlite-netFx46-binary-bundle-x64-2015-1.0.115.5.zip,解開後的 System.Data.SQLite.dll 就等於一款資料庫系統。

由於 .NET Framework 版的 Microsoft.Data.Sqlite 和 System.Data.SQLite 都已停止更新,因此不內建 SQLite 3 的 Microsoft.Data.Sqlite 會是更好的選擇,可以替換新版本的 sqlite3.dll,System.Data.SQLite 內建的 SQLite 版本停留在 2015 年。

System.Data.SQLite 和 Microsoft.Data.Sqlite 是同樣原始碼的產品,所以兩者程式寫法一樣,只差在大小寫,一個是 Sqlite,一個是 SQLite:


csc /r:System.Data.SQLite.dll *.cs
Hello, SQLite!


序列化

將物件轉為檔案或字串,往後再從檔案或字串還原為物件。


序列化為二進制檔案


00 01 00 00 00 FF FF FF FF 01 00 00 00 00 00 00 00 0C 02 00 00 00 3A 72 75 6E 2C 20 56 65 72 73 69 6F 6E 3D 30 2E 30 2E 30 2E 30 2C 20 43 75 6C 74 75 72 65 3D 6E 65 75 74 72 61 6C 2C 20 50 75 62 6C 69 63 4B 65 79 54 6F 6B 65 6E 3D 6E 75 6C 6C 05 01 00 00 00 06 50 6C 61 79 65 72 02 00 00 00 04 6E 61 6D 65 06 6E 75 6D 62 65 72 01 00 08 02 00 00 00 06 03 00 00 00 0C 54 77 69 64 65 65 6D 20 43 69 76 73 3A 00 00 00 0B
Twideem Civs
58


序列化為 SOAP 格式


<SOAP-ENV:Envelope
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
 xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0"
 SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Player id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/asse
m/run%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyTok
en%3Dnull">
<name id="ref-3">Twideem Civs</name>
<number>58</number>
</a1:Player>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Twideem Civs
58


序列化為 XML 格式


<?xml version="1.0"?>
<Player xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <name>Twideem Civs</name>
  <number>58</number>
</Player>

Twideem Civs
58


序列化為 JSON 字串


{"name":"Twideem Civs", "number":58}
Twideem Civs
58


Zip 壓縮檔案


csc /r:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.IO.
Compression.FileSystem.dll *.cs
launch


播放音樂


SoundPlayer

.NET Framework 只能播放 WAVE 格式 😨


NAudio

想播放 MP3 格式,請至 NuGet Gallery 下載 NAudio 1.10.0 組件,它能將 MP3 轉為 WAVE 格式給 SoundPlayer 播放…不過 NAudio 本身就能播放 WAVE 了。

下載後,直接用壓縮軟體解開,然後將 net35 資料夾裡面的 NAudio.dll 跟你的 C# 程式碼放在一起。


csc /r:NAudio.dll *.cs


調用外部程式功能


範例一:Process.Start()


如果有預設網頁瀏覽器,還可以下網址,用來開啟網站。


範例二:Process.WaitForExit()


範例三:StartInfo.WindowStyle

在程式碼設定 StartInfo.WindowStyle 屬性為 ProcessWindowStyle.Hidden,然後編譯時下 /t:winexe 參數,這樣執行程式時,既不會跳出視窗、也不會顯示命令提示字元。


csc /t:winexe /out:快速關機.exe *.cs


終止外部程式執行


通常其它程式語言要傳入完整的名稱:msedge.exe,但 .NET Framework 必須省略後面的 .exe。


暫停一段時間


日期和時間


取得年月日時分秒


2019 年
4 月
18 日
12 點
34 分
56 秒


計算間隔時間


123456789101112131415161718192021222324252627282930313233343444 (省略)
00:00:01.4561230


主控台

Console.BackgroundColor
Console.ForegroundColor
Console.Title
Console.Beep()
Console.Clear()
Console.GetCursorPosition()
Console.Read()取得按下的鍵值
Console.ReadLine()取得輸入的字串
Console.ReadKey()按任意鍵
Console.SetBufferSize()
Console.SetCursorPosition()
Console.SetWindowPosition()
Console.SetWindowSize()
Console.RestColor()
Console.Write()
Console.WriteLine()

使用者介面與程式碼分離的 WPF + XAML 視窗程式設計

WPF(Windows Presentation Foundation)是 .NET Framewrok 3.0 新增的 UI 框架,基於 XML 和向量繪圖技術,能將使用者介面另外寫成 XAML(eXtensible Application Markup Language),與程式碼分離。

設計 WPF 程式,真正標準的做法,是把建立視窗的前置性作業寫在 *.csproj 專案檔,程式碼只要一行 InitializeComponent() 就好。不過這適合用 msbuild.exe 編譯,且 *.csproj 不適合手寫,通常由 Visual Studio .NET 代勞。

本文示範的做法是用 csc.exe 編譯,所以載入 *.xaml 建立視窗的工作是寫在程式碼裡面。

雖然很少人這樣寫 WPF 程式,但不失為一種做法,方便只用 Windows 10 內建的 .NET Framework 4.8 就能編譯出來~


window.xaml


launch.cs


編譯

WPF 的編譯要手動載入多個組件,以 Windows 10 內建 .NET Framework 4.8 為例,這些組件是:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.Xaml.dll
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationCore.dll
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\PresentationFramework.dll
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\WindowsBase.dll

因此可以這樣下指令:

PATH=C:\Windows\Microsoft.NET\Framework64\v4.0.30319\
csc /r:System.Xaml.dll, WPF\PresentationCore.dll,
WPF\PresentationFramework.dll, WPF\WindowsBase.dll /t:winexe *.cs


執行

插圖


說明

通常 XAML 檔只用來配置外觀,不負責功能。

如果想改變視窗介面的外觀與配置,只要修改 window.xaml 即可,不用重新編譯 launch.cs。

需要更改功能時,才修改原始程式碼,重新編譯。

真要在 XAML 寫功能的話,可使用 <CODE> 標籤寫功能程式,然後指定給控制項標籤的事件屬性。(不適用先前的範例,要重新修改程式才支援。)


建議

如果沒有使用者介面與程式分離的需求,其實繼續用 Windows Forms 就好,不用轉換到 WPF。兩者需求不同,WPF 雖然是新的技術,但沒有新舊交替、取代 Windows Forms 的意思。

如果一開始學的就是 WPF,某天想跟 Windows Forms 一樣,把使用者介面寫死在主程式裡面,其實也不用轉換技術,System.Windows.Controls 的元件可以直接寫進 Window 裡面,並不是非得 XAML 不可,像這樣:

frame.cs

launch.cs


CGI 程式設計


列出所有可用資料


以 UTF-8 字元編碼輸出文字

如果作業系統預設使用的字元編碼是 Big5,那 Console.Write() 也會使用 Big5 字元編碼輸出文字,使得網頁指定 UTF-8 字元編碼或者內容有日文的話會亂碼。

解決辦法是用 Console.OutputEncoding = System.Text.Encoding.UTF8; 設定為 UTF-8。


Cookie 的存取


GET 資料的接收

直接取得 GET 資料

剖析 GET 資料


POST 資料的接收

直接取得 POST 資料

逐位元讀取並剖析 POST 資料


判斷 GET 或 POST


轉址

第一種做法是寫在 HTTP 的 header,請求 URL redirect:


第二種做法是用 HTML 來完成:


還有更多種做法,像是用 JavaScript 的 location.href="網址",道理跟第二種一樣,就不贅述了~


上一頁的妙用

就像超鏈結文字 <A/> 或按鈕 <BUTTON/> 能夠原地執行伺服器的功能一樣,還能避免大量的下一頁,非常好用 👍

home.html

perform.cgi

雖然用 location.replace() 是更好的選擇,但每次都能以這方式間接載入 CGI 網頁也挺煩的~


用 HTTPListener 開發後端


以 GET 傳輸含中文資料並寫入檔案

backend.cs


編譯出來的程式,必須以系統管理員身分執行。

frontend.html


按下網頁的超鏈結文字會輸出 data.txt:

中文


以 POST 傳輸含中文資料並寫入檔案

backend.cs


編譯出來的程式,必須以系統管理員身分執行。

frontend.html


按下網頁的按鈕會輸出 data.txt:

args=中文


用 HTTPListener 架網頁伺服器


server.cs


index.html


page.html


編譯出來的程式,必須以系統管理員身分執行。

然後開啟網頁瀏覽器,輸入 127.0.0.1/index.html。

不能省略 index.html,因為程式碼沒寫!可根據 context.Request.Url.LocalPath 是否為 /,改寫為 /index.html 來實現。


將 HTTPListener 後端監聽程式與網頁伺服器合併起來

每個 HTTPListener 用不同埠號錯開,再用 System.Threading.Thread 分頭執行:


server.cs


client.html


WebSocket 伺服器

雖然可以用 System.Net.Sockets 的 TcpListener 等相關類別實作 WebSocket,但能跑是能跑,後續得解決大小毛病,寫出一堆醜程式,所以還是交給第三方程式庫完善大小事吧!

請至 NuGet Gallery 下載 Fleck 組件,直接用壓縮軟體解開,然後將 net45 資料夾裡面的 Fleck.dll 跟你的 C# 程式碼放在一起。


server.cs


csc.exe /r:Fleck.dll *.cs
執行 Server.exe 時需要動態連結 Fleck.dll,因此不要更改它的檔名,否則會出現「無法列印例外狀況字串,因為 Exception.ToString() 失敗。」錯誤訊息。


client.html


按下網頁按鈕,主控台會顯示 Hello(客戶端發出的訊息),按鈕文字會變成 Hello world(伺服端發出的訊息)。


[*] 更多 Fleck 用法,請參訪 GitHub


用 WebClient 取得網頁內容


# twideem.github.io

在部落格和社群的時代,很少有網站繼續提供免費個人網頁的服務,所以用 GitHub Pages 讓我的個人網站繼續下去;雖然這項服務的本意是用來宣傳專案,不是架個人網站用~

感謝 *GitHub*,也感謝一路走來,有 *奇摩*、*PC home Online*、*8d8d*、*Graffiti.net*、*Freewebs*、*Google 雲端硬碟代管網頁*、*Pancake.io* 提供免費又無廣告的網頁空間服務,讓我過足寫網頁文章的癮。


解析網址


http
twideem.github.io
80
/test.html
?name=twideem&number=58


取得內網 IP 和主機名稱


GA502IV
fe80::8901:fb38:3265:d96d%15
192.168.1.101