- 用泛型實現參數化類型(Parameterized typing with generics)
- 泛型帶來了更好的編譯時檢查、更清楚的看到所要表達的訊息,當然還有IDE的支持及型能的提升,減少boxing和unboxing
- 泛型有泛型類型(generic types)跟泛型方法(generic methods)
- 最簡單的泛型就是Dictionary<TKey, TValue>
- 泛型類型分為兩種,未繫結泛型類型(unbound generic type)就是泛型角框號裡的型別是T的,已建構類型(constructed type)
//未繫結泛型類型
Dictionary<Tkey, Tvalue> unbound = New Dictionary<Tkey, Tvalue>();
//已建構類型
Dictionary<string, int> cons = New Dictionary<string, int>();
- 已建構類型有分開放類型(open type)、封閉類型(closed type),未繫結泛型類型基本上就是開放類型,其他皆為封閉類型
- 泛型方法
static double TakeSquareRoot(int x)
{
return Math.Sqrt(x);
}
List<int> integers = new List<int>();
integers.Add(1);
integers.Add(2);
integers.Add(3);
integers.Add(4);
Converter<int double> converter = TakeSquareRoot;
List<double> doubles;
//調用泛型方法
doubles = integers.ConvertAll(converter);
foreach (double item in doubles)
{ }
- 泛型類型的條件約束(type constraint)
//參考型別限制
struct RefSample<T> where T : class { }
//值型別限制
class ValSample<T> where T : struct { }
//建構式限制
public T CreateInstance<T>() where T : new()
{
return new T();
}
//轉換型別限制
class Sample<T> where T : Stream
struct Sample<T> where T : IDisposable
class Sample<T> where T : IComparable<T>
//T繼承至U
class Sample<T, U> where T : U
//組合限制
class Sample<T> where T : class, IDisposable, new()
class Sample<T, U> where T : class where U : struct, T
- 泛型類型的推斷(type inference)
static Lits<T, U> MakeList<T, U>(T first, Tsecond);
List<string, U> list = MakeList<string, U>("Line 1", "Line 2");
//類型推斷可簡化寫法成
List<string, U> list = MakeList("Line 1", "Line 2");
- 實作泛型(Implementing generics),自行撰寫泛型時有幾件事需要注意
- 預設關鍵字(Default value Expressions),利用default取出型別的預設值
static int CompareToDefault<T, U>(T value)
where T : IComparable<T, U>
{
return value.CompareTo(default(T));
}
Console.WriteLine(CompareToDefault("x")); //1
Console.WriteLine(CompareToDefault(10)); //1
Console.WriteLine(CompareToDefault(0)); //0
Console.WriteLine(CompareToDefault(-10)); //-1
Console.WriteLine(CompareToDefault(DateTime.MinValue)); //0
- 直接比較(Direct comparisons),運用到多載運算子(overload operator),泛型T不可直接==或!=比較,必須要運用EqualityComparer<T>、Comparer<T>
static bool AreReferencesEqual<T>(T first, T second)
where T : class
{
return first == second;
}
string name = "Jon";
string intro1 = "My name is " + name;
string intro2 = "My name is " + name;
Console.WriteLine(intro1 == intro2); //true
Console.WriteLine(AreReferencesEqual(intro1, intro2)); //false
- 一個完整的泛型實作範例
public sealed class Pair<T1, T2> : IEquatable<Pair<T1, T2>>
{
private static readonly IEqualityComparer<T1> FirstComparer =
EqualityComparer<T1>.Default;
private static readonly IEqualityComparer<T2> SecondComparer =
EqualityComparer<T2>.Default;
private readonly T1 first;
private readonly T2 second;
public Pair(T1 first, T2 second)
{
this.first = first;
this.second = second;
}
public T1 First { get { return first; } }
public T2 Second { get { return second; } }
public bool Equals(Pair<T1, T2> other)
{
return other != null &&
FirstComparer.Equals(this.First, other.First) &&
SecondComparer.Equals(this.Second, other.Second);
}
public override bool Equals(object o)
{
return Equals(o as Pair<T1, T2>);
}
public override int GetHashCode()
{
return FirstComparer.GetHashCode(first) * 37 +
SecondComparer.GetHashCode(second);
}
}
- 高級泛型(Advanced generics)
- 泛型在C#的限制(Limitations of generics in C#)
- 缺乏可變性,這部分我倒認為蠻好的,這確保了型別的安全性,在C#4.0前有一些方式可處理共變數及反變數,但處理的方式我覺得都不太直覺
- 缺乏運算子操作約束,也就是在泛型類別裡,運用到 += 這種運算子去加T型別時造成的問題,C#4.0前沒有好的方法可解決
- 缺乏泛型屬性、索引器和其他成員類型,這個問題一般來說不太會遇到
- Null的類型 (Saying nothinh with nullable types)
- 在C# 1.0 時還沒有nullable的型別,有三種模式可以來表示nullable
- 魔術值(The magic value),基本上就是定義一個空值讓耀表示空值得來指定,像Datetime可以把空值指定到Datetime.MinValue,ADO.NET就會使用DBNULL.Value來表示
- 包裝參考型別
- 額外的註記
- System.Nullable<T> and System.Nullable
- System.Nullable<T>是Value Type
- 有HasValue及Value,HasValue提供Boolen
- GetValueOrDefault如果沒有值則基於基底型別提供預設值,有則同Value,或者可以給予預設值,ex : GetValueOrDefault(10)
- C# 2 的語法糖
- 使用?來寫跟System.Nullable<T>一樣,ex: int?
- 提到Nullable與非Nullable的比較及運算式,這比較沒什麼爭議,基本上很直覺的可以判斷
- 可以使用as語法來判斷是否可轉型,確保型別判斷正確,但這部分有效能考量,這牽扯到boxing和unboxing,使用上得注意
static void PrintValueAsInt32(object o)
{
int? nullable = o as int?;
Console.WriteLine(nullable.HasValue ?
nullable.Value.ToString() : "null");
}
PrintValueAsInt32(5); //5
PrintValueAsInt32("some string"); //null
- 使用 ?? 的語法(null coalescing operator)來簡化程式碼並提高可讀性,原則上第一個值的判斷必須要是可為Null的型別,不然就毫無意義了
//原本的寫法
DateTime lastAlive = (death == null ? DateTime.Now : death.Value);
return lastAlive - birth;
//??的方式
DateTime lastAlive = death ?? DateTime.Now;
return lastAlive - birth;
//也可簡化成1行
return (death ?? DateTime.Now) - birth;
//判斷的變數的數量沒有限制,可寫成這樣
Address contact = user.ContactAddress ??
order.ShippingAddress ??
user.BillingAddress;
- Nullable的額外特殊用法,利用??的語法延伸出的變化,下面範例是可以簡化類別比較的寫法
public static class PartialComparer
{
public static int? Compare<T>(T first, T second)
{
return Compare(Comparer<T>.Default, first, second);
}
public static int? Compare<T>(IComparer<T> comparer,
T first, T second)
{
int ret = comparer.Compare(first, second);
return ret == 0 ? new int?() : ret;
}
public static int? ReferenceCompare<T>(T first, T second)
where T : class
{
return first == second ? 0
: first == null ? -1
: second == null ? 1
: new int?();
}
}
public int Compare(Product first, Product second)
{
return PC.ReferenceCompare(first, second) ??
PC.Compare(second.Popularity, first.Popularity) ??
PC.Compare(first.Price, second.Price) ??
PC.Compare(first.Name, second.Name) ??
0;
}
- 進入快速通道的委派(Fast-tracked delegates)
- 跟笨拙的委派語法說拜拜(Saying goodbye to awkward delegate syntax),以下範例是C# 1.0
static void LogPlainEvent(object sender, EventArgs e)
{
Console.WriteLine("LogPlain");
}
static void LogKeyEvent(object sender, KeyPressEventArgs e)
{
Console.WriteLine("LogKey");
}
static void LogMouseEvent(object sender, MouseEventArgs e)
{
Console.WriteLine("LogMouse");
}
...
Button button = new Button();
button.Text = "Click me";
button.Click += new EventHandler(LogPlainEvent);
button.KeyPress += new KeyPressEventHandler(LogKeyEvent);
button.MouseClick += new MouseEventHandler(LogMouseEvent);
Form form = new Form();
form.AutoSize = true;
form.Controls.Add(button);
Application.Run(form);
- 群組方法轉換(Method group conversions),可簡略語法,但還不算是很好的寫法
- 共變數與反變數(Covariance and contravariance),下面範例是前面看到的範例改以C# 2.0的方式,看起來沒什麼意義只是要表達這樣的寫法
static void LogPlainEvent(object sender, EventArgs e)
{
Console.WriteLine("An event occurred");
}
Button button = new Button();
button.Text = "Click me";
button.Click += LogPlainEvent;
button.KeyPress += LogPlainEvent;
button.MouseClick += LogPlainEvent;
Form form = new Form();
form.AutoSize = true;
form.Controls.Add(button);
Application.Run(form);
- 委派回傳值的反變數(Covariance of delegate return types),也就是是否為上層型別可否轉型,以下範例表示MemoryStream可轉型成Stream,但反過來就不行
delegate Stream StreamFactory();
static MemoryStream GenerateSampleData()
{
byte[] buffer = new byte[16];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = (byte) i;
}
return new MemoryStream(buffer);
}
StreamFactory factory = GenerateSampleData;
using (Stream stream = factory())
{
int data;
while ((data = stream.ReadByte()) != -1)
{
Console.WriteLine(data);
}
}
- 使用匿名方法的委派實作(Inline delegate actions with anonymous methods)
- 處理一個參數(action on a parameter),運用泛型委派Action<T>
Action<string> printReverse = delegate(string text)
{
char[] chars = text.ToCharArray();
Array.Reverse(chars);
Console.WriteLine(new string(chars));
};
Action<int> printRoot = delegate(int number)
{
Console.WriteLine(Math.Sqrt(number));
};
Action<IList<double>> printMean = delegate(IList<double> numbers) {
double total = 0;
foreach (double value in numbers)
{
total += value;
Console.WriteLine(total / numbers.Count);
};
printReverse("Hello world");
printRoot(2);
printMean(new double[] { 1.5, 2.5, 3, 4.5 });
- 前面範例的匿名方法有點繁瑣,我們在用更精簡的方式
List<int> x = new List<int>();
x.Add(5);
x.Add(10);
x.Add(15);
x.Add(20);
x.Add(25);
x.ForEach(delegate(int n) {
Console.WriteLine(Math.Sqrt(n));
});
- 匿名方法回傳值,第一個範例我們用C#2內建的Predicate來實作判斷值回傳true/false,第二個範例我們一樣用C#2的Comparison來實作排序的比較
Predicate isEven = delegate(int x) { return x % 2 == 0; };
Console.WriteLine(isEven(1));
Console.WriteLine(isEven(4));
static void SortAndShowFiles(string title, Comparison sortOrder)
{
string[] fileName = Directory.GetFiles(@"C:\");
FileInfo[] files = new FileInfo[fileName.Length];
//
for (int i0 = 0; i0 < fileName.Length; i0++)
{
files[i0] = new FileInfo(fileName[i0]);
}
Array.Sort(files, sortOrder);
//
Console.WriteLine(title);
foreach (FileInfo file in files)
{
Console.WriteLine(" {0} ({1} bytes)", file.Name, file.Length);
}
}
SortAndShowFiles("Sorted by name:", delegate (FileInfo f1, FileInfo f2) {
return f1.Name.CompareTo(f2.Name);
});
SortAndShowFiles("Sorted by length:", delegate (FileInfo f1, FileInfo f2) {
return f1.Length.CompareTo(f2.Length);
});
- 忽略委派的參數,利用前面Button的範例再來改寫一下,用匿名方法不帶參數的方式,大大的簡化了程式碼
Button button = new Button();
button.Text = "Click me";
button.Click += delegate { Console.WriteLine("LogPlain"); };
button.KeyPress += delegate { Console.WriteLine("LogKey"); };
button.MouseClick += delegate { Console.WriteLine("LogMouse"); };
Form form = new Form();
form.AutoSize = true;
form.Controls.Add(button);
Application.Run(form);
- 在匿名方法裡抓取變數(Capturing variables in anonymous methods)
- 這邊所代表意思就是在定義匿名方法時有用到外層的變數,因為執行的階段不一樣所以會導致變數可能在某個時間點被變更,匿名方法要等到被執行時才抓取當下的變數值,如以下範例
string captured = "before x is created";
MethodInvoker x = delegate
{
Console.WriteLine(captured);
captured = "changed by x";
};
captured = "directly before x is invoked";
x();
Console.WriteLine(captured);
captured = "before second invocation";
x();
//執行這段城市會得到以下的結果
directly before x is invoked
changed by x
before second invocation
- 再來看點複雜的範例,如果有變數是在內部宣告及外部宣告時的情境,因為在for迴圈裡宣告變數時會視為各迴圈的獨立變數個體,所以傳進委派方法時都會是獨立的,下面範例foreach會輸出0,10,20,30,40,再來分別是1,2,3 11
List<methodinvoker> list = new List<methodinvoker>();
for (int index = 0; index < 5; index++)
{
int counter = index * 10;
list.Add(delegate
{
Console.WriteLine(counter);
counter++;
});
}
foreach (MethodInvoker t in list)
{
t();
}
list[0]();
list[0]();
list[0]();
//
list[1]();
- 簡單實作迭代器(Implementing iterators the easy way)
- C# 1.0必須自己刻出iterators的實作,要自行記錄狀態,如MoveNext、Current、Reset等,非常之麻煩。
- C# 2.0提供yield的方式來達到iterators的實作,大大的減少瑣碎的程式碼撰寫
-
來看個範例,這個範例主要是顯示出iterators的執行過程
static readonly string Padding = new string(' ', 30);
static IEnumerable<int> CreateEnumerable()
{
Console.WriteLine("{0}Start of CreateEnumerable()", Padding);
for (int i = 0; i < 3; i++)
{
Console.WriteLine("{0}About to yield {1}", Padding, i);
yield return i;
Console.WriteLine("{0}After yield", Padding);
}
Console.WriteLine("{0}Yielding final value", Padding);
yield return -1;
Console.WriteLine("{0}End of CreateEnumerable()", Padding);
}
IEnumerable<int> iterable = CreateEnumerable();
IEnumerator<int> iterator = iterable.GetEnumerator();
Console.WriteLine("Starting to iterate");
while (true)
{
Console.WriteLine("Calling MoveNext()...");
bool result = iterator.MoveNext();
Console.WriteLine("... MoveNext result={0}", result);
if (!result)
{
break;
}
Console.WriteLine("Fetching Current...");
Console.WriteLine("... Current result={0}", iterator.Current);
}
有幾點要注意幾個地方
- CreateEnumerable執行直到MoveNext才開始執行方法裡的程式
- 執行Current不會執行任何程式
- yeild return執行完就停住直到下次MoveNext
- 一個方法可以有多個yeild return
- 方法會直到MoveNext為false才會結束
- 它可以在執行過程中中斷它,yeild break,另外還可以在裡面下try..Catch..Finally,如果不是用yeild break而是直接return做停止執行iterators時finally理論上不會執行,但他會等到最後離開foreach時執行,這是比較特別的地方
- 這邊有幾點要了解的
- 在第一次執行MoveNext之前,取得Current會返回型別的預設值
- MoveNext返回false後,Current會儲存最後一個值
- Reset是不實作的
- 真實使用iterators,這邊作者列了3個例子
static IEnumerable<DateTime> DateRange
{
get
{
for (DateTime day = new DateTime(2016,6,3); day <= new DateTime(2016, 07, 01); day = day.AddDays(1))
{
yield return day;
}
}
}
foreach (var item in DateRange)
{
//列出日期
var a = item;
}
- 列出文件
foreach (var item in ReadLines("C:/Programing/test.txt"))
{
string a = item;
}
static IEnumerable<string> ReadLines(Func<TextReader> provider)
{
using (TextReader reader = provider())
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
static IEnumerable<string> ReadLines(string filename)
{
return ReadLines(delegate {
return File.OpenText(filename);
});
}
- 利用謂詞作篩選
IEnumerable<string> lines = ReadLines("C:/Programing/test.txt");
Predicate<string> predicate = delegate (string line)
{ return line.StartsWith("abc"); };
foreach (string line in Where(lines, predicate))
{
Console.WriteLine(line);
}
public static IEnumerable<T> Where<T>(IEnumerable<T> source,
Predicate<T> predicate)
{
if (source == null || predicate == null)
{
throw new ArgumentNullException();
}
return WhereImpl(source, predicate);
}
private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source,
Predicate<T> predicate)
{
foreach (T item in source)
{
if (predicate(item))
{
yield return item;
}
}
}
- 剩下的C# 2.0的新特性(Conculding C# 2 the final features)
- partial types部分類型,就是class及method可以建立成部分,可分布在不同的檔案,通常是用在有程式碼產生器產出的類別中可以讓程式設計師惡外加東西在類別上,這是就可另外建立partial避免程式碼被洗掉,或是有需要分工的部分也可以用partial做區分;在C# 3.0有多增加一個特性,就是假設在partial裡只有宣告但沒指定method的實作時編譯器會忽略此段code,避免程式碼出錯,如以下範例
// Generated.cs
using System;
partial class PartialMethodDemo
{
public PartialMethodDemo()
{
OnConstructorStart(); //這段在編譯器就看不到
Console.WriteLine("Generated constructor");
OnConstructorEnd();
}
partial void OnConstructorStart();
partial void OnConstructorEnd();
}
// Handwritten.cs
using System;
partial class PartialMethodDemo
{
partial void OnConstructorEnd()
{
Console.WriteLine("Manual code");
}
}
- static class靜態類別,在C# 1.0時只有靜態方法,靜態類別有幾點要注意
- 不可宣告為抽象類別(abstract)及密封類別(sealed)
- 不可繼承介面並實作類別
- 不可有任何非靜態的方法,包含建構子
- 不可以有運算子
- 不可有protected或protected internal
- 獨立的getter/setter 屬性,C# 1.0不能定義public get / private set
- 命名空間別名,如果有相同的名字在不同的命名空間時可使用
using System;
using WinForms = System.Windows.Forms;
using WebForms = System.Web.UI.WebControls;
class WinForms {}
class Test {
static void Main()
{
Console.WriteLine(typeof(WinForms::Button));
Console.WriteLine(typeof(WebForms::Button));
}
}
- 使用global::,例如有兩個class,一個有命名空間另一個沒有的情況下可使用
using System;
class Configuration {}
namespace Chapter7
{
class Configuration {}
class Test {
static void Main()
{
Console.WriteLine(typeof(Configuration));
Console.WriteLine(typeof(global::Configuration));
Console.WriteLine(typeof(global::Chapter7.Test));
}
}
}
- 外部別名(Extern allases),如引用兩個外部類別庫都有相同的命名空間及方法時可使用
// Compile with
// csc Test.cs /r:FirstAlias=First.dll /r:SecondAlias=Second.dll
extern alias FirstAlias;
extern alias SecondAlias;
using System;
using FD = FirstAlias::Demo;
class Test
{
static void Main()
{
Console.WriteLine(typeof(FD.Example));
Console.WriteLine(typeof(SecondAlias::Demo.Example));
}
}
- 使用Pragma指令
- 在VS撰寫程式碼時常常會有一些警告訊息,如變數未使用等,通常都會去修正不正確的地方,Pragma的指令是讓你可以暫時忽略這些警告,比如像以下的範例
public class FieldUsedOnlyByReflection {
#pragma warning disable 0169
int x;
#pragma warning restore 0169
}
- 非安全程式碼中的固定暫存大小(Fixed-size buffers in unsafe code)
- 可以使用fixed陳述式,在資料結構中建立具有固定大小陣列的緩衝區
private fixed char name[30];
- 把內部的成員暴露給指定的類別庫(Exposing Internal members to selected assembles)
// Compiled to Source.dll
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("FriendAssembly")]
public class Source
{
internal static void InternalMethod() {}
public static void PublicMethod() {}
}
// Compiled to FriendAssembly.dll
public class Friend
{
static void Main()
{
Source.InternalMethod();
Source.PublicMethod();
}
}
// Compiled to EnemyAssembly.dll
public class Enemy
{
static void Main()
{
// Source.InternalMethod();
Source.PublicMethod();
}
}
- 通常會使用到他的地方是在寫單元測試的時候,但也不是必要的
- 這邊提到剩下的C# 2.0新增的特性也不是非常必要或是常用的,但有需要時可以使用.