C#每個版本新特性
一直都認為學習語言應該系統的進行學習,瞭解每一個版本的新增特性,才能在實際應用中做到有的放矢。最近發現團隊中有不少人雖然用著最新的技術,但知識儲備還停留在一個比較初始的狀態,這樣在編碼過程中會走不少彎路。
本文梳理下C#從1.0到7.0版本的一些常用特性,對於不常用的或者我沒有用到過的一些特性,會列出來,但不會做詳細描述。另外C#8.0現在還沒有正式推出,並且目前我們也只是在使用dotNet Core2.1,所以C#8.0本文也不會涉及。
C#1.X
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
1.0 | VS2002 | 1.0 | 1.0 |
1.1 | VS2003 | 1.1 | 1.1 |
在C#1.0或1.1版本中,從語言的角度就是基本的面向物件的語法,可以說任何一本C#語言的書籍都包含了C#1.X的所有內容。
如果您已經在使用C#語言編寫程式碼,那麼C#1.X的相關知識應該已經掌握。基礎語法部分這裡就不再贅述了。
C#2.0
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
2.0 | VS2005 | 2.0 | 2.0 |
2.0中對應VS2005我用的也不多,因為很快就被VS2008替代了,不過在語言方面卻帶來了很多新的東西。
泛型
C#2中最重要的一個特性應該就是泛型。泛型的用處就是在一些場景下可以減少強制轉換來提高效能。在C#1中就有很多的強制轉換,特別是對一些集合進行遍歷時,如ArrayList、HashTable,因為他們是為不同資料型別設計的集合,所以他們中鍵和值的型別都是object,這就意味著會平凡發生裝箱拆箱的操作。C#2中有了泛型,所以我們可以使用List、Dictionary。泛型能夠帶來很好的編譯時型別檢查,也不會有裝箱拆箱的操作,因為型別是在使用泛型的時候就已經指定了。
.NET已經通過了很多的泛型型別供我們使用,如上面提到的List,Dictionary,我們也可以自己來建立泛型型別(類、介面、委託、結構)或是方法。在定義泛型型別或時可以通過定義泛型約束來對泛型引數進行限制,更好的使用編譯時檢查。泛型約束是通過關鍵字where來實現的,C#2中的泛型約束有4種:
-
引用型別約束:確保型別實參是引用型別,使用where T:class來表示;
-
值型別約束:確保型別實參是值型別,使用where T:truct來表示;
-
建構函式型別約束,使用where T:new()來表示;
-
轉換型別約束:約束型別實參是另外的一種型別,例如:where T:IDisposable 。
分部類(Partil)
分部類可以允許我們在多個檔案中為一個型別(class、struct、interface)編寫程式碼,在Asp.Net2.0中用的極為廣泛。新建一個Aspx頁面,頁面的CodeBehind和頁面中的控制元件的定義就是通過分部類來實現的。如下:
public partial class _Default : System.Web.UI.Page
public partial class _Default
分部類使用關鍵字partial來定義,當一個類中的程式碼非常多時,可以使用分部類來進行拆分,這對程式碼的閱讀很有好處,而且不會影響呼叫。不過現在我們前後端分離,後端程式碼要做到單一職責原則,不會有很多大的類,所以這個特性很少用到。
靜態類
靜態類中的公用方法必須也是靜態的,可以由類名直接呼叫,不需要例項化,比較適用於編寫一些工具類。如System.Math類就是靜態類。工具類有一些特點,如:所有成員都是靜態的、不需要被繼承、不需要進行例項化。在C#1中我們可以通過如下程式碼來實現:
//宣告為密封類防止被繼承
public sealed class StringHelper
{
//新增私有無參構造函ˉ數防止被例項化,如果不新增私有建構函式
//會自動生成共有無參建構函式
private StringHelper(){};
public static int StringToInt32(string input)
{
int result=0;
Int32.TryParse(input, out result);
return result;
}
}
C#2中可以使用靜態類來實現:
public static class StringHelper
{
public static int StringToInt32(string input)
{
int result=0;
Int32.TryParse(input, out result);
return result;
}
}
屬性的訪問級別
在C#1中宣告屬性,屬性中的get和set的訪問級別是和屬性一致,要麼都是public要麼都是private,如果要實現get和set有不同的訪問級別,則需要用一種變通的方式,自己寫GetXXX和SetXXX方法。在C#2中可以單獨設定get和set的訪問級別,如下:
private string _name;
public string Name
{
get { return _name; }
private set { _name = value; }
}
需要注意的是,不能講屬性設定為私有的,而將其中的get或是set設定成公有的,也不能給set和get設定相同的訪問級別,當set和get的訪問級別相同時,我們可以直接設定在屬性上。
命名空間別名
名稱空間可以用來組織類,當不同的名稱空間中有相同的類時,可以使用完全限定名來防止類名的衝突,C#1中可以使用空間別名來簡化書寫,空間別名用using關鍵字實現。但還有一些特殊情況,使用using並不能完全解決,所以C#2中提供了下面幾種特性:
-
名稱空間修飾符語法
-
全域性命名空間別名
-
外部別名
我們在構建名稱空間和類的時候,儘量避免出現衝突的情況,這個特性也較少用到。
友元程式集
當我們希望一個程式集中的型別可以被外部的某些程式集訪問,這時如果設定成Public,就可以被所有的外部程式集訪問。怎樣只讓部分程式集訪問,就要使用友元程式集了,具體參考之前的博文《C#:友元程式集(http://blog.fwhyy.com/2010/11/csharp-a-friend-assembly/)》
可空型別
可空型別就是允許值型別的值為null。通常值型別的值是不應該為null的,但我們很多應用是和資料庫打交道的,而資料庫中的型別都是可以為null值的,這就造成了我們寫程式的時候有時需要將值型別設定為null。在C#1中通常使用”魔值“來處理這種情況,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有型別的空值可以用DBNull.Value來表示。C#2中可空型別主要是使用System.Nullable的泛型型別,型別引數T有值型別約束。可以像下面這樣來定義可空型別:
Nullable<int> i = 20;
Nullable<bool> b = true;
C#2中也提供了更方便的定義方式,使用操作符?:
int? i = 20;
bool? b = true;
迭代器
C#2中對迭代器提供了更便捷的實現方式。提到迭代器,有兩個概念需要了解
-
可列舉物件和列舉器,實現了System.Collections.IEnumerable介面的物件是可列舉物件,這些物件可以被C#中的foreach進行迭代;
-
實現了System.Collections.IEnumeror介面的物件被稱為列舉器。在C#1中實現迭代器非常繁瑣,
看下面一個例子:
public class Test
{
static void Main()
{
Person arrPerson = new Person("oec2003","oec2004","oec2005");
foreach (string p in arrPerson)
{
Console.WriteLine(p);
}
Console.ReadLine();
}
}
public class Person:IEnumerable
{
public Person(params string[] names)
{
_names = new string[names.Length];
names.CopyTo(_names, 0);
}
public string[] _names;
public IEnumerator GetEnumerator()
{
return new PersonEnumerator(this);
}
private string this[int index]
{
get { return _names[index]; }
set { _names[index] = value; }
}
}
public class PersonEnumerator : IEnumerator
{
private int _index = -1;
private Person _p;
public PersonEnumerator(Person p) { _p = p; }
public object Current
{
get { return _p._names[_index]; }
}
public bool MoveNext()
{
_index++;
return _index < _p._names.Length;
}
public void Reset()
{
_index = -1;
}
}
C#2中的迭代器變得非常便捷,使用關鍵字yield return關鍵字實現,下面是C#2中使用yield return的重寫版本:
public class Test
{
static void Main()
{
Person arrPerson = new Person("oec2003","oec2004","oec2005");
foreach (string p in arrPerson)
{
Console.WriteLine(p);
}
Console.ReadLine();
}
}
public class Person:IEnumerable
{
public Person(params string[] names)
{
_names = new string[names.Length];
names.CopyTo(_names, 0);
}
public string[] _names;
public IEnumerator GetEnumerator()
{
foreach (string s in _names)
{
yield return s;
}
}
}
匿名方法
匿名方法比較適用於定義必須通過委託呼叫的方法,用多執行緒來舉個例子,在C#1中程式碼如下:
private void btnTest_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(DoWork));
thread.Start();
}
private void DoWork()
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
this.Invoke(new Action<string>(this.ChangeLabel),i.ToString());
}
}
private void ChangeLabel(string i)
{
label1.Text = i + "/100";
}
使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel兩個方法,程式碼如下:
private void btnTest_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(delegate() {
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
this.Invoke(new Action(delegate() { label1.Text = i + "/100"; }));
}
}));
thread.Start();
}
其他相關特性
-
固定大小緩衝區(Fixed-size buffers)
-
編譯指令(Pragma directives)
-
委託的逆變協變
C#3.0
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
3.0 | VS2008 | 2.0 | 3.0 3.5 |
如果說C#2中的核心是泛型的話,那麼C#3中的核心就應是Linq了,C#3中的特性幾乎都是為Linq服務的,但每一項特性都可以脫離Linq來使用。下面就來看下C#3中有哪些特性。
自動實現的屬性
這個特性非常簡單,就是使定義屬性變得更簡單了。程式碼如下:
public string Name { get; set; }
public int Age { private set; get; }
隱式型別的區域性變數和擴充套件方法
隱式型別的區域性變數是讓我們在定義變數時可以比較動態化,使用var關鍵字作為型別的佔位符,然後由編譯器來推導變數的型別。
擴充套件方法可以在現有的型別上新增一些自定義的方法,比如可以在string型別上新增一個擴充套件方法ToInt32,就可以像“20”.ToInt32()這樣呼叫了。
具體參見《C#3.0學習(1)—隱含型別區域性變數和擴充套件方法(http://blog.fwhyy.com/2008/02/learning-csharp-3-0-1-implied-type-of-local-variables-and-extension-methods/)》。
隱式型別雖然讓編碼方便了,但有些不少限制:
-
被宣告的變數只能是區域性變數,而不能是靜態變數和例項欄位;
-
變數在宣告的同時必須初始化,初始化值不能為null;
-
語句中只能宣告一個變數;
物件集合初始化器
簡化了物件和集合的建立,具體參見《C#3.0學習(2)—物件集合初始化器(http://blog.fwhyy.com/2008/02/learning-c-3-0-2-object-collection-initializer/)》。
隱式型別的陣列
和隱式型別的區域性變數類似,可以不用顯示指定型別來進行陣列的定義,通常我們定義陣列是這樣:
string[] names = { "oec2003", "oec2004", "oec2005" };
使用匿名型別陣列可以想下面這樣定義:
protected void Page_Load(object sender, EventArgs e)
{
GetName(new[] { "oec2003", "oec2004", "oec2005" });
}
public string GetName(string[] names)
{
return names[0];
}
匿名型別
匿名型別是在初始化的時候根據初始化列表自動產生型別的一種機制,利用物件初始化器來建立匿名物件的物件,具體參見《C#3.0學習(3)—匿名型別(http://blog.fwhyy.com/2008/03/learning-csharp-3-0-3-anonymous-types/)》。
Lambda表示式
實際上是一個匿名方法,Lambda表達的表現形式是:(引數列表)=>{語句},看一個例子,建立一個委託例項,獲取一個string型別的字串,並返回字串的長度。程式碼如下:
Func<string, int> func = delegate(string s) { return s.Length; };
Console.WriteLine(func("oec2003"));
使用Lambda的寫法如下:
Func<string, int> func = (string s)=> { return s.Length; };
Func<string, int> func1 = (s) => { return s.Length; };
Func<string, int> func2 = s => s.Length;
上面三種寫法是逐步簡化的過程。
Lambda表示式樹
是.NET3.5中提出的一種表達方式,提供一種抽象的方式將一些程式碼表示成一個物件樹。要使用Lambda表示式樹需要引用名稱空間System.Linq.Expressions,下面程式碼構建一個1+2的表示式樹,最終表示式樹編譯成委託來得到執行結果:
Expression a = Expression.Constant(1);
Expression b = Expression.Constant(2);
Expression add = Expression.Add(a, b);
Console.WriteLine(add); //(1+2) Func<int> fAdd = Expression.Lambda<Func<int>>(add).Compile();
Console.WriteLine(fAdd()); //3
Lambda和Lambda表示式樹為我們使用Linq提供了很多支援,如果我們在做的一個管理系統使用了Linq To Sql,在列表頁會有按多個條件來進行資料的篩選的功能,這時就可以使用Lambda表示式樹來進行封裝查詢條件,下面的類封裝了And和Or兩種條件:
public static class DynamicLinqExpressions
{
public static Expression<Func<T, bool>> True<T>() { return f => true; }
public static Expression<Func<T, bool>> False<T>() { return f => false; }
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.Or(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>
(Expression.And(expr1.Body, invokedExpr), expr1.Parameters);
}
}
下面是獲取條件的方法:
public Expression<Func<Courses, bool>> GetCondition()
{
var exp = DynamicLinqExpressions.True<Courses>();
if (txtCourseName.Text.Trim().Length > 0)
{
exp = exp.And(g => g.CourseName.Contains(txtCourseName.Text.Trim()));
}
if (ddlGrade.SelectedValue != "-1")
{
exp=exp.And(g => g.GradeID.Equals(ddlGrade.SelectedValue));
}
return exp;
}
Linq
Linq是一個很大的話題,也是NET3.5中比較核心的內容,有很多書籍專門來介紹Linq,下面只是做一些簡單的介紹,需要注意的是Linq並非是Linq To Sql,Linq是一個大的集合,裡面包含:
-
Linq To Object:提供對集合和物件的處理;
-
Linq To XML:應用於XML;
-
Linq To Sql:應用於SqlServer資料庫;
-
Linq To DataSet: DataSet;
-
Linq To Entities:應用於SqlServer之外的關係資料庫,我們還可以通過Linq的擴充套件框架來實現更多支援Linq的資料來源。
下面以Linq To Object為例子來看看Linq是怎麼使用的:
public class UserInfo
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Test
{
static void Main()
{
List<UserInfo> users = new List<UserInfo>()
{
new UserInfo{Name="oec2003",Age=20},
new UserInfo{Name="oec2004",Age=21},
new UserInfo{Name="oec2005",Age=22}
};
IEnumerable<UserInfo> selectedUser = from user in users
where user.Age > 20
orderby user.Age descending select user;
foreach (UserInfo user in selectedUser)
{
Console.WriteLine("姓名:"+user.Name+",年齡:"+user.Age);
}
Console.ReadLine();
}
}
可以看出,Linq可以讓我們使用類似Sql的關鍵字來對集合、物件、XML等進行查詢。
C#4.0
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
4.0 | VS2010 | 4.0 | 4.0 |
可選引數
VB在很早就已經支援了可選引數,而C#直到4了才支援,顧名思義,可選引數就是一些引數可以是可選的,在方法呼叫的時候可以不用輸入。看下面程式碼:
public class Test
{
static void Main()
{
Console.WriteLine(GetUserInfo()); //姓名:ooec2003,年齡:30
Console.WriteLine(GetUserInfo("oec2004", 20));//姓名:ooec2004,年齡:20
Console.ReadLine();
}
public static string GetUserInfo(string name = "oec2003", int age = 30)
{
return "姓名:" + name + ",年齡:" + age.ToString();
}
}
命名實參
命名實參是在制定實參的值時,可以同時指定相應引數的名稱。編譯器可以判斷引數的名稱是否正確,命名實參可以讓我們在呼叫時改變引數的順序。命名實參也經常和可選引數一起使用,看下面的程式碼:
static void Main()
{
Console.WriteLine(Cal());//9
Console.WriteLine(Cal(z: 5, y: 4));//25
Console.ReadLine();
}
public static int Cal(int x=1, int y=2, int z=3)
{
return (x + y) * z;
}
通過可選引數和命名引數的結合使用,我們可以減少程式碼中方法的過載。
動態型別
C#使用dynamic來實現動態型別,在沒用使用dynamic的地方,C#依然是靜態的。靜態型別中當我們要使用程式集中的類,要呼叫類中的方法,編譯器必須知道程式集中有這個類,類裡有這個方法,如果不能事先知道,編譯時會報錯,在C#4以前可以通過反射來解決這個問題。看一個使用dynamic的小例子:
dynamic a = "oec2003";
Console.WriteLine(a.Length);//7
Console.WriteLine(a.length);//string 型別不包含length屬性,但編譯不會報錯,執行時會報錯
Console.ReadLine();
您可能會發現使用dynamic宣告變數和C#3中提供的var有點類似,其他他們是有本質區別的,var宣告的變數在編譯時會去推斷出實際的型別,var只是相當於一個佔位符,而dynamic宣告的變數在編譯時不會進行型別檢查。
dynamic用的比較多的應該是替代以前的反射,而且效能有很大提高。假設有一個名為DynamicLib的程式集中有一個DynamicClassDemo類,類中有一個Cal方法,下面看看利用反射怎麼訪問Cal方法:
namespace DynamicLib
{
public class DynamicClassDemo
{
public int Cal(int x = 1, int y = 2, int z = 3)
{
return (x + y) * z;
}
}
}
static void Main()
{
Assembly assembly = Assembly.Load("DynamicLib");
object obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
Type type = obj.GetType();
MethodInfo method = type.GetMethod("Cal");
Console.WriteLine(method.Invoke(obj, new object[] { 1, 2, 3 }));//9
Console.ReadLine();
}
用dynamic的程式碼如下:
Assembly assembly = Assembly.Load("DynamicLib");
dynamic obj = assembly.CreateInstance("DynamicLib.DynamicClassDemo");
Console.WriteLine(obj.Cal());
Console.ReadLine();
在前後端分離的模式下,WebAPI介面的引數也可以採用dynamic來定義,直接就可以解析前端傳入的json引數,不用每一個介面方法都定義一個引數型別。不好的地方就是通過Swagger來生產API文件時,不能明確的知道輸入引數的每個屬性的含義。
C#4中還有一些COM互操作性的改進和逆變性和協變性的改進,我幾乎沒有用到,所以在此就不講述了。
C#5.0
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
5.0 | VS2012\2013 | 4.0 | 4.5 |
非同步處理
非同步處理是C#5中很重要的一個特性,會涉及到兩個關鍵字:async和await,要講明白這個需要單獨寫一篇來介紹。
可以簡單理解為,當Winform窗體程式中有一個耗時操作時,如果是同步操作,窗體在返回結果之前會卡死,當然在C#5之前的版本中有多種方法可以來解決這個問題,但C#5的非同步處理解決的更優雅。
迴圈中捕獲變數
與其說是一個特性,不如說是對之前版本問題的修復,看下面的程式碼:
public static void CapturingVariables()
{
string[] names = { "oec2003","oec2004","oec2005"};
var actions = new List<Action>();
foreach(var name in names)
{
actions.Add(() => Console.WriteLine(name));
}
foreach(Action action in actions)
{
action();
}
}
這段程式碼在之前的C#版本中,會連續輸出三個oec2005,在C#5中會按照我們的期望依次輸出oec2003、oec2004、oec2005。
如果您的程式碼在之前的版本中有利用到這個錯誤的結果,那麼在升級到C#5或以上版本中就要注意了。
呼叫者資訊特性
我們的程式通常是以release形式釋出,釋出後很難追蹤到程式碼執行的具體資訊,在C#5中提供了三種特性(Attribute), 允許獲取呼叫者的當前編譯器的執行檔名、所在行數與方法或屬性名稱。程式碼如下:
static void Main(string[] args)
{
ShowInfo();
Console.ReadLine();
}
public static void ShowInfo(
[CallerFilePath] string file = null,
[CallerLineNumber] int number = 0,
[CallerMemberName] string name = null)
{
Console.WriteLine($"filepath:{file}");
Console.WriteLine($"rownumber:{number}");
Console.WriteLine($"methodname:{name}");
}
呼叫結果如下:
filepath:/Users/ican_macbookpro/Projects/CsharpFeature/CsharpFeature5/Program.cs
rownumber:12
methodname:Main
C#6.0
C# | VS版本 | CLR版本 | .NET Framework |
---|---|---|---|
6.0 | VS2015 | 4.0 | 4.6 |
在C#6中提供了不少的新功能,我認為最有用的就是Null條件運算子和字串嵌入。
Null條件運算子
在C#中,一個常見的異常就是“未將物件引用到物件的例項”,原因是對引用物件沒有做非空判斷導致。在團隊中雖然再三強調,但依然會在這個問題上栽跟頭。下面的程式碼就會導致這個錯誤:
class Program
{
static void Main(string[] args)
{
//Null條件運算子
User user = null;
Console.WriteLine(user.GetUserName());
Console.ReadLine();
}
}
class User
{
public string GetUserName() => "oec2003";
}
要想不出錯,就需要對user物件做非空判斷
if(user!=null)
{
Console.WriteLine(user.GetUserName());
}
在C#6中可以用很簡單的方式來處理這個問題
//Null條件運算子
User user = null;
Console.WriteLine(user?.GetUserName());
注:雖然這個語法糖非常簡單,也很好用,但在使用時也需要多想一步,當物件為空時,呼叫其方法返回的值也是空,這樣的值對後續的操作會不會有影響,如果有,還是需要做判斷,並做相關的處理。
字串嵌入
字串嵌入可以簡化字串的拼接,很直觀的就可以知道需要表達的意思,在C#6及以上版本中都應該用這種方式來處理字串拼接,程式碼如下:
//字串嵌入
string name = "oec2003";
//之前版本的處理方式1
Console.WriteLine("Hello " + name);
//之前版本的處理方式2
Console.WriteLine(string.Format("Hello {0}",name));
//C#6字串嵌入的處理方式
Console.WriteLine($"Hello {name}");
其他相關特性
-
只讀自動屬性
-
自動屬性初始化表示式
-
using static
-
nameof表示式
-
異常篩選器
-
使用索引器初始化關聯集合
C#7.0
C# | VS版本 | .NET Framework |
---|---|---|
7.0 | VS2017 15.0 | .NET Core1.0 |
7.1 | VS2017 15.3 | .NET Core2.0 |
7.2 | VS2017 15.5 | .NET Core2.0 |
7.3 | VS2017 15.7 | .NET Core2.1 |
out 變數
此特性簡化了out變數的使用,之前的版本中使用程式碼如下:
int result = 0;
int.TryParse("20", out result);
Console.WriteLine(result);
優化後的程式碼,不需要事先定義一個變數
int.TryParse("20", out var result);
Console.WriteLine(result);
模式匹配
這也是一個減少我們編碼的語法糖,直接看程式碼吧
public class PatternMatching
{
public void Test()
{
List<Person> list = new List<Person>();
list.Add(new Man());
list.Add(new Woman());
foreach (var item in list)
{
//在之前版本中此處需要做型別判斷和型別轉換
if (item is Man man)
Console.WriteLine(man.GetName());
else if (item is Woman woman)
Console.WriteLine(woman.GetName());
}
}
}
public abstract class Person
{
public abstract string GetName();
}
public class Man:Person
{
public override string GetName() => "Man";
}
public class Woman : Person
{
public override string GetName() => "Woman";
}
詳細參考官方文件:https://docs.microsoft.com/zh-cn/dotnet/csharp/pattern-matching
本地方法
可以在方法中寫內部方法,在方法中有時需要在多個程式碼邏輯執行相同的處理,之前的做法是在類中寫私有方法,現在可以讓這個私有方法寫在方法的內部,提高程式碼可讀性。
static void LocalMethod()
{
string name = "oec2003";
string name1 = "oec2004";
Console.WriteLine(AddPrefix(name));
Console.WriteLine(AddPrefix(name1));
string AddPrefix(string n)
{
return $"Hello {n}";
}
}
非同步 main 方法
這個最大的好處是,在控制檯程式中除錯非同步方法變得很方便。
static async Task Main()
{
await SomeAsyncMethod();
}
private protected 訪問修飾符
可以限制在同一個程式集中的派生類的訪問,是對protected internal的一種補強,protected internal是指同一程式集中的類或派生類進行訪問。
其他相關特性
-
元組優化(7.0)
-
棄元(7.0)
-
Ref 區域性變數和返回結果(7.0)
-
通用的非同步返回型別(7.0)
-
數字文字語法改進(7.0)
-
throw 表示式(7.0)
-
預設文字表示式(7.1)
-
推斷元組元素名稱(7.1)
-
非尾隨命名引數(7.2)
-
數值文字中的前導下劃線(7.2)
-
條件 ref 表示式(7.2)
總結
每個特性都需要我們去編碼實現下,瞭解了真正的含義和用途,我們才能在工作