轉html
C# 2.0web
泛型(Generics)express
泛型是CLR 2.0中引入的最重要的新特性,使得能夠在類、方法中對使用的類型進行參數化。編程
例如,這裏定義了一個泛型類:數組
class MyCollection<T> { T variable1; private void Add(T param) { } }
使用的時候:安全
MyCollection<string> list2 = new MyCollection<string>();
MyCollection<Object> list3 = new MyCollection<Object>();
泛型的好處app
泛型方法、泛型委託、泛型接口less
除了泛型類以外,還有泛型方法、泛型委託、泛型接口:異步
//泛型委託 public static delegate T1 MyDelegate<T1, T2>(T2 item);
MyDelegate<Int32, String> MyFunc = new MyDelegate<Int32, String>(SomeMethd); //泛型接口 public class MyClass<T1, T2, T3> : MyInteface<T1, T2, T3> { public T1 Method1(T2 param1, T3 param2) { throw new NotImplementedException(); } } interface MyInteface<T1, T2, T3> { T1 Method1(T2 param1, T3 param2); } //泛型方法 static void Swap<T>(ref T t1, ref T t2) { T temp = t1; t1 = t2; t2 = temp; }
String str1 = "a"; String str2 = "b"; Swap<String>(ref str1, ref str2);
泛型約束(constraints)
能夠給泛型的類型參數上加約束,能夠要求這些類型參數知足必定的條件
約束async |
說明 |
where T: struct | 類型參數需是值類型 |
where T : class | 類型參數需是引用類型 |
where T : new() | 類型參數要有一個public的無參構造函數 |
where T : <base class name> | 類型參數要派生自某個基類 |
where T : <interface name> | 類型參數要實現了某個接口 |
where T : U | 這裏T和U都是類型參數,T必須是或者派生自U |
這些約束,能夠同時一塊兒使用:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
default 關鍵字
這個關鍵可使用在類型參數上:
default(T);
對於值類型,返回0,引用類型,返回null,對於結構類型,會返回一個成員值所有爲0的結構實例。
迭代器(iterator)
能夠在不實現IEnumerable就能使用foreach語句,在編譯器碰到yield return時,它會自動生成IEnumerable 接口的方法。在實現迭代器的方法或屬性中,返回類型必須是IEnumerable, IEnumerator, IEnumerable<T>,或 IEnumerator<T>。迭代器使得遍歷一些零碎數據的時候很方便,不用去實現Current, MoveNext 這些方法。
public System.Collections.IEnumerator GetEnumerator() { yield return -1; for (int i = 1; i < max; i++) { yield return i; } }
可空類型(Nullable Type)
可空類型System.Nullable<T>,可空類型僅針對於值類型,不能針對引用類型去建立。System.Nullable<T>簡寫爲T ?。
int? num = null; if (num.HasValue == true) { System.Console.WriteLine("num = " + num.Value); } else { System.Console.WriteLine("num = Null"); }
若是HasValue爲false,那麼在使用value值的時候會拋出異常。把一個Nullable的變量x賦值給一個非Nullable的變量y能夠這麼寫:
int y = x ?? -1;
匿名方法(Anonymous Method)
在C#2.0以前,給只能用一個已經申明好的方法去建立一個委託。有了匿名方法後,能夠在建立委託的時候直接傳一個代碼塊過去。
delegate void Del(int x); Del d = delegate(int k) { /* ... */ }; System.Threading.Thread t1 = new System.Threading.Thread (delegate() { System.Console.Write("Hello, "); } ); 委託語法的簡化// C# 1.0的寫法 ThreadStart ts1 = new ThreadStart(Method1); // C# 2.0能夠這麼寫 ThreadStart ts2 = Method1;
委託的協變和逆變(covariance and contravariance)
有下面的兩個類:
class Parent { } class Child: Parent { }
而後看下面的兩個委託:
public delegate Parent DelgParent();
public delegate Child DelgChild();
public static Parent Method1() { return null; }
public static Child Method2() { return null; }
static void Main() { DelgParent del1= Method1; DelgChild del2= Method2; del1 = del2; }
注意上面的,DelgParent 和DelgChild 是徹底不一樣的類型,他們之間自己沒有任何的繼承關係,因此理論上來講他們是不能相互賦值的。可是由於協變的關係,使得咱們能夠把DelgChild類型的委託賦值給DelgParent 類型的委託。協變針對委託的返回值,逆變針對參數,原理是同樣的。
部分類(partial)
在申明一個類、結構或者接口的時候,用partial關鍵字,可讓源代碼分佈在不一樣的文件中。我以爲這個東西徹底是爲了照顧Asp.net代碼分離而引入的功能,真沒什麼太大的實際用處。微軟說在一些大工程中能夠把類分開在不一樣的文件中讓不一樣的人去實現,方便團隊協做,這個我以爲純屬胡扯。
部分類僅是編譯器提供的功能,在編譯的時候會把partial關鍵字定義的類和在一塊兒去編譯,和CRL沒什麼關係。
靜態類(static class)
靜態類就一個只能有靜態成員的類,用static關鍵字對類進行標示,靜態類不能被實例化。靜態類理論上至關於一個只有靜態成員而且構造函數爲私有的普通類,靜態類相對來講的好處就是,編譯器可以保證靜態類不會添加任何非靜態成員。
global::
這個表明了全局命名空間(最上層的命名空間),也就是任何一個程序的默認命名空間。
class TestApp { public class System { } const int Console = 7; static void Main() { //用這個訪問就會出錯,System和Console都被佔用了 //Console.WriteLine(number); global::System.Console.WriteLine(number); } }
extern alias
用來消除不一樣程序集中類名重複的衝突,這樣能夠引用同一個程序集的不一樣版本,也就是說在編譯的時候,提供了一個將有衝突的程序集進行區分的手段。
在編譯的時候,使用命令行參數來指明alias,例如:
/r:aliasName=assembly1.dll
在Visual Studio裏面,在被引用的程序集的屬性裏面能夠指定Alias的值,默認是global。
而後在代碼裏面就可使用了:
extern alias aliasName; //這行須要在using這些語句的前面 using System; using System.Collections.Generic; using System.Text; using aliasName.XXX;
屬性Accessor訪問控制
public virtual int TestProperty { protected set { } get { return 0; } }
友元程序集(Friend Assembly)
可讓其它程序集訪問本身的internal成員(private的仍是不行),使用Attributes來實現,例如:
[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]
注意這個做用範圍是整個程序集。
fixed關鍵字
可使用fixed關鍵字來建立固定長度的數組,可是數組只能是bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, double中的一種。
這主要是爲了更好的處理一些非託管的代碼。好比下面的這個結構體:
public struct MyArray { public fixed char pathName[128]; }
若是不用fixed的話,沒法預先佔住128個char的空間,使用fixed後能夠很好的和非託管代碼進行交互。
volatile關鍵字
用來表示相關的字可能被多個線程同時訪問,編譯器不會對相應的值作針對單線程下的優化,保證相關的值在任什麼時候候訪問都是最新的。
#pragma warning
用來取消或者添加編譯時的警告信息。每一個警告信息都會有個編號,若是warning CS01016之類的,使用的時候取CS後面的那個數字,例如:
#pragma warning disable 414, 3021
這樣CS414和CS3021的警告信息就都不會顯示了。
C# 3.0
類型推斷
申明變量的時候,能夠不用直指定類型:
var i = 5;
var s = "Hello";
//兩種寫法是同樣的 int i = 5; string s = "Hello";
類型推斷也支持數組:
var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[] { "hello", null, "world」 }; // string[]
擴展方法
擴展方法必須被定義在靜態類中,而且必須是非泛型、非嵌套的靜態類。例如:
public static class JeffClass { public static int StrToInt32(this string s) { return Int32.Parse(s); } public static T[] SomeMethd<T>(this T[] source, int pram1, int pram2) { /**/ } }
上面一個是給string類型的對象添加了一個方法,另外一個是給全部類型的數組添加了一個方法,方法有兩個整型參數。
擴展方法只在當前的命名空間類有效,若是所在命名空間被其它命名空間import引用了,那麼在其它命名空間中也有效。擴展方法的優先級低於其它的常規方法,也就是說若是擴展方法與其它的方法相同,那麼擴展方法不會被調用。
Lamda表達式
能夠當作是對匿名方法的一個語法上的簡化,可是λ表達式同時能夠裝換爲表達式樹類型。
對象和集合的初始化
var contacts = new List<Contact> {
new Contact { Name = "Chris", PhoneNumbers = { "123455", "6688" } }, new Contact { Name = "Jeffrey", PhoneNumbers = { "112233" } } };
匿名類型
var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2;
自動屬性
會自動生成一個後臺的私有變量
public Class Point
{
public int X { get; set; } public int Y { get; set; } }
查詢表達式
這個其實就是擴展方法的運用,編譯器提供了相關的語法便利,下面兩端代碼是等價的:
from g in
from c in customers group c by c.Country select new { Country = g.Key, CustCount = g.Count() } customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() })
表達式樹
Func<int,int> f = x => x + 1; Expression<Func<int,int>> e = x => x + 1;
C# 4.0
協變和逆變
這個在C#2.0中就已經支持委託的協變和逆變了,C#4.0開始支持針對泛型接口的協變和逆變:
IList<string> strings = new List<string>(); IList<object> objects = strings;
協變和逆變僅針對引用類型。
動態綁定
看例子:
class BaseClass
{
public void print() { Console.WriteLine(); } }
Object o = new BaseClass();
dynamic a = o;
//這裏能夠調用print方法,在運行時a會知道本身是個什麼類型。 這裏的缺點在於編譯的時候沒法檢查方法的合法性,寫錯的話就會出運行時錯誤。 a.print();
可選參數,命名參數
private void CreateNewStudent(string name, int studentid = 0, int year = 1)
這樣,最後一個參數不給的話默認值就是1,提供這個特性能夠免去寫一些重載方法的麻煩。
調用方法的時候,能夠指定參數的名字來給值,不用按照方法參數的順序來制定參數值:
CreateNewStudent(year:2, name:"Hima", studentid: 4); //沒有按照方法定義的參數順序
C# 5.0
1. 異步編程
在.Net 4.5中,經過async和await兩個關鍵字,引入了一種新的基於任務的異步編程模型(TAP)。在這種方式下,能夠經過相似同步方式編寫異步代碼,極大簡化了異步編程模型。以下式一個簡單的實例:
static async void DownloadStringAsync2(Uri uri)
{
var webClient = new WebClient();
var result = await webClient.DownloadStringTaskAsync(uri);
Console.WriteLine(result);
}
而以前的方式是這樣的:
static void DownloadStringAsync(Uri uri)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
Console.WriteLine(e.Result);
};
webClient.DownloadStringAsync(uri);
}
也許前面這個例子不足以體現async和await帶來的優越性,下面這個例子就明顯多了:
public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
Action<IAsyncResult> readWriteLoop = null;
readWriteLoop = iar =>
{
for (bool isRead = (iar == null); ; isRead = !isRead)
{
switch (isRead)
{
case true:
iar = source.BeginRead(buffer, 0, buffer.Length,
readResult =>
{
if (readResult.CompletedSynchronously) return;
readWriteLoop(readResult);
}, null);
if (!iar.CompletedSynchronously) return;
break;
case false:
int numRead = source.EndRead(iar);
if (numRead == 0)
{
return;
}
iar = destination.BeginWrite(buffer, 0, numRead,
writeResult =>
{
if (writeResult.CompletedSynchronously) return;
destination.EndWrite(writeResult);
readWriteLoop(null);
}, null);
if (!iar.CompletedSynchronously) return;
destination.EndWrite(iar);
break;
}
}
};
readWriteLoop(null);
}
public async Task CopyToAsync(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await destination.WriteAsync(buffer, 0, numRead);
}
}
關於基於任務的異步編程模型須要介紹的地方還比較多,不是一兩句能說完的,有空的話後面再專門寫篇文章來詳細介紹下。另外也可參看微軟的官方網站:Visual Studio Asynchronous Programming,其官方文檔Task-Based Asynchronous Pattern Overview介紹的很是詳細, VisualStudio中自帶的CSharp Language Specification中也有一些說明。
2. 調用方信息
不少時候,咱們須要在運行過程當中記錄一些調測的日誌信息,以下所示:
public void DoProcessing()
{
TraceMessage("Something happened.");
}
爲了調測方便,除了事件信息外,咱們每每還須要知道發生該事件的代碼位置以及調用棧信息。在C++中,咱們能夠經過定義一個宏,而後再宏中經過__FILE__和__LINE__來獲取當前代碼的位置,但C#並不支持宏,每每只能經過StackTrace來實現這一功能,但StackTrace卻有不是很靠譜,經常獲取不了咱們所要的結果。
針對這個問題,在.Net 4.5中引入了三個Attribute:CallerMemberName、CallerFilePath和CallerLineNumber。在編譯器的配合下,分別能夠獲取到調用函數(準確講應該是成員)名稱,調用文件及調用行號。上面的TraceMessage函數能夠實現以下:
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
另外,在構造函數,析構函數、屬性等特殊的地方調用CallerMemberName屬性所標記的函數時,獲取的值有所不一樣,其取值以下表所示:
調用的地方 |
CallerMemberName獲取的結果 |
方法、屬性或事件 |
方法,屬性或事件的名稱 |
構造函數 |
字符串 ".ctor" |
靜態構造函數 |
字符串 ".cctor" |
析構函數 |
該字符串 "Finalize" |
用戶定義的運算符或轉換 |
生成的名稱成員,例如, "op_Addition"。 |
特性構造函數 |
特性所應用的成員的名稱 |
例如,對於在屬性中調用CallerMemberName所標記的函數便可獲取屬性名稱,經過這種方式能夠簡化 INotifyPropertyChanged 接口的實現。
C# 6.0
一、自動屬性的加強
1.一、自動屬性初始化 (Initializers for auto-properties)
C#4.0下的果斷實現不了的。
C#6.0中自動屬性的初始化方式
只要接觸過C#的確定都會喜歡這種方式。真是簡潔方便呀。
1.二、只讀屬性初始化Getter-only auto-properties
先來看一下咱們以前使用的方式吧
public class Customer { public string Name { get; } public Customer(string firstName,string lastName) { Name = firstName +" "+ lastName; } }
再來看一下C#6.0中
public class Customer { public string FirstName { get; }="aehyok"; public string LastName { get; }="Kris"; }
和第一條自動屬性初始化使用方式一致。
二、Expression bodied function members
2.1 用Lambda做爲函數體Expression bodies on method-like members
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
再來舉一個簡單的例子:一個沒有返回值的函數
public void Print() => Console.WriteLine(FirstName + " " + LastName);
2.二、Lambda表達式用做屬性Expression bodies on property-like function members
public override string ToString() { return FirstName + " " + LastName; }
如今C#6中
public class User { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() => string.Format("{0}——{1}", FirstName, LastName); public string FullName => FirstName + " " + LastName; }
三、引用靜態類Using Static
在Using中能夠指定一個靜態類,而後能夠在隨後的代碼中直接使用靜態的成員
四、空值判斷Null-conditional operators
直接來看代碼和運行結果
經過結果能夠發現返回的都爲null,不再像之前那樣繁瑣的判斷null勒。
五、字符串嵌入值
在字符串中嵌入值
以前一直使用的方式是
如今咱們能夠簡單的經過以下的方式進行拼接
六、nameof表達式nameof expressions
在方法參數檢查時,你可能常常看到這樣的代碼(以前用的少,此次也算學到了)
public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException("customer"); } }
裏面有那個customer是咱們手寫的字符串,在給customer更名時,很容易把下面的那個字符串忘掉,C#6.0 nameof幫咱們解決了這個問題,看看新寫法
public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException(nameof(customer)); } }
七、帶索引的對象初始化器Index initializers
直接經過索引進行對象的初始化,原來真的能夠實現
經過這種方式能夠發現字典中只有三個元素,因此也就只有這三個索引能夠訪問額,其餘類型的對象和集合也是能夠經過這種方式進行初始化的,在此就不進行一一列舉了。
八、異常過濾器 (Exception filters)
先來看一個移植過來的方法
try { var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" }; } catch (ArgumentNullException e) { if (e.ParamName == "customer") { Console.WriteLine("customer can not be null"); } }
在微軟的文檔中還給出了另外一種用法,這個異常會在日誌記錄失敗時拋給上一層調用者
private static bool Log(Exception e) { ///處理一些日誌 return false; } static void Main(string[] args) { try { /// } catch (Exception e){if (!Log(e)) { } } Console.ReadLine(); }
九、catch和finally 中的 await —— Await in catch and finally blocks
在C#5.0中,await關鍵字是不能出如今catch和finnaly塊中的。而在6.0中
try { res = await Resource.OpenAsync(…); // You could do this. … } catch (ResourceException e) { await Resource.LogAsync(res, e); // Now you can do this … } finally { if (res != null) await res.CloseAsync(); // … and this. }
十、無參數的結構體構造函數—— Parameterless constructors in structs