顯示具有 C# 標籤的文章。 顯示所有文章
顯示具有 C# 標籤的文章。 顯示所有文章

2014年7月11日 星期五

[C#] Extension Method

C#3.0之後提供extension method這個功能,簡單來說就是讓你在現有的type中加入新的的method。一般是用在內建型態或是sealed class上,一般的type我們可以透過繼承的方式來加入新method,但是我們沒辦法修改內建型態,因此extension method就派上用場了。
Extension會有以下這些特徵:
  1. 必須在非generic的static class中,並且宣告為static method
  2. Method最少要有一個參數
  3. 第一個參數用this關鍵字宣告
  4. 第一個參數不能有this以外的修飾詞,例如out或ref
  5. 第一個參數型態不能是pointer type
以下是一個extension method的範例
public static class StringHelper
{
    public static bool IsCapitalized (this string s)
    {
        if (string.IsNullOrEmpty(s)) return false;
        return char.IsUpper(s[0]);
    }
}
IsCapitalized就是string type的extension method,我們可以這樣使用它:
Console.WriteLine("Test".IsCapitalized());
其實這段呼叫在編譯時候會被修改成去呼叫static method,背後的細節被compiler隱藏起來。
Console.WriteLine(StringHelper.IsCapitalized("Test"));
總結一下,extension method背後會做以下轉換:
arg0.Method(arg1, arg2, ...);  // Extension method call
StaticClass.Method(arg0, arg1, arg2, ...);  // Static method call
Interface也可以被extended,範例如下:
public static T First (this IEnumerable sequence)
{
    foreach (T element in sequence)
        return element;

    throw new InvalidOperationException("No elements!");
}
...
Console.WriteLine("Test".First());   // Output: T

2014年5月14日 星期三

Pluggable Object

Pluggable Object是最近在Kent Beck寫得TDD by example中所看到的Design Pattern。其實就是用多型來處理重複的判斷條件。Kent Beck在書中以圖片編輯器為例,在圖片上按下滑鼠左鍵並移動滑鼠表示拖曳圖片移動。而在空白的地方按下左鍵移動滑鼠會出現方框,讓你一次選取多張圖片。這個流程如果寫成程式碼會長得像這樣:

Figure selected; 
public void mouseDown() { 
    selected= findFigure(); 
    if (selected != null)
        select(selected);
}
public void mouseMove() {
    if (selected != null) 
        move(selected); 
    else 
        moveSelectionRectangle(); 
}
public void mouseUp() { 
    if (selected == null) 
        selectAll(); 
}

你會發現有許多重複的判斷條件,該怎麼避免呢?這時候就可以用Pluggable Object。

首先先定義一個Interface叫做SelectionMode,這個Interface會有兩個函式分別是mouseMove()以及mouseUp(),並且建立兩個class(SingleSelection, MultipleSelection)實做此Interface,分別處理單一選取圖片以及多重選取圖片。如此一來整個程式碼可以被改寫如下:

SelectionMode mode; 
public void mouseDown() { 
    selected = findFigure(); 
    if (selected != null)
        mode = SingleSelection(selected);
    else
        mode = MultipleSelection();
}

public void mouseMove() {
    mode.mouseMove();
}

public void mouseUp() {
    mode.mouseUp();
}

程式碼變得乾淨整潔許多,這就是Pluggable Object。

2014年4月16日 星期三

[C#]讓StreamReader從頭開始讀取

有時候我們會希望先從檔案讀一部分資料後,再重頭從檔案最前面讀取資料
大部分的程式語言都會提供Seek函式,讓你設定讀取的位置
不過在C#中會根據你使用Stream的不同,作法會有點不一樣
大多數的Stream都只要呼叫Seek函式指回0就可以
比較要注意的是使用StreamReader的時候,你需要多呼叫DiscardBufferedData函式來把cache清空
把cache清掉常常會被忘記,使用StreamReader要多加注意
fileReader.DiscardBufferedData(); 
fileReader.BaseStream.Seek(0, SeekOrigin.Begin); 
fileReader.BaseStream.Position = 0;

2013年5月29日 星期三

[C#]?? Operator (null coalescing operator)

?? operator可以被用在nullable types以及reference types
語法的意思很簡單:如果operand不是null,把他回傳給我;否則給我一個預設的值。比如說以下例子因為x是null所以y被預設成5。
int? x = null;
int y = x ?? 5;  // y is 5
first ?? second語法被執行的步驟如下:

  1. 檢查first 
  2. 如果first不是null,回傳first 
  3. 如果first是null,回傳second

?? operator可以用來串接多個nullable variable以及reference variable。會回傳第一個不是null的變數。以下範例會印出1,因為a是null。
int? a = null, b = 1, c = 2;
Console.WriteLine (a ?? b ?? c);  // 1
善用?? operator可以讓我們的程式碼變得更簡潔,以下是一個範例程式用來計算使用者的年紀,這個例子沒有使用?? operator。
DateTime birth;
DateTime? death;
public TimeSpan Age
{
    get
    {
        if (death == null)
        {
            return DateTime.Now - birth;
        }
        else
        {
            return death.Value - birth;
        }
    }
}
使用?? operator我們可以把上面的範例改寫成好懂又精簡的版本。
DateTime birth;
DateTime? death;
public TimeSpan Age
{
    get
    {
        DateTime lastAlive = death ?? DateTime.Now;
        return lastAlive – birth;
    }
}

[C#]Optional parameters and Named arguments

C#從4.0版之後開始支援optional parameters以及named arguments,這兩個語法通常會一起使用。他讓我們的程式碼變得更簡潔並增加可讀性。首先先來介紹什麼是Parameters and Arguments。

Parameters and Arguments

Parameters和Arguments是兩個很容易搞混的名詞,先來定義這兩者有什麼不同。下面是一段程式碼範例:

void Foo(int x, int y)
{
// Do something with x and y
}
...
int a = 10;
Foo(a, 20);

parameters是function定義時所使用的變數,arguments是我們呼叫function時傳進去的變數或值 範例中的x和y是parameters,a和20是arguments。

Optional parameters

Optional parameters簡單說就是給parameter一個特定的預設值,當使用者沒有傳入此parameter所對應的arguments時,會自動使用預設值。在C#4.0之前沒有optional parameters,如果要達到這個功能必須使用overloading function,會造成寫一大堆overloading function的情況,並不是很好使用。C#4.0之後使用optional parameters可以免去宣告一堆overloading function的困擾。以下是optional parameters的一個簡單範例:

static void Dump(int x, int y = 20, int z = 30)
{
    Console.WriteLine("x={0} y={1} z={2}", x, y, z);
}
...
Dump(1, 2, 3);
Dump(1, 2);
Dump(1);

我們設定y和z的預設值為20和30,當我們在呼叫function時,編譯器會根據我們提供的參數去做對應,找出最合適的function。程式執行結果如下:

x=1 y=2 z=3
x=1 y=2 z=30
x=1 y=20 z=30

程式會從parameters list的左邊到右邊一一去對應參數。如果出現無法對應的情況會丟出exception。

由於optional parameters和arguments的對應是按照宣告的順序,因此當我們呼叫Dump(1, 2)時會自動把y對應成2,沒辦法把2對應到z讓y使用預設值。要讓參數能自由對應需要使用接下來要介紹的named arguments。

parameter的預設值必須是一個常數,因此以下範例是錯誤的,因為DataTime.Now並不是一個常數。

Foo(DateTime dt = DateTime.Now)

Optional parameters也被限制必須放在後面,不能放在非optional parameters之前。

Foo(int x = 0, int y)

Name Arguments

Name arguments讓我們可以不按照順序傳入參數。他的語法是在argument前加入一個對應parameter的名稱。以下是範例:

static void Dump(int x, int y, int z)
{
    Console.WriteLine("x={0} y={1} z={2}", x, y, z);
}
...
Dump(1, 2, 3);
Dump(x: 1, y: 2, z: 3);
Dump(z: 3, y: 2, x: 1);
Dump(1, y: 2, z: 3);
Dump(1, z: 3, y: 2);

不管argument的順序是如何,編譯器會自動幫我們對應到正確的parameters名稱。

x=1 y=2 z=3
x=1 y=2 z=3
x=1 y=2 z=3
x=1 y=2 z=3
x=1 y=2 z=3

Abusing argument evaluation order

int i = 0;
Dump(z: ++i, x: ++i, y: ++i);
x=2 y=3 z=1

上面這行程式碼輸出的結果可能會和你想的不一樣,這是因為傳進去的argument還是根據他宣告的順序做計算,而不是patameters的順序。我們應該要避免寫出這種程式碼。

2013年5月23日 星期四

C# in Depth(精通C#)

這是一本想更深入瞭解C#的人必讀的一本書。作者完整的交代了C#1.0到C#4.0之間的改變,這樣的寫法讓讀這本書有點像在念C#的歷史。書中詳細的說明C#每次改版新增了哪些功能,以及為什麼要新增這些功能,是為了解決什麼樣的問題。

我一直覺得要瞭解一個程式語言的語法最重要的事情就是瞭解語法演進的過程。其實就像學習數學和物理一樣,只會死背公式是沒有用的。程式語言作者設計某種語法一定是有他的原因。如果我們不懂背後設計的原由,就很難活用這個語法。學習程式語言另一個重要事情是此語法實際的用途和例子,比如說使用某種語法可以改善程式的可讀性,讓程式看起來更精簡。這兩點作者都有完整的解釋,這也是這本書為什麼被這麼多人推薦的原因。

書中甚至提到一些compiler相關的東西,像是compiler如何推論匿名型別的正確型別,LINQ和extension methods是如何被compiler轉譯。這些東西在其他C#的書幾乎沒有被解釋。雖然說不用懂這些也能寫程式,不過我相信要寫出好程式多瞭解這些東西是會有幫助的。

這本書雖然有中文版,但是我覺得翻譯不是很理想。在閱讀過程中常常可以看到錯字或缺字,句子翻譯起來不太通順(書中很常出現"然而"有點妨礙閱讀),甚至會有少翻的情況發生。因此還是建議有能力的讀者可以閱讀原文本。另外第二版沒有中文版是蠻可惜的,中文版只有介紹到C#3.0。

總而言之,這是一本C#的經典書籍,很適合學完基本語法後當C#的第二本書,看完之後一定會對C#有更深入的體認

最近看到C# in Depth要出第三版,內容涵蓋了C#5.0的語法。有興趣的人可以到Amazon網站注意出版訊息。

2013年4月10日 星期三

Static Typing, Dynamic Typing, Strong Typing, Weak Typing

Static Typing

如果一個程式語言中的每個變數型別在編譯時期就完全決定好,我們稱這個程式語言屬於static type。這類的程式語言在使用變數時通常都需要先宣告變數,編譯器會在編譯時期去做type checking。比如說Java, C, C++, C#。以下是一段C的範例:

int num, sum; // explicit declaration
num = 5; // now use the variables
sum = 10;
sum = sum + num;

變數使用前需要宣告是static type的特徵之一

Dynamic Typing

Dynamic type和static type相反,一個屬於dynamic type的程式語言不需要宣告變數,因此編譯器無法對型別做檢查在編譯時找出型別上的錯誤。在執行期間存取變數時,會透過適當的機制來判斷此變數可能的型別。Dynamic type不需要事先宣告變數就能直接使用,比如說Python, PHP, Ruby, Perl。以下是一段Python的範例程式:

sum = 1   // directly using the variable
sum += 2

上面的範例我們直接使用變數sum而沒有事先宣告,這是dynamic type的其中一個特徵。 簡單來說,Static typing會在編譯時去做type checking。而dynamic type會在run-time時做check。

Strong Typing

Strong typing很常會和static typing以及dynamic typing搞混。Strong typing是指變數會被綁定到某個型別,除非經過明顯的型別轉換,不然型別不會任意改變。比如說Python就是strong typing,以下是個範例:

>>> x = 3
>>> y = '4'
>>> print(x+y)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Python不允許int和string相加,除非我們主動去做型別轉換,不然x和y變數不會經過隱藏的型別轉換加在一起。這就是Strong typing的主要特徵。Python是dynamic typing同時也是strong typing

Weak Typing

Weak typing是指型別的概念比較弱,變數的型別可能隨時會經過隱性的轉換。比如說JavaScript,以下是JavaScript的範例:

var x = 3    
var y = '4'
alert(x + y) //Produces "34"

不同於Python,JavaScript允許這種語法並且會自動幫你做型別轉換。這就是weak typing的特徵之一。

Strong Typing v.s. Weak Typing

其實這兩者並沒有一個約定成俗的定義,很多程式語言都同時有兩種的特性。只能說偏向某一邊比較多。比如說C,大部分人認為C偏向Weak Typing,因為C有pointer可以做任意轉型,同時也有void *這種變數可存取任意型別。

2013年4月9日 星期二

Type-safe v.s. Type-Unsafe

某一些程式語言比較自由,允許我們去做一些危險的動作。比如說C和C++的Pointer就允許我們直接對記憶體做操作。這類的功能讓程式語言變得很強大,但是一不小心有可能會讓程式整個當掉。這種允許我們任意存取記憶體位置的程式語言就不能算是Type-safe。比如說以下的C程式讓我們access超過int boundary的位置:
int i = 10;
char *s = (char*)i;
print(*(s+10));
這種問題不會發生在C#身上,因為C#是Type-safe的程式語言。以下範例在C和C++合法,但是在C#會丟出錯誤訊息:
double d = 10.59; 
int i = 5; 
i = d; //this causes an Error
在C#中不能隨便把某個型別的資料當作是另外一種型別,如果編譯器認為這樣轉型是不合法的,在編譯的時候就會丟出錯誤。

[C#]Delegate

如果你有學過C,C#的delegate其實很類似C的function pointer。delegate讓我們可以把function透過參數的形式來傳遞,也就是說delegate可以讓我們把function當作first class opject來操作。下列是一個delegate的簡單範例:
using System;
delegate void StringProcessor(string input);
class Person
{
    string name;
    public Person(string name) { this.name = name; }
    public void Say(string message)
    {
        Console.WriteLine("{0} says: {1}", name, message);
    }
}

class SimpleDelegateUse
{
    static void Main()
    {
        Person jon = new Person("Jon");
        Person tom = new Person("Tom");
        StringProcessor jonsVoice, tomsVoice;
        jonsVoice = new StringProcessor(jon.Say);
        tomsVoice = new StringProcessor(tom.Say);
        jonsVoice("Hello, son.");
        tomsVoice.Invoke("Hello, Daddy!");
    }
}
此範例中我們建立了宣告了兩個delegate的instance,分別是jonsVoice以及tomsVoice。最後用兩種不同的方式呼叫function
使用delegate要有以下三個步驟:

1. 宣告delegate type
delegate void StringProcessor(string input);
這行就是我們宣告delegate type的地方,這個delegate的type是含有一個string參數且回傳值為void的function。此delegate type只能接受回傳值以及參數個數一模一樣的function。不過有一種特殊情況:
void Test(object x);
此function也可以被assigned給此delegate的instance,因為string是繼承自object

2. 建立delegate instance
StringProcessor jonsVoice, tomsVoice;
jonsVoice = new StringProcessor(jon.Say);
用new可以建立剛才宣告好得delegate instance,建立傳入的參數是之後要透過delegate呼叫的function

3. 呼叫delegate instance的function

編譯之後會把jonsVoice("Hello, son.")轉成jonsVoice.Invoke("Hello, son.")
最後又會透過delegate呼叫原本assigned的function jon.Say("Hello, son.")

delegate很常用在UI Button的click,我們會事先宣告EventHandler的delegate instance。當button被按下時,執行對應的處理function。有點類似call back function。

每個delegate instance內部都有一個invocation list用來紀錄function,也就是說一個delegate instance可以紀錄不只一個function。當有多個function時,delegate會一一呼叫invocation list中所儲存的function。我們可以透過+=以及-=運算子來新增移除function。以下是個簡單的範例:

void HowAreYou(string sender) {
  Console.WriteLine("How are you, " + sender + '?');
}
 
void HowAreYouToday(string sender) {
  Console.WriteLine("How are you today, " + sender + '?');
}
 
Notifier greetMe;
 
greetMe = new Notifier(HowAreYou);
greetMe += new Notifier(HowAreYouToday);
 
greetMe("Leonardo");                      // "How are you, Leonardo?"
                                          // "How are you today, Leonardo?"
 
greetMe -= new Notifier(HowAreYou);
 
greetMe("Pereira");                       // "How are you today, Pereira?"

2013年4月3日 星期三

[C#]快速讀取、寫入檔案的方法

C#提供一些快速的Method可以直接寫入或是讀取檔案,善用的話可以節省一些時間
以下三個static methods只需要一個步驟就可把資料從檔案讀到記憶體中,傳入參數都是讀取檔案的路徑
File.ReadAllText(path)                        // (回傳string)
File.ReadAllLines(path)                       // (回傳array of strings)
File.ReadAllBytes(path)                       // (回傳一個byte array)

下列四個static methods同樣只需要一個步驟就可把資料寫入到檔案中,傳入參數是路徑以及寫入的資料
File.WriteAllText(path, string)
File.WriteAllLines(path, array of strings)
File.WriteAllBytes(path, bytes)      
File.AppendAllText(path, string)              // (適合用在log file)
File.ReadAllLines和File.ReadLines看起來很像,但是ReadAllLines會一次把所有資料讀到記憶體中
ReadLines會回傳一個IEnumerable<string>,不會一次把所有資料讀到記憶體中

2012年11月1日 星期四

[C#]建立資料夾範例程式

C#可透過System.IO.Directory.CreateDirectory(dirPath)來建立資料夾
如果dirPath中有不存在的資料夾,CreateDirectory會順便建立此資料夾。
下列範例會在C:\底下建立一個TestDir
using System;
using System.IO;

public class Program
{
    public static void Main()
    {
        string dirPath = @"C:\TestDir";
        if (Directory.Exists(dirPath))
        {
            Console.WriteLine("The directory {0} already exists.", dirPath);
        }
        else
        {
            Directory.CreateDirectory(dirPath);
            Console.WriteLine("The directory {0} was created.", dirPath);
        }
    }
}

[C#]在C#呼叫Native DLLs (P/Invoke)

在C#我們可以透過PInvoke(Platform Invocation Services)來access unmanaged DLL中的function, structs甚至是callbacks function。以下是一個簡單的例子,示範如何在C#中呼叫Windows DLL user32.dll。
MessageBox在user32.dll宣告長這樣:
MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
C#範例程式:
using System;
using System.Runtime.InteropServices;    // For StructLayout, DllImport

class MsgBoxTest
{
    [DllImport("user32.dll"]
    static extern int MessageBox(IntPtr hWnd, String text, String caption, int type);

    public static void Main()
    {
        MessageBox(IntPtr.Zero, "Text", "Caption", 0);
    }
}
有三個地方要注意。DllImport定義function位於哪個dll中,static定義這是一個global method,extern則是告訴CLR此function是實做在外部。
看到這邊好像覺得P/Invoke沒什麼,繼續看下去就知道P/Invoke為什麼這麼讓人頭痛。

如果Native function的參數是struct該怎麼處理?讓我們看一個例子:
void GetSystemTime(LPSYSTEMTIME lpSystemTime);

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
要呼叫這個function,我們必須定義一個C#的Class和C Struct有一樣的結構。
using System;
using System.Runtime.InteropServices;     


[ StructLayout( LayoutKind.Sequential )]
public class SystemTime 
{
   public ushort year;
   public ushort month;
   public ushort weekday;
   public ushort day;
   public ushort hour;
   public ushort minute;
   public ushort second;
   public ushort millisecond;
}
StructLayout告訴marshaler如何把每個C# class中的field對應到C Struct中,LayoutKind.Sequential表示每個field是根據pack-size aligned sequentially。預設的pack-size是8,也就是說所有的field都會aligned成8 bytes。我們可以在StructLayout中加入Pack attribute來修改pack-size。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
GetSystemTime範例由於剛好是8 byte aligned所以不需更改pack size。
要呼叫GetSystemTime很簡單:
using System;
using System.Runtime.InteropServices;

class MsgBoxTest
{
    [DllImport("Kernel32.dll")]
    public static extern void GetSystemTime(SystemTime st);

    public static void Main()
    {
        SystemTime t = new SystemTime();
        GetSystemTime(t);
        Console.WriteLine(t.Year);
    }
}
如果不知道native function如何在C#中宣告,可以去PINVOKE.NET搜尋。此網站也提供一個方便的Visual Studio add-in可讓你在VS中直接查詢。

2012年10月30日 星期二

[C#]取得現在日期和時間(DateTime Format String)

在C#中,可以用DataTime取得目前的時間以及日期。
Console.Write(DateTime.Now.TimeOfDay);
21:15:56.0797894
Console.Write(DateTime.Now.ToShortTimeString());
下午 09:16
也可以更進一步用ToString()來把DataTime轉成我們要得格式
Console.Write(DateTime.Now.ToString("HH:mm ss tt"));
21:18 01 下午
大寫HH表示用24小時制表示,也可以改成小寫hh用12小時制
Console.Write(DateTime.Now.ToString("HHmm"));
2118
日期也可以用DataTime取得,以下是範例:
Console.Write(DateTime.Now.ToShortDateString());
2012/10/30
同樣也可以用ToString()轉成我們要得格式
Console.Write(DateTime.Now.ToString("MM-dd-yyyy"));
10-30-2012
Console.Write(DateTime.Now.ToString("ddd dd MMM, yyyy"));
週二 30 十月, 2012

2012年6月29日 星期五

[C#]OpenFileDialog在XP會更改working directory

今天碰到一個奇怪的bug。我寫了一個C#的tool用來parsing一些binary的檔案,此程式運作流程如下:

1. 用OpenFileDialog讓使用者選取parsing的binary檔案
2. 程式讀取一個parsing過程會用到的參數設定檔(放在和執行檔同一個目錄下)
3. parsing binary並且把結果寫到一個新的檔案

看起來很正常的流程,在第二步讀取設定檔時卻會一直回傳找不到檔案的錯誤訊息。而且很奇怪的是這段程式在Win7上可以執行,在XP上卻不能執行。找了很久才發現在XP上執行OpenFileDialog會更改working directory,造成我用相對路徑開檔時會找不到設定檔。而Win7的working directory在執行OpenFileDialog並不會被更改。

以下是一個簡單的範例程式可以看出working directory在執行OpenFileDialog後會被改掉:
 var dlg = new Microsoft.Win32.OpenFileDialog();
 workingDirectory = Directory.GetCurrentDirectory(); // Correct
 if (open.ShowDialog() == DialogResult.OK) {
        workingDirectory = Directory.GetCurrentDirectory(); // Wrong!!
 }
上面例子執行後會發現workingDirectory最後會被設定成OpenFileDialog選取檔案的那個資料夾。
還好網路上蠻多人碰到這個問題,解決方法很簡單,只要把RestoreDirectory設成true就可以了,這個Property在WPF以及Windows Form的FileDialog中都有實做:
 var dlg = new Microsoft.Win32.OpenFileDialog();
 dlg.RestoreDirectory = true;
 workingDirectory = Directory.GetCurrentDirectory(); 
 if (open.ShowDialog() == DialogResult.OK) {
        workingDirectory = Directory.GetCurrentDirectory(); 
 }
執行後可以發現執行完OpenFileDialog後working directory不會被改掉了。至於為什麽Win7的working directory不會被改掉,推測是因為Win7的RestoreDirectory預設就是true,而XP預設為false。(此推測是錯誤的,原因是因為系統底層的差異)

2012年6月26日 星期二

[C#]Nullable Type

在C#中,你不能把一個int或bool變數設定成null。比如說以下這個範例:
bool b = null;  //以下程式編譯時會出現錯誤訊息
int i = null;     
DateTime date = null;   
但是有時候我們使用的變數不一定有值怎麼辦? 這時候就可以用Nullable type。只要在宣告變數時候在型態後面加一個問號,此變數就變成可以被設成null的Nullable type。
bool? nullableBool = null;
int? nullableInt = null;
DateTime? nullableDate = null;
一般的變數可以自動轉換成nullable type:
int i = 5566;
int? j = i;
如果要把nullable type轉換成一般變數,就需要強制轉換:
int? i = 5566;
int j = (int)i;
當一個變數是nullable type時,會自動多一個HasValue的特性。可以用來判斷變數是不是null。如果是null的話HasValue會是false。反之有值不是null的話,就會是true。
int? num = null;
if (num.HasValue == true)
{
    System.Console.WriteLine("num = " + num.Value);
}
使用nullable type最大的優點就是可以讓你程式比較不容易當掉,一些由user輸入的變數可以宣告成nullable type。如此一來當user輸入錯誤資料時候,可以把變數設定成null之後再做處理。