【推荐阅读】微服务还能火多久?>>> 代码演示C#各2.0到8.0版本[FK,2.0-4.8.0]-LMLPHP

代码演示C#各版本新功能

 

C#各版本新功能其实都能在官网搜到,但很少有人整理在一起,并通过非常简短的代码将每个新特性演示出来。

C# 2.0版 - 2005

泛型

Java中的泛型不支持值类型,且会运行时类型擦除,这一点.NET更优秀。

// Declare the generic class.
public class GenericList<T> { public void Add(T input) { } } class TestGenericList { private class ExampleClass { } static void Main() { // Declare a list of type int. GenericList<int> list1 = new GenericList<int>(); list1.Add(1); // Declare a list of type string. GenericList<string> list2 = new GenericList<string>(); list2.Add(""); // Declare a list of type ExampleClass. GenericList<ExampleClass> list3 = new GenericList<ExampleClass>(); list3.Add(new ExampleClass()); } } 

分部类型

public partial class Employee
{
    public void DoWork() { } } public partial class Employee { public void GoToLunch() { } } 

匿名方法

Func<int, int, int> sum = delegate (int a, int b) { return a + b; }; Console.WriteLine(sum(3, 4)); // output: 7 

可以为null的值类型

double? pi = 3.14;
char? letter = 'a';

int m2 = 10; int? m = m2; bool? flag = null; // An array of a nullable type: int?[] arr = new int?[10]; 

迭代器

static void Main() { foreach (int number in SomeNumbers()) { Console.Write(number.ToString() + " "); } // Output: 3 5 8 Console.ReadKey(); } public static System.Collections.IEnumerable SomeNumbers() { yield return 3; yield return 5; yield return 8; } 

协变和逆变

// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str; // Covariance. IEnumerable<string> strings = new List<string>(); // An object that is instantiated with a more derived type argument // is assigned to an object instantiated with a less derived type argument. // Assignment compatibility is preserved. IEnumerable<object> objects = strings; // Contravariance. // Assume that the following method is in the class: // static void SetObject(object o) { } Action<object> actObject = SetObject; // An object that is instantiated with a less derived type argument // is assigned to an object instantiated with a more derived type argument. // Assignment compatibility is reversed. Action<string> actString = actObject; 

C# 3.0版 - 2007

自动实现的属性

// This class is mutable. Its data can be modified from
// outside the class.
class Customer
{
    // Auto-implemented properties for trivial get and set public double TotalPurchases { get; set; } public string Name { get; set; } public int CustomerID { get; set; } // Constructor public Customer(double purchases, string name, int ID) { TotalPurchases = purchases; Name = name; CustomerID = ID; } // Methods public string GetContactInfo() { return "ContactInfo"; } public string GetTransactionHistory() { return "History"; } // .. Additional methods, events, etc. } class Program { static void Main() { // Intialize a new object. Customer cust1 = new Customer(4987.63, "Northwind", 90108); // Modify a property. cust1.TotalPurchases += 499.99; } } 

匿名类型

var v = new { Amount = 108, Message = "Hello" };

// Rest the mouse pointer over v.Amount and v.Message in the following // statement to verify that their inferred types are int and n . Console.WriteLine(v.Amount + v.Message); 

查询表达式(LINQ)

LINQ允许你可以像写SQL一样写C#代码,像这样:

from p in persons
where p.Age > 18 && p.IsBeatiful
select new { p.WeChatId, p.PhoneNumber } 

LINQ的意义在于让C#做出了重大调整,本章中说到的lambda表达式、扩展方法、表达式树、匿名类型、自动属性等,都是LINQ的必要组成部分。

由于用扩展方法的形式也能得到一致的结果,而且还能让代码风格更加一致,所以我平时用LINQ语法较少:

// 与上文代码相同,但改成了扩展方法风格:
persons
    .Where(x => x.Age > 18 && x.IsBeatiful)
    .Select(x => new
    {
        x.WeChatId,
        x.PhoneNumber,
    });

Lambda表达式

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25 

表达式树

这个是LINQ的基础之一,它的作用是将代码像数据一样,保存在内存中;然后稍后对这些“代码数据”进行重新解释/执行。

Entity Framework就是一个经典场景,它先将表达式树保存起来,然后执行时,将其翻译为SQL发给数据库执行。

扩展方法

static void Main() { Console.WriteLine ("Perth".IsCapitalized()); // Equivalent to: Console.WriteLine (StringHelper.IsCapitalized ("Perth")); // Interfaces can be extended, too: Console.WriteLine ("Seattle".First()); // S } public static class StringHelper { public static bool IsCapitalized (this string s) { if (string.IsNullOrEmpty(s)) return false; return char.IsUpper (s[0]); } public static T First<T> (this IEnumerable<T> sequence) { foreach (T element in sequence) return element; throw new InvalidOperationException ("No elements!"); } } 

var

var i = 10; // Implicitly typed.
int i = 10; // Explicitly typed. 

分部方法

namespace PM
{
    partial class A { partial void OnSomethingHappened(string s); } // This part can be in a separate file. partial class A { // Comment out this method and the program // will still compile. partial void OnSomethingHappened(String s) { Console.WriteLine("Something happened: {0}", s); } } } 

对象和集合初始值设定项

public class Cat
{
    // Auto-implemented properties.
    public int Age { get; set; } public string Name { get; set; } public Cat() { } public Cat(string name) { this.Name = name; } } 

C# 4.0版 - 2010

dynamic

这个是特性使得CLR不得不进行一次修改。有了这个,C#也能像jsphppython等弱类型语言一样写代码了。

dynamic a = 3;
a = 3.14;
a = "Hello World";
a = new[] { 1, 2, 3, 4, 5 }; a = new Func<int>(() => 3); a = new StringBuilder(); Console.WriteLine(a.GetType().Name); // StringBuilder 

注意dynamic可以表示任何东西,包含数组、委托等等。滥用dynamic容易让程序变得很难维护。

命名参数/可选参数

PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31);
public void ExampleMethod(int required, string optionalstr = "default string", int optionalint = 10) 

泛型中的协变和逆变

IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());

类型等效、内置互操作类型

这个主要是为了和COM进行交互。之前需要引用一些COM类型相关的程序集,现在可以直接引用COM
具体可以参见:https://docs.microsoft.com/zh-cn/dotnet/framework/interop/type-equivalence-and-embedded-interop-types

C# 5.0版 - 2012

async/await

private DamageResult CalculateDamageDone()
{
    // Code omitted: // // Does an expensive calculation and returns // the result of that calculation. } calculateButton.Clicked += async (o, e) => { // This line will yield control to the UI while CalculateDamageDone() // performs its work. The UI thread is free to perform other work. var damageResult = await Task.Run(() => CalculateDamageDone()); DisplayDamage(damageResult); }; 

async/await的本质是状态机,像IEnumerable<T>一样。以前游戏引擎Unity只支持C# 3.0,因此当时它用状态机发Http请求是用的IEnumerable<T>

async/await有两个好处,一是可以避免UI线程卡顿,二是提高系统吞吐率,最终提高性能。

调用方信息

public void DoProcessing() { TraceMessage("Something happened."); } public void TraceMessage(string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { System.Diagnostics.Trace.WriteLine("message: " + message); System.Diagnostics.Trace.WriteLine("member name: " + memberName); System.Diagnostics.Trace.WriteLine("source file path: " + sourceFilePath); System.Diagnostics.Trace.WriteLine("source line number: " + sourceLineNumber); } // Sample Output: // message: Something happened. // member name: DoProcessing // source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs // source line number: 31 

注意这个是编译期生成的,因此比StackTrace更能保证性能。

C# 6.0版 - 2015

静态导入

终于可以不用写静态类名了。

using static System.Math;
using static System.Console;

WriteLine(Sin(3.14)); // 0.00159265291648683 

异常筛选器

try-catch时,可以按指定的条件进行catch,其它条件不catch

public static async Task<string> MakeRequest() { WebRequestHandler webRequestHandler = new WebRequestHandler(); webRequestHandler.AllowAutoRedirect = false; using (HttpClient client = new HttpClient(webRequestHandler)) { var stringTask = client.GetStringAsync("https://docs.microsoft.com/en-us/dotnet/about/"); try { var responseText = await stringTask; return responseText; } catch (System.Net.Http.HttpRequestException e) when (e.Message.Contains("301")) { return "Site Moved"; } } } 

自动初始化表达式

public ICollection<double> Grades { get; } = new List<double>(); 

Expression-bodied 函数成员

public override string ToString() => $"{LastName}, {FirstName}"; 

Null传播器

var first = person?.FirstName;

字符串内插

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

nameof表达式

有时字符串值和某个变量名称一致,尤其是在做参数验证时。这里nameof就能在编译期,自动从变量名生成一个字符串。

if (IsNullOrWhiteSpace(lastName))
    throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName)); 

索引初始值设定项

C# 7.0版本 - 2017

out变量

if (int.TryParse(input, out int result))
    Console.WriteLine(result);
else Console.WriteLine("Could not parse input"); 

元组和解构

(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}"); 

如上代码所示,解构可以将元组拆分为多个变量。

模式匹配

现在可以在匹配一个类型时,自动转换为这个类型的变量,如果转换失败,这个变量就赋值为默认值(null0)。

极简版:

if (input is int count)
    sum += count;

switch/case版:

public static int SumPositiveNumbers(IEnumerable<object> sequence) { int sum = 0; foreach (var i in sequence) { switch (i) { case 0: break; case IEnumerable<int> childSequence: { foreach(var item in childSequence) sum += (item > 0) ? item : 0; break; } case int n when n > 0: sum += n; break; case null: throw new NullReferenceException("Null found in sequence"); default: throw new InvalidOperationException("Unrecognized type"); } } return sum; } 

本地函数

这个主要是方便,javascript就能这样写。

lambda的好处在于,这个可以定义在后面,而lambda必须定义在前面。

public static IEnumerable<char> AlphabetSubset3(char start, char end) { if (start < 'a' || start > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter"); if (end < 'a' || end > 'z') throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter"); if (end <= start) throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}"); return alphabetSubsetImplementation(); IEnumerable<char> alphabetSubsetImplementation() { for (var c = start; c < end; c++) yield return c; } } 

更多的expression-bodied成员

该功能可以让一些函数写成表达式的形式,非常的方便。

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label; // Expression-bodied finalizer ~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!"); private string label; // Expression-bodied get / set accessors. public string Label { get => label; set => this.label = value ?? "Default label"; } 

Ref 局部变量和返回结果

这个功能主要是为了提高值类型的性能,让它真正发挥其作用。C++就有类似的功能。

public static ref int Find(int[,] matrix, Func<int, bool> predicate) { for (int i = 0; i < matrix.GetLength(0); i++) for (int j = 0; j < matrix.GetLength(1); j++) if (predicate(matrix[i, j])) return ref matrix[i, j]; throw new InvalidOperationException("Not found"); } 
ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24; Console.WriteLine(matrix[4, 2]); 

弃元

using System;
using System.Collections.Generic;

public class Example { public static void Main() { var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010); Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}"); } private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2) { int population1 = 0, population2 = 0; double area = 0; if (name == "New York City") { area = 468.48; if (year1 == 1960) { population1 = 7781984; } if (year2 == 2010) { population2 = 8175133; } return (name, area, year1, population1, year2, population2); } return ("", 0, 0, 0, 0, 0); } } // The example displays the following output: // Population change, 1960 to 2010: 393,149 

二进制文本和数字分隔符

这个用于使数字和二进制更可读。

// 二进制文本:
public const int Sixteen =   0b0001_0000; public const int ThirtyTwo = 0b0010_0000; public const int SixtyFour = 0b0100_0000; public const int OneHundredTwentyEight = 0b1000_0000; // 数字分隔符: public const long BillionsAndBillions = 100_000_000_000; public const double AvogadroConstant = 6.022_140_857_747_474e23; public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M; 

throw表达式

void Main()
{
	// You can now throw expressions in expressions clauses. // This is useful in conditional expressions: string result = new Random().Next(2) == 0 ? "Good" : throw new Exception ("Bad"); result.Dump(); Foo().Dump(); } public string Foo() => throw new NotImplementedException(); 

C# 8.0 版 - 2019

Readonly 成员

public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin"; 

默认接口方法

void Main()
{
	ILogger foo = new Logger(); foo.Log (new Exception ("test")); } class Logger : ILogger { public void Log (string message) => Console.WriteLine (message); } interface ILogger { void Log (string message); // Adding a new member to an interface need not break implementors: public void Log (Exception ex) => Log (ExceptionHeader + ex.Message); // The static modifier (and other modifiers) are now allowed: static string ExceptionHeader = "Exception: "; } 

模式匹配增强

这个是为简化代码、函数式编程而生的,我个人非常喜欢。

属性模式

public static decimal ComputeSalesTax(Address location, decimal salePrice) => location switch { { State: "WA" } => salePrice * 0.06M, { State: "MN" } => salePrice * 0.75M, { State: "MI" } => salePrice * 0.05M, // other cases removed for brevity... _ => 0M }; 

Tuple模式

public static string RockPaperScissors(string first, string second) => (first, second) switch { ("rock", "paper") => "rock is covered by paper. Paper wins.", ("rock", "scissors") => "rock breaks scissors. Rock wins.", ("paper", "rock") => "paper covers rock. Paper wins.", ("paper", "scissors") => "paper is cut by scissors. Scissors wins.", ("scissors", "rock") => "scissors is broken by rock. Rock wins.", ("scissors", "paper") => "scissors cuts paper. Scissors wins.", (_, _) => "tie" }; 

位置模式

static Quadrant GetQuadrant(Point point) => point switch { (0, 0) => Quadrant.Origin, var (x, y) when x > 0 && y > 0 => Quadrant.One, var (x, y) when x < 0 && y > 0 => Quadrant.Two, var (x, y) when x < 0 && y < 0 => Quadrant.Three, var (x, y) when x > 0 && y < 0 => Quadrant.Four, var (_, _) => Quadrant.OnBorder, _ => Quadrant.Unknown }; 

switch表达式

这个功能能使代码从大量的if/elseswitch/case变成“一行代码”,符合函数式编程的思想,非常好用!

public static RGBColor FromRainbow(Rainbow colorBand) => colorBand switch { Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00), Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00), Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00), Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00), Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF), Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82), Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3), _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)), }; 

using声明

static int WriteLinesToFile(IEnumerable<string> lines) { using var file = new System.IO.StreamWriter("WriteLines2.txt"); // Notice how we declare skippedLines after the using statement. int skippedLines = 0; foreach (string line in lines) { if (!line.Contains("Second")) { file.WriteLine(line); } else { skippedLines++; } } // Notice how skippedLines is in scope here. return skippedLines; // file is disposed here } 

静态本地函数

相比非静态本地函数,静态本地函数没有闭包,因此生成的代码更少,性能也更容易控制。

int M()
{
    int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; } 

异步流

这个功能和IEnumerable<T>Task<T>对应,一个经典的表格如下:

其中,这个问号?终于有了答案,它就叫异步流——IAsyncEnumerable<T>

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } } 

不像IEnumerable<T>IAsyncEnumerable<T>系统还没有内置扩展方法,因此可能没有IEnumerable<T>方便,但是可以通过安装NuGetf来实现和IEnumerable<T>一样(或者更爽)的效果。

索引和范围

var words = new string[]
{
                // index from start    index from end
    "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0 var quickBrownFox = words[1..4]; var lazyDog = words[^2..^0]; var allWords = words[..]; // contains "The" through "dog". var firstPhrase = words[..4]; // contains "The" through "fox" var lastPhrase = words[6..]; // contains "the", "lazy" and "dog" 

Null合并赋值

List<int> numbers = null;
int? i = null;

numbers ??= new List<int>(); numbers.Add(i ??= 17); numbers.Add(i ??= 20); Console.WriteLine(string.Join(" ", numbers)); // output: 17 17 Console.WriteLine(i); // output: 17 

非托管构造类型

Span<Coords<int>> coordinates = stackalloc[]
{
    new Coords<int> { X = 0, Y = 0 }, new Coords<int> { X = 0, Y = 3 }, new Coords<int> { X = 4, Y = 0 } }; 

嵌套表达式中的 stackalloc

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 }; var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6 ,8 }); Console.WriteLine(ind); // output: 1 

附录/总结

这么多功能,你印象最深刻的是哪个呢?

04-15 16:37