C#新特性

C#7.1

C#7

1.out variables

//以前必須先定義,再使用,不在同一行上,而且有可能沒有賦值就使用
int numericResult;
if (int.TryParse(input, out numericResult))
    WriteLine(numericResult);
else
    WriteLine("Could not parse input");

//現在可以在使用的地方定義,變量作用域會自動“leaks"到外面
if (int.TryParse(input, out int result))
    WriteLine(result);
else
    WriteLine("Could not parse input");
//而且可以使用var類型推斷
if (int.TryParse(input, out var answer))
    WriteLine(answer);
else
    WriteLine("Could not parse input");

2.System.ValueTuple

//1).要使用,需要通過NuGet下載System.ValueTuple包
//2).聲明和訪問元素
//未指定每個field的名字,自動用Item1, Item2...
var letters = ("a", "b");

//左側指定每個field的名字
(string Alpha, string Beta) namedLetters = ("a", "b");

//右側指定每個field的名字
var alphabetStart = (Alpha: "a", Beta: "b");

//兩邊都指定以左邊爲尊,同時獲得一個警告
(string First, string Second) firstLetters = (Alpha: "a", Beta: "b");

System.Diagnostics.Trace.WriteLine($"{namedLetters.Alpha},{letters.Item1}, 
{alphabetStart.Beta}, {firstLetters.First}");

//3).tuple最常用的地方:作爲函數返回值,省去定義結構
List<int> numbers = new List<int>() { 10, 1, 9, 7, 20, 8 };
//返回的是tuple,其成員是Min,Max
var range = Range(numbers);
System.Diagnostics.Trace.WriteLine($"range is [{range.Min}, {range.Max}]");

//4).解構tuple
//有時可能需要解構元組(deconstructing tuple),這可以通過爲元組中的每個值聲明獨立的變量來實現
(int max, int min) = Range(numbers);
System.Diagnostics.Trace.WriteLine($"range is [{min}, {max}]");

//5).可以爲任何.net類型提供deconstruction功能
//要實現這個功能,需要爲類添加一個名爲Deconstruct的成員函數,
//併爲你像想要抽取的成員提供參數類型爲out的參數
var p = new MyPoint(1, 2, 3);
(double X, double Y, double Z) = p;//解構MyPoint
//名字可以和Deconstruct裏面的不一樣,它們並非邦死的
(double horizontalDistance, double verticalDistance, double z) = p;

//for 3). and 4).
//tuple最有用的地方是作爲private/internal方法的返回值, 這省去了創建class/struct類型作爲返回值
private static (int Max, int Min) Range(IEnumerable<int> numbers)
{//好處如下:
//You save the work of authoring a class or a struct that defines the type returned.
//You do not need to create new type.
//The language enhancements removes the need to call the Create<T1>(T1) methods.
    int min = int.MaxValue;
    int max = int.MinValue;
    foreach (var n in numbers)
    {
        min = (n < min) ? n : min;
        max = (n > max) ? n : max;
    }
    return (max, min);
}

//for 5).
public class MyPoint
{
    public double X { get; }
    public double Y { get; }
    public double Z { get; }

    public MyPoint(double x, double y, double z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    //要實現Deconstruction功能,需要爲類添加一個名爲Deconstruct的成員函數,
    //併爲你像想要抽取的成員提供參數類型爲out的參數
    public void Deconstruct(out double x, out double y, out double z)
    {
        x = this.X;
        y = this.Y;
        z = this.Z;
    }
}

C#6

1.Read-only auto-properties 只讀自動屬性
以前版本:類內部可以set這兩個屬性

public string FirstName { get; private set; }
public string LastName { get; private set; }

新版本:只有在構造函數裏面可以設置這兩個屬性

public string FirstName { get; }
public string LastName { get;  }

2.Auto-Property Initializers 自動屬性初始化器
允許在聲明自動屬性(無論是隻讀還是讀寫)的同時給它賦初值

public ICollection<double> Grades { get; } = new List<double>();
public Standing YearInSchool { get; set;} = Standing.Freshman;

3.Expression-bodied function members表達式函數成員
支持method 和 read-only property.

public class Student
{   //auto-property
    public string FirstName { get; set; } = "FirstName";
    public string LastName { get; set; } = "LastName";

    //public field
    public int ID;

    //It works for methods and read-only properties.
    //只讀屬性(別和上面的ID域混淆)舉例
    public string FullName => $"{FirstName}.{LastName}";
    //函數舉例
    public override string ToString() => $"{FullName}";
}
var s = new Student();
System.Diagnostics.Trace.WriteLine(s.ToString());
s.FullName = "abc";//error, readonly
s.ID = 7;//ok

4.using static
using static導入的靜態方法;using則導入namespace裏的所有。
最好的一個例子是System.Math,因爲它裏面沒有實例方法。

以前不能using System.Math,因爲System.Math不是namespace;
現在可以using static System.Math,因爲using static可以導入類的靜態方法。
注意即使一個類不全是靜態方法,也可以用這種方法導入它的靜態方法,比如System.String。

//注意Math和String都是類
using static System.Math;
Sqrt(2);

using static System.String;//注意要使用String而不是string
if (IsNullOrWhiteSpace(lastName))
{
}

using static不包括extension methods,因爲擴展方法實際使用的是類實例對象。

5.Null-conditional operators(null條件操作符)
訪問對象之前通常都需要判空以免引用空對象。null條件操作符”?.”提供了方便。

//person爲null,則返回null;否則返回person.FirstName
//表達式的返回類型爲null或者string
var first = person?.FirstName;
//經常和null coalescing operator(??)一起使用
//表達式的返回類型爲string
var first = person?.FirstName ?? "Unspecified";

“?.”運算符保證操作符前面的值只被求值一次,這比用if語句判空更準確

//if判斷的時候可能不爲空,但使用的時候可能已經爲空了
var handler = this.SomethingHappened;
if (handler != null)
    handler(this, eventArgs);
if (this.SomethingHappened != null)
    this.SomethingHappened(this, eventArgs);
//如果使用"?."可以保證此操作符前面的值只被求值一次,避免上述情況
this.SomethingHappened?.Invoke(this, eventArgs);

6.String Interpolation(字符串插值)
C#6提供了比string.format更簡潔的語法”$”

public string FullName
{
    get
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}
簡化爲:
public string FullName => $"{FirstName} {LastName}";

格式化輸出,”$”支持string支持的所有format控制,用”:”指定格式控制符

public string GetFormattedGradePoint() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average()}";

//控制2位浮點數
public string GetGradePointPercentage() =>

//使用"?:"表達式時,要注意用括號來區別格式控制符
public string GetGradePointPercentages() =>
    $"Name: {LastName}, {FirstName}. G.P.A: {(Grades.Any() ? Grades.Average() : double.NaN):F2}";

//"$"裏面的表達式沒有任何顯示,你可以使用想用的任何表達式,包括複雜的LINQ查詢
public string GetAllGrades() =>
    $@"All Grades: {Grades.OrderByDescending(g => g)
    .Select(s => s.ToString("F2")).Aggregate((partial, element) => $"{partial}, {element}")}";

//"$"還支持指定specific cultures
FormattableString str = @"Average grade is {s.Grades.Average()}";
var gradeStr = string.Format(null, 
    System.Globalization.CultureInfo.CreateSpecificCulture("de-de"),
    str.GetFormat(), str.GetArguments());

7.Exception Filters(異常過濾)
當過濾表達式爲true時,執行catch;否則,跳過catch.

用法之一是用於檢查catch從句是否能處理某種異常(One use is to examine information about an exception to determine if a catch clause can process the exception)

//以前這樣寫
public static async Task<string> MakeRequest()
{ 
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e)
    {
        if (e.Message.Contains("301"))
            return "Site Moved";
        else
            throw;//這樣做,將導致原始異常拋出點到這裏的信息丟失,而使用異常過濾可以避免這種情況發生。
        //The actual exception object will contain the original call stack,
        //but all other information about any variables in the call stack 
        //between this throw point and the location of the original throw point has been lost.
    }
}
//現在可以這樣寫
public static async Task<string> MakeRequest()
{ 
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        return "Site Moved";
    }
}

用法之二是用於log例程,讓log例程始終返回false,利用when過濾來記錄log。
Another recommended pattern with exception filters is to use them for logging routines. This usage also leverages the manner in which the exception throw point is preserved when an exception filter evaluates to false.

//log例程是參數爲Exception,返回值始終爲false的函數,如下:
//A logging method would be a method whose argument is the 
//exception that unconditionally returns false
public static bool LogException(this Exception e)
{
    Console.Error.WriteLine(@"Exceptions happen: {e}");
    return false;
} 
//當需要記錄異常時,可以如下使用:
//Whenever you want to log an exception, you can add a catch clause, 
//and use this method as the exception filter:
public void MethodThatFailsSometimes()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {//因爲e.LogException始終返回false(只有return true纔會進來),所以這裏永遠執行不到。
     //這就使得我們可以通過這種方法,在任何異常處理之前,記錄異常。
        // This is never reached!
        //The exceptions are never caught, because the LogException method always returns false. 
    }
} 
//先記錄異常,然後處理異常
public void MethodThatFailsButHasRecoveryPath()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {//e.LogException記錄異常,並返回false,所以這裏永遠執行不到
        // This is never reached!
    }
    catch (RecoverableException ex)
    {//記錄完之後,然後開始處理異常
        Console.WriteLine(ex.ToString());
        // This can still catch the more specific
        // exception because the exception filter
        // above always returns false.
        // Perform recovery here 
    }
}

用法之三是Another recommended pattern helps prevent catch clauses from processing exceptions when a debugger is attached. This technique enables you to run an application with the debugger, and stop execution when an exception is thrown.

public void MethodThatFailsWhenDebuggerIsNotAttached()
{
    try {
        PerformFailingOperation();
    } catch (Exception e) when (e.LogException())
    {
        // This is never reached!
    }
    catch (RecoverableException ex) when (!System.Diagnostics.Debugger.IsAttached)
    {
        Console.WriteLine(ex.ToString());
        // Only catch exceptions when a debugger is not attached.
        // Otherwise, this should stop in the debugger. 
    }
}
After adding this in code, you set your debugger to break on all unhandled exceptions. Run the program under the debugger, and the debugger breaks whenever PerformFailingOperation() throws a RecoverableException. The debugger breaks your program, because the catch clause won't be executed due to the false-returning exception filter.

8.nameof表達式
用於獲取symbol的名字,這是用於獲得變量,屬性,成員字段名稱的好方法。
使用這種方法而不是直接使用常量的好處是:重構時不需要一個一個去修改。

//One of the most common uses for nameof is to provide the name 
//of a symbol that caused an exception:
if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));

//Another use is with XAML based applications that 
//implement the INotifyPropertyChanged interface:
private string lastName;
public string LastName
{
    get { return lastName; }
    set
    {
        if (value != lastName)
        {
            lastName = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(LastName)));
        }
    }
}

//nameof獲得的是不完全限定名,即使nameof(這裏用完全限定名),結果也是非完全限定名。
private string firstName;
public string FirstName
{
    get { return firstName; }
    set
    {
        if (value != firstName)
        {
            firstName = value;
            PropertyChanged?.Invoke(this, 
                new PropertyChangedEventArgs(nameof(UXComponents.ViewModel.FirstName)));
        }
    }
}
//這裏nameof(UXComponents.ViewModel.FirstName)的結果是FirstName,
//而不是UXComponents.ViewModel.FirstName

9.Await in Catch and Finally blocks(catch/finally中可以使用await)
之前的版本await可以使用的地方是有限制的,比如在catch/finally語句塊裏面就不能使用;C#6則支持。
在catch/finally block中添加await表達式,會使處理過程變得複雜。在任何異步方法中都可以在catch/finally中使用await,下面是一個例子。

//With C# 6, you can also await in catch expressions. 
//This is most often used with logging scenarios:
public static async Task<string> MakeRequestAndLogFailures()
{ 
    await logMethodEntrance();
    var client = new System.Net.Http.HttpClient();
    var streamTask = client.GetStringAsync("https://localHost:10000");
    try {
        var responseText = await streamTask;
        return responseText;
    } 
    //下面在catch/finally中使用await確保行爲和同步執行的代碼是一樣的
    //ensure that the behavior is consistent with the behavior for synchronous code. 
    //catch/finally裏面的代碼拋出異常,會在下面的查找合適的catch//如果拋出的異常就是當前這樣的異常,那這個異常將丟失。
    //同樣的,如果await表達式拋出異常,也會查找合適的catch,並且當前的異常(如果有的話)也會丟失。
    //When code executed in a catch or finally clause throws, 
    //execution looks for a suitable catch clause in the next surrounding block. 
    //If there was a current exception, that exception is lost. 
    //The same happens with awaited expressions in catch and finally clauses: 
    //a suitable catch is searched for, and the current exception, if any, is lost.
    catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301"))
    {
        await logError("Recovered from redirect", e);
        return "Site Moved";
    }
    finally
    {
        await logMethodExit();
        client.Dispose();
    }
    //以上例子也說明了:我們在寫catch/finally時要特別小心,要避免引起新的異常。
}

10.Index Initializers(索引初始化器)
以前只能順序初始化

//現在
private Dictionary<int, string> webErrors = new Dictionary<int, string>
{
    [404] = "Page not Found",
    [302] = "Page moved, but left a forwarding address.",
    [500] = "The web server can't come out to play today."
};

11.Extension methods for collection initializers(集合初始化的擴展方法)
之前的版本,是無法像下面的代碼這樣使用Enrollment object來初始化集合的,儘管Enrollment類提供了Enroll方法來添加student。
You could not use collection initializers with an Enrollment object(The Enroll method adds a student. But it doesn’t follow the Add pattern).

public class Enrollment : IEnumerable<Student>
{
    private List<Student> allStudents = new List<Student>();
    public void Enroll(Student s)
    {
        allStudents.Add(s);
    }
    public IEnumerator<Student> GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
}
//之前是無法這樣初始化自定義的集合的
var classList = new Enrollment()
{
    new Student("Lessie", "Crosby"),
    new Student("Vicki", "Petty"),
    new Student("Ofelia", "Hobbs")
};

新版裏面我們可以通過擴展方法來實現。具體做法是:創建一個名爲Add的擴展方法,並把一個添加元素的方法映射到這個Add方法。本例爲:創建一個Add擴展方法並映射到Enroll方法(因爲Enrollment類是通過Enroll方法添加元素的)。

//創建擴展方法
public static class StudentExtensions
{
    public static void Add(this Enrollment e, Student s) => e.Enroll(s);
}
public class Enrollment : IEnumerable<Student>
{
    private List<Student> allStudents = new List<Student>();
    public void Enroll(Student s)
    {
        allStudents.Add(s);
    }
    public IEnumerator<Student> GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable<Student>)allStudents).GetEnumerator();
    }
}
//然後我們就可以這樣初始化集合對象了
var classList = new Enrollment()
{
    new Student("Lessie", "Crosby"),
    new Student("Vicki", "Petty"),
    new Student("Ofelia", "Hobbs")
};

12.Improved overload resolution(提高重載解析度)

static Task DoThings() 
{
     return Task.FromResult(0); 
}
//以前的版本只能這樣用:
System.Threading.Tasks.Task.Run(() => DoThings());
//因爲以前的編譯器無法正確的區分Task.Run(Action)Task.Run(Func<Task>()),
//所以需要用lambda表達式作爲參數。

//現在則可以區分,所以可以這樣用:
System.Threading.Tasks.Task.Run(DoThings);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章