C#高級編程9 第17章 使用VS2013
編輯定位到
?
如果默認勾選了這項,請去掉勾選,因為勾選之后解決方案的目錄會根據當前文件選中。
?可以設置項目并行生成數
?
版本控制軟件設置
所有文本編輯器行號顯示
?
啟用編輯繼續
?收集調試信息,將影響性能
?
?Code Compare這是擴展與更新里面的插件,安裝之后才會顯示,用來比較代碼是否相同
?nuget包源配置,提供了nuget更新的數據源
?目標框架的設置影響到項目基礎框架的引用,不同目標框架的項目之間不能互相引用。
?不安全代碼和警告等級可能會影響生成
特定頁指定了項目運行的起始頁面,URL指定了端口號和虛擬目錄,必須創建虛擬目錄之后才能運行項目,調試器一般使用asp.net
?項目發布一般只需要運行所需的文件
?windows服務項目可以選擇啟用本機調試,如果出現無法寫入內存的錯誤時。
web發布
?
?
?
?
?
?
?
VS2015....VS2017...
?
C#6.0特性:
- NameOf表達式。曾幾何時,我們一直在hardcode各種參數異常,譬如:?
void ThrowArgumentNullException(string firstVersionArgumentName)?
{?
? threw new ArgumentNullException(“firstVersionArgumentName”, “can not be null”);?
}?
很悲催的是第二版說不定PM就說:“這個參數名字不合適,咱改改吧”,得益于IDE的重構功能,這個很容易,直接F2改名然后回車,簽入代碼;若干天后,測試找上門來,說你的參數名字是變了,但是異常信息沒變。好吧,原來這里的hardcode字符組,這個是不會隨著重構功能改變的!?
再來看看新的Nameof表達式給我帶來什么,同樣的功能,代碼如下:?
void ThrowArgumentNullException(string firstVersionArgumentName)?
{?
? threw new ArgumentNullException(nameof(firstVersionArgumentName), “can not be null”);?
}?
在回到IDE中,再次按F2觸發重構改名,你會發現異常信息也能一起改變了。 - 空值判斷操作符(Null-conditional operators),又一個重量級代碼提升,直接上示例代碼:?
public static string Tuncate(this string value, int length)?
{?
? if(!string.IsNullOrEmpty(value))?
? {?
? ? return value.Substring(0, Math.Min(value.Length, length));?
? }?
? return value;?
}?
這只是一個很小的折影,在開發過程中我們有無數這樣的方法,無數次重復為空判斷,但是這對代碼的可讀性和業務處理沒有任何提升,反而增加了代碼復雜度,讓我們更難理解當初的設計初衷。顯然,C#6.0使用null-conditional operators來向前推進了一大步:?
public static string Tuncate(this string value, int length)?
{?
? return value?.Substring(0, Math.Min(value.Length, length));?
}?
是不是更加簡潔明了,而且能突出業務核心邏輯! - 字符串嵌入值(string interpolation),終于可以擺脫長長的string.Format函數了,如下代碼就可以輕松改寫了:?
var fullName = string.Format(“FirstName is {0}, LastName is {1}”, customer.FirstName, customer.LastName);?
使用新特性之后代碼:?
var fullName = “FirstName is \{customer.FirstName}, LastName is \{customer.LastName}”; - Lambda表達式函數和僅get的屬性。對于那些只有一兩句話的函數,可以省掉一些廢話了,這個新功能可以大大節省人力:?
public override string ToString() => “\{FirstName} \{LastName}”;?
public override int GetHashcode() => (FirstName.GetHashcode()^8) & (LastName.GetHashcode());?
public DateTime TimeStamp { get; } => DateTime.UtcNow; - 自動屬性(auto-property)和索引初始化(Index initializers),終于可以像變量一樣給屬性賦初值了,大大提升代碼可讀性。?
public string FirstName { get; set; } = “John”;?
public string LastName { get; set; } = “Lennon”;?
private Dictionary<int, string> _dicts = new Dictionary<int, string> { [3] = “third”, [8] = “eight” };?
public string FullName { get; }?
pubic MyClass ()??
{?
? FullName = “\{FirstName} \{LastName}”;?
} - 異常過濾器(Exception filter),回想曾經的錯誤處理,為了提示不同的錯誤,我們不得不定義多個自定義異常,有了異常過濾器之后,我們可以通過給異常添加一個簡單的額外屬性就可以解決了:?
try { … }?
catach ( CustomException ex ) if ( CheckException(ex) )?
{ … }??
想想這個還有一個好處,比如嚴重異常日志,在這個過濾器里我們可以最簡單的判斷,發現若果是嚴重的問題,可以直接做更早的提醒。 - 引用靜態類(using static),懶人必備,想想某大仙在前面定義了一個超級無敵的靜態類和輔助方法,你有超級多的地方需要用,然后你就得一遍一遍的敲這個靜態類名和方法名,萬一這個靜態類名字很長就更悲催了,拷貝吧,最后總是看著大段大段重復心里很不爽(程序員大部分都有代碼潔癖),好吧,這個應用靜態類就能很好的解決了:?
using GrapeCity.Demo.LongLongNameStaticClass;?
void AnotherMethod()?
{?
? UtilA(…) // no LongLongNameStaticClass.UtilA(…)?
} - Await增強,終于可以把await放到catch和finally塊中了,典型的用例是像IO資源操作之類可以簡單整潔的處理關閉了:?
Resource res = null;?
try?
{?
? res = await Resource.OpenAsync(…); //一直都可以而且一直這么做的?
? ...?
}?
catch(ResourceException ex)?
{?
? await Resource.LogAsync(res, ex); //寫日志吧,不阻塞?
}?
finally?
{?
? res?.CloseAsync(); //結合空值判斷操作符更簡潔明了?
}
- NameOf表達式。曾幾何時,我們一直在hardcode各種參數異常,譬如:?
C#7.0特性
1. out-variables(Out變量)
以前,我們使用out變量的時候,需要在外部先申明,然后才能傳入方法,類似如下:
string ddd = ""; //先申明變量 ccc.StringOut(out ddd); Console.WriteLine(ddd);
在c#7.0中我們可以不必申明,直接在參數傳遞的同時申明它,如下:
StringOut(out string ddd); //傳遞的同時申明 Console.WriteLine(ddd); Console.ReadLine();
?
2.Tuples(元組)
曾今在.NET4.0中,微軟對多個返回值給了我們一個解決方案叫元組,類似代碼如下:
static void Main(string[] args){var data = GetFullName();Console.WriteLine(data.Item1);Console.WriteLine(data.Item2);Console.WriteLine(data.Item3);Console.ReadLine(); } static Tuple<string, string, string> GetFullName() {return new Tuple<string, string, string>("a", "b", "c"); }
上面代碼展示了一個方法,返回含有3個字符串的元組,然而當我們獲取到值,使用的時候 心已經炸了,Item1,Item2,Item3是什么鬼,雖然達到了我們的要求,但是實在不優雅
那么,在C#7.0中,微軟提供了更優雅的方案:(注意:需要通過nuget引用System.ValueTuple)如下:
static void Main(string[] args){var data=GetFullName();Console.WriteLine(data.a); //可用命名獲取到值Console.WriteLine(data.b);Console.WriteLine(data.c);Console.ReadLine();}//方法定義為多個返回值,并命名private static (string a,string b,string c) GetFullName(){return ("a","b","c");}
解構元組,有的時候我們不想用var匿名來獲取,那么如何獲取abc呢?我們可以如下:
static void Main(string[] args){//定義解構元組(string a, string b, string c) = GetFullName();Console.WriteLine(a);Console.WriteLine(b);Console.WriteLine(c);Console.ReadLine();}private static (string a,string b,string c) GetFullName(){return ("a","b","c");}
?
3.?Pattern Matching(匹配模式)
在C#7.0中,引入了匹配模式的玩法,先舉個老栗子.一個object類型,我們想判斷他是否為int如果是int我們就加10,然后輸出,需要如下:
object a = 1; if (a is int) //is判斷 {int b = (int)a; //拆int d = b+10; //加10Console.WriteLine(d); //輸出 }
那么在C#7.0中,首先就是對is的一個小擴展,我們只需要這樣寫就行了,如下:
object a = 1; if (a is int c) //這里,判斷為int后就直接賦值給c {int d = c + 10;Console.WriteLine(d); }
這樣是不是很方便?特別是經常用反射的同志們..
那么問題來了,挖掘機技術哪家強?!(咳咳,呸 開玩笑)
其實是,如果有多種類型需要匹配,那怎么辦?多個if else?當然沒問題,不過,微軟爸爸也提供了switch的新玩法,我們來看看,如下:
我們定義一個Add的方法,以Object作為參數,返回動態類型
static dynamic Add(object a){dynamic data;switch (a){case int b:data=b++;break;case string c:data= c + "aaa";break;default:data = null;break;}return data;}
下面運行,傳入int類型:
object a = 1; var data= Add(a); Console.WriteLine(data.GetType()); Console.WriteLine(data);
輸出如圖:
我們傳入String類型的參數,代碼和輸出如下:
object a = "bbbb"; var data= Add(a); Console.WriteLine(data.GetType()); Console.WriteLine(data);
通過如上代碼,我們就可以體會到switch的新玩法是多么的順暢和強大了.
匹配模式的Case When篩選
有的基友就要問了.既然我們可以在Switch里面匹配類型了,那我們能不能順便篩選一下值?答案當然是肯定的.
我們把上面的Switch代碼改一下,如下:
switch (a){case int b when b < 0:data = b + 100;break;case int b:data=b++;break;case string c:data= c + "aaa";break;default:data = null;break;}
在傳入-1試試,看結果如下:
?
?
4.ref?locals and returns(局部變量和引用返回)
?
首先我們知道 ref關鍵字是將值傳遞變為引用傳遞
那么我們先來看看ref locals(ref局部變量)
列子代碼如下:
static void Main(string[] args){int x = 3;ref int x1 = ref x; //注意這里,我們通過ref關鍵字 把x賦給了x1x1 = 2;Console.WriteLine($"改變后的變量 {nameof(x)} 值為: {x}");Console.ReadLine();}
這段代碼最終輸出 "2"
大家注意注釋的部分,我們通過ref關鍵字把x賦給了x1,如果是值類型的傳遞,那么對x將毫無影響 還是輸出3.
好處不言而喻,在某些特定的場合,我們可以直接用ref來引用傳遞,減少了值傳遞所需要開辟的空間.
?
接下來我們看看ref ?returns?(ref引用返回)
這個功能其實是非常有用的,我們可以把值類型當作引用類型來進行return
老規矩,我們舉個栗子,代碼如下:
很簡單的邏輯..獲取指定數組的指定下標的值
static ref int GetByIndex(int[] arr, int ix) => ref arr[ix]; //獲取指定數組的指定下標
我們編寫測試代碼如下:
int[] arr = { 1, 2, 3, 4, 5 };ref int x = ref GetByIndex(arr, 2); //調用剛才的方法x = 99;Console.WriteLine($"數組arr[2]的值為: {arr[2]}");Console.ReadLine();
我們通過ref返回引用類型,在重新賦值, arr數組中的值,相應也改變了.
總結一下:ref關鍵字很早就存在了,但是他只能用于參數,這次C#7.0讓他不僅僅只能作為參數傳遞,還能作為本地變量和返回值了
?
5.Local Functions (局部函數)
嗯,這個就有點顛覆..大家都知道,局部變量是指:只在特定過程或函數中可以訪問的變量。
那這個局部函數,顧名思義:只在特定的函數中可以訪問的函數(媽蛋 好繞口)
使用方法如下:
?
public static void DoSomeing(){//調用Dosmeing2int data = Dosmeing2(100, 200);Console.WriteLine(data);//定義局部函數,Dosmeing2.int Dosmeing2(int a, int b){return a + b;}}
呃,解釋下來 大概就是在DoSomeing中定義了一個DoSomeing2的方法,..在前面調用了一下.(注:值得一提的是局部函數定義在方法的任何位置,都可以在方法內被調用,不用遵循逐行解析的方式)
?
6.More expression-bodied members(更多的函數成員的表達式體)
C#6.0中,提供了對于只有一條語句的方法體可以簡寫成表達式。
如下:
public void CreateCaCheContext() => new CaCheContext();//等價于下面的代碼public void CreateCaCheContext(){new CaCheContext();}
但是,并不支持用于構造函數,析構函數,和屬性訪問器,那么C#7.0就支持了..代碼如下:
// 構造函數的表達式寫法 public CaCheContext(string label) => this.Label = label;// 析構函數的表達式寫法 ~CaCheContext() => Console.Error.WriteLine("Finalized!");private string label;// Get/Set屬性訪問器的表達式寫法 public string Label {get => label;set => this.label = value ?? "Default label"; }
7.throw
?Expressions (異常表達式)
在C#7.0以前,我們想判斷一個字符串是否為null,如果為null則拋除異常,我們需要這么寫:
public string IsNull(){string a = null;if (a == null){throw new Exception("異常了!");}return a;}
?
這樣,我們就很不方便,特別是在三元表達式 或者非空表達式中,都無法拋除這個異常,需要寫if語句.
那么我們在C#7.0中,可以這樣:
public string IsNull(){string a = null;return a ?? throw new Exception("異常了!");}
?
8.Generalized async return types (通用異步返回類型)
嗯,這個,怎么說呢,其實我異步用的較少,所以對這個感覺理解不深刻,還是覺得然并卵,在某些特定的情況下應該是有用的.
我就直接翻譯官方的原文了,實例代碼也是官方的原文.
異步方法必須返回 void,Task 或 Task<T>,這次加入了新的ValueTask<T>,來防止異步運行的結果在等待時已可用的情境下,對 Task<T> 進行分配。對于許多示例中設計緩沖的異步場景,這可以大大減少分配的數量并顯著地提升性能。
官方的實例展示的主要是意思是:一個數據,在已經緩存的情況下,可以使用ValueTask來返回異步或者同步2種方案
public class CaCheContext{public ValueTask<int> CachedFunc(){return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(loadCache());}private bool cache = false;private int cacheResult;private async Task<int> loadCache(){// simulate async work:await Task.Delay(5000);cache = true;cacheResult = 100;return cacheResult;}}
調用的代碼和結果如下:
//main方法可不能用async修飾,所以用了委托.static void Main(string[] args){Action act = async () =>{CaCheContext cc = new CaCheContext();int data = await cc.CachedFunc();Console.WriteLine(data);int data2 = await cc.CachedFunc();Console.WriteLine(data2);};// 調用委托 act();Console.Read();}
上面的代碼,我們連續調用了2次,第一次,等待了5秒出現結果.第二次則沒有等待直接出現結果和預期的效果一致.
?
9.Numeric literal syntax improvements(數值文字語法改進)
這個就純粹的是..為了好看了.
在C#7.0中,允許數字中出現"_"這個分割符號.來提高可讀性,舉例如下:
int a = 123_456;int b = 0xAB_CD_EF;int c = 123456;int d = 0xABCDEF;Console.WriteLine(a==c);Console.WriteLine(b==d);//如上代碼會顯示兩個true,在數字中用"_"分隔符不會影響結果,只是為了提高可讀性
當然,既然是數字類型的分隔符,那么?decimal
,?float
?和?double??都是可以這樣被分割的..