C#新特性

本文介绍了C#7.1版本中的新特性,包括out变量改进、ValueTuple的引入及其应用场景、只读自动属性的增强等。此外,还详细讲解了C#6的一些重要特性,如表达式函数成员、using static导入、null条件操作符等。
摘要由CSDN通过智能技术生成

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值