目录
[What's new in C# 7](# 7)
[C# 7 in Visual Studio 2017](# 7 in Visual Studio 2017)
[Out Variables](#Out Variables)
[Pattern Matching](#Pattern Matching)
[Tuples (System.ValueTuple)](#Tuples (System.ValueTuple))
[Local Functions](#Local Functions)
[Ref Returns and Locals](#Ref Returns and Locals)
[Expression Bodied Members](#Expression Bodied Members)
[Throw Expressions](#Throw Expressions)
[Generated Async Return Types](#Generated Async Return Types)
[Literal Improvements](#Literal Improvements)
[C# 7.1 in Visual Studio 2017.3](# 7.1 in Visual Studio 2017.3)
Compilation Issues (how to switch to C#7.1))
[Async Main](#Async Main)
[Default Expressions](#Default Expressions)
[Ref Assemblies](#Ref Assemblies)
[Infer Tuple Names](#Infer Tuple Names)
[Pattern-Matching with Generics](#Pattern-Matching with Generics)
[C# 7.2 in Visual Studio 2017 15.5](# 7.2 in Visual Studio 2017 15.5)
[Leading Digit Separators](#Leading Digit Separators)
['Private Protected' Access Modifier](#'Private Protected' Access Modifier)
[Non-Trailing Named Arguments](#Non-Trailing Named Arguments)
[Reference Semantics on Value Types值类型的引用语义](#Reference Semantics on Value Types值类型的引用语义)
[C# 7.3 in Visual Studio 2017 15.5](# 7.3 in Visual Studio 2017 15.5)
[Performance Improvement](#Performance Improvement)
[Features Enhancements](#Features Enhancements)
[Extensioned expression variables in initializers](#Extensioned expression variables in initializers)
[New Compiler Features](#New Compiler Features)
[What's new in C# 8](# 8)
[Nullable Reference Types](#Nullable Reference Types)
[Method 1: 通过引用ReSharper.Annotations](#Method 1: 通过引用ReSharper.Annotations)
[Method 2: 通过使用string?](#Method 2: 通过使用string?)
[Override Null Checks](#Override Null Checks)
[Diable nullable check](#Diable nullable check)
[Enable nullable check](#Enable nullable check)
[Index and Range](#Index and Range)
[Default Interface Members](#Default Interface Members)
[Extension to create Default function in Interface](#Extension to create Default function in Interface)
[Default function in iterface](#Default function in iterface)
[Pattern Matching](#Pattern Matching)
[What's new in C# 9 (. NET 5)](# 9 (. NET 5))
[Record Types](#Record Types)
[Top-level Calls](#Top-level Calls)
[Initial Setters](#Initial Setters)
[Pttern Matching Improvement](#Pttern Matching Improvement)
[Target-Typed New](#Target-Typed New)
[Source Generators](#Source Generators)
[Partial Method Syntax and Modules Initializers(部分方法语法和模块初始化器)](#Partial Method Syntax and Modules Initializers(部分方法语法和模块初始化器))
[What's new in C# 10 (. NET 6)](# 10 (. NET 6))
[Record Structs](#Record Structs)
[Global Using Directives](#Global Using Directives)
[File-Scoped Namespace Declarations](#File-Scoped Namespace Declarations)
[Extended Property Patterns](#Extended Property Patterns)
[Generic Attributes通用属性](#Generic Attributes通用属性)
[Lambda Improvements,提高很多](#Lambda Improvements,提高很多)
[Enhanced #line directives](#line directives)
What's new in C# 7
C# 7 in Visual Studio 2017
Out Variables
cs
using System;
using static System.Console;
namespace CSharp7Demos
{
class OutVariables
{
static void MainOV(string[] args)
{
DateTime dt; // struct
if (DateTime.TryParse("01/01/2017", out dt))
{
WriteLine($"Old-fashioned parse: {dt}");
}
// variable declaration is an expression, not a statement
if (DateTime.TryParse("02/02/2016", out /*DateTime*/ var dt2))
{
WriteLine($"New parse: {dt2}");
}
// the scope of dt2 extends outside the if block
WriteLine($"I can use dt2 here: {dt2}");
// what if the parse fails?
int.TryParse("abc", out var i);
WriteLine($"i = {i}"); // default value
}
}
}
Pattern Matching
cs
using static System.Console;
namespace CSharp7Demos
{
public class Shape
{
}
public class Rectangle : Shape
{
public int Width, Height;
}
public class Circle : Shape
{
public int Diameter;
}
public class PatternMatching
{
public void DisplayShape(Shape shape)
{
if (shape is Rectangle)
{
var rc = (Rectangle) shape;
} else if (shape is Circle)
{
// ...
}
var rect = shape as Rectangle;
if (rect != null) // nonnull
{
//...
}
if (shape is Rectangle r)
{
// use r
}
// can also do the invserse
if (!(shape is Circle cc))
{
// not a circle!
}
switch (shape)
{
case Circle c:
// use c
break;
case Rectangle sq when (sq.Width == sq.Height):
// square!
break;
case Rectangle rr:
// use rr
break;
}
var z = (23, 32);
//switch (z)
//{
// case (0, 0):
// WriteLine("origin");
//}
}
static void Main(string[] args)
{
}
}
}
Tuples (System.ValueTuple)
cs
using System;
using System.Linq;
using Microsoft.SqlServer.Server;
using static System.Console;
namespace CSharp7Demos
{
public class Point
{
public int X, Y;
public void Deconstruct(out string s)
{
s = $"{X}-{Y}";
}
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
public class Tuples
{
static Tuple<double, double> SumAndProduct(double a, double b)
{
return Tuple.Create(a + b, a * b);
}
// requires ValueTuple nuget package
// originally with no names
static (double sum, double product) NewSumAndProduct(double a, double b)
{
return (a+b,a*b);
}
static void MainT(string[] args)
{
// New
var sp = SumAndProduct(2, 5);
// sp.Item1 ugly
WriteLine($"sum = {sp.Item1}, product = {sp.Item2}");
var sp2 = NewSumAndProduct(2, 5);
WriteLine($"new sum = {sp2.sum}, product = {sp2.product}");
WriteLine($"Item1 = {sp2.Item1}");
WriteLine(sp2.GetType());
// converting to valuetuple loses all info
var vt = sp2;
// back to Item1, Item2, etc...
var item1 = vt.Item1; // :(
// can use var below
//(double sum, var product) = NewSumAndProduct(3, 5);
var (sum, product) = NewSumAndProduct(3, 5);
// note! var works but double doesn't
// double (s, p) = NewSumAndProduct(3, 4);
(double s, double p) = NewSumAndProduct(3, 4);//This can work
WriteLine($"sum = {sum}, product = {product}");
WriteLine(sum.GetType());
// also assignment
double s, p;
(s, p) = NewSumAndProduct(1, 10);
// tuple declarations with names
//var me = new {name = "Evan", age = 37}; // AnonymousType
var me = (name: "Evan", age: 37);
WriteLine(me);
WriteLine(me.GetType());//Print is System.ValueTuple
// names are not part of the type:
WriteLine("Fields: " + string.Join(",", me.GetType().GetFields().Select(pr => pr.Name)));
WriteLine("Properties: " + string.Join(",", me.GetType().GetProperties().Select(pr => pr.Name)));
WriteLine($"My name is {me.name} and I am {me.age} years old");
// proceed to show return: TupleElementNames in dotPeek (internally, Item1 etc. are used everywhere)
// unfortunately, tuple names only propagate out of a function if they're in the signature
var snp = new Func<double, double, (double sum, double product)>((a, b) => (sum: a + b, product: a * b));
var result = snp(1, 2);
// there's no result.sum unless you add it to signature
WriteLine($"sum = {result.sum}");
}
}
}
Deconstruct解构
cs
// deconstruction
Point pt = new Point {X = 2, Y = 3};
var (x,y) = pt; // interesting error here
Console.WriteLine($"Got a point x = {x}, y = {y}");
// can also discard values
(int z, _) = pt;
Local Functions
CalculateDiscriminant可以放在方法SolveQuadratic体内,或者方法体外,放在方法体内是,所在的位置可以在调用前或者调用后面。
建议放在前面,方便代码维护。
cs
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string MiddleName { get; set; }
//normal ctor
//public Employee(string firstName, string lastName, string middleName)
//{
// FirstName = firstName;
// LastName = lastName;
// MiddleName = middleName;
//}
//lambda ctor
public Employee(string firstName, string lastName, string middleName) => (FirstName, LastName, MiddleName) = (firstName, lastName, middleName);
}
cs
using System;
namespace CSharpDemos
{
public class EquationSolver
{
//private Func<double, double, double, double> CalculateDiscriminant = (aa, bb, cc) => bb * bb - 4 * aa * cc;
//Quadratic 二次方程
public static Tuple<double, double> SolveQuadratic(double a, double b, double c)
{
//var CalculateDiscriminant = new Func<double, double, double, double>((aa, bb, cc) => bb * bb - 4 * aa * cc);
//double CalculateDiscriminant(double aa, double bb, double cc)
//{
// return bb * bb - 4 * aa * cc;
//}
//double CalculateDiscriminant(double aa, double bb, double cc) => bb * bb - 4 * aa * cc;
//double CalculateDiscriminant() => b * b - 4 * a * c;
//var disc = CalculateDiscriminant(a, b, c);
var disc = CalculateDiscriminant();
var rootDisc = Math.Sqrt(disc);
return Tuple.Create(
(-b + rootDisc) / (2 * a),
(-b - rootDisc) / (2 * a)
);
// can place here
double CalculateDiscriminant() => b * b - 4 * a * c;
}
//private static double CalculateDiscriminant(double a, double b, double c)
//{
// return b * b - 4 * a * c;
//}
}
public class LocalFunctions
{
static void MainT(string[] args)
{
var result = EquationSolver.SolveQuadratic(1, 10, 16);
Console.WriteLine(result);
}
}
}
Ref Returns and Locals
cs
using System;
using System.Collections.Generic;
using static System.Console;
namespace CSharpDemos
{
public class RefReturnsAndLocals
{
static ref int Find(int[] numbers, int value)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == value)
return ref numbers[i];
}
// cannot do by value return
//return -1;
// cannot return a local
//int fail = -1;
//return ref fail;
throw new ArgumentException("meh");
}
static ref int Min(ref int x, ref int y)
{
//return x < y ? (ref x) : (ref) y;
//return ref (x < y ? x : y);
if (x < y) return ref x;
return ref y;
}
static void MainRRL(string[] args)
{
// reference to a local element
int[] numbers = { 1, 2, 3 };
ref int refToSecond = ref numbers[1];
var valueOfSecond = refToSecond;
// cannot rebind
// refToSecond = ref numbers[0];
refToSecond = 123;
WriteLine(string.Join(",", numbers)); // 1, 123, 3
// reference persists even after the array is resized
Array.Resize(ref numbers, 1);
WriteLine($"second = {refToSecond}, array size is {numbers.Length}");
refToSecond = 321;
WriteLine($"second = {refToSecond}, array size is {numbers.Length}");
//numbers.SetValue(321, 1); // will throw
// won't work with lists
var numberList = new List<int> {1, 2, 3};
//ref int second = ref numberList[1]; // property or indexer cannot be out
int[] moreNumbers = {10, 20, 30};
//ref int refToThirty = ref Find(moreNumbers, 30);
//refToThirty = 1000;
// disgusting use of language
Find(moreNumbers, 30) = 1000;
WriteLine(string.Join(",",moreNumbers));
// too many references:
int a = 1, b = 2;
ref var minRef = ref Min(ref a, ref b);
// non-ref call just gets the value
int minValue = Min(ref a, ref b);
WriteLine($"min is {minValue}");
}
}
}
Expression Bodied Members
cs
using System.Collections.Generic;
namespace CSharpDemos
{
// community contributed feature
public class Person
{
private int id;
private static readonly Dictionary<int, string> names = new Dictionary<int, string>();
public Person(int id, string name) => names.Add(id, name);
~Person() => names.Remove(id);
public string Name
{
get => names[id];
set => names[id] = value;
}
}
}
Throw Expressions
cs
using System;
using static System.Console;
namespace CSharpDemos
{
public class ThrowExpressions
{
public string Name { get; set; }
public ThrowExpressions(string name)
{
Name = name ?? throw new ArgumentNullException(paramName: nameof(name));
}
int GetValue(int n)
{
return n > 0 ? n + 1 : throw new Exception();
}
static void MainTE(string[] args)
{
int v = -1;
try
{
var te = new ThrowExpressions("");
v = te.GetValue(-1); // does not get defaulted!
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
WriteLine(v);
}
}
}
}
Generated Async Return Types
cs
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
namespace CSharpDemos
{
public class GeneralizedAsyncReturnTypes
{
public static async Task<long> GetDirSize(string dir)
{
if (!Directory.EnumerateFileSystemEntries(dir).Any())
return 0;
// Task<long> is return type so it still needs to be instantiated
return await Task.Run(() => Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories)
.Sum(f => new FileInfo(f).Length));
}
// C# 7 lets us define custom return types on async methods
// main requirement is to implement GetAwaiter() method
// ValueTask is a good example
// need nuget package
public static async ValueTask<long> NewGetDirSize(string dir)
{
if (!Directory.EnumerateFileSystemEntries(dir).Any())
return 0;
// Task<long> is return type so it still needs to be instantiated
return await Task.Run(() => Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories)
.Sum(f => new FileInfo(f).Length));
}
static void MainGART(string[] args)
{
// async methods used to require void, Task or Task<T>
// C# 7 allows other types such as ValueType<T> - prevent
// allocation of a task when the result is already available
// at the time of awaiting
Console.WriteLine(NewGetDirSize(@"c:\temp").Result);
}
}
}
Literal Improvements
cs
namespace CSharpDemos
{
public class LiteralImprovements
{
static void MainLI(string[] args)
{
int a = 123_321;
int b = 123_321______123;
// cannot do trailing
//int c = 1_2_3___; // R# remove
// also works for hex
long h = 0xAB_BC_D123EF;
// also binay
var bin = 0b1110_0010_0011;
}
}
}
C# 7.1 in Visual Studio 2017.3
Compilation Issues (how to switch to C#7.1)
以下内码在vs2017.3中会报编译错误,你可以从Solution Explore右击Project-->Properties-->Build --> Advance去更改C#版本到7.1去解决编译错误。
cs
static async Task Main(string[] args)
{
Console.WriteLine("ABC");
}
Async Main
cs
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace CSharpDemos
{
internal class Program
{
// used to be the case that your demo
// would have to reside in a separate
// body
private static string url = "http://google.com/robots.txt";
//private static async Task MainAsync(string s)
//{
// // blah
// Console.WriteLine(await new HttpClient().GetStringAsync(s));
//}
//public static void Main(string[] args)
//{
// // fine
// MainAsync(url).GetAwaiter().GetResult();
//}
// there is no async void, it's
// Task Main
// Task<int> Main if you need to return
static async Task Main(string[] args)
{
Console.WriteLine(await new HttpClient().GetStringAsync(url));
}
}
}
Default Expressions
cs
using System;
using System.Collections.Generic;
using static System.Console;
namespace CSharpDemos
{
public class DefaultLiteral
{
// allowed in argument names
// only upside: switching from ref to value type
// VS Action 'Simplify Default Expression'
public DateTime GetTimestamps(List<int> items = default(List<int>))
{
// ...
return default;
}
/// <summary>
/// Default literal, one of the slightly meaningless features.
/// </summary>
static void Main()
{
// Simplify default expression here
int a = default(int);
WriteLine(a);
int av = default;//same as above, 0 is int default value
WriteLine(av);
int b = default;
WriteLine(b);
// constants are ok if the inferred type is suitable
const int c = default;
WriteLine(c);
// will not work here
// const int? d = default; // oops
// cannot leave defaults on their own
var e = new[] {default, 33, default};
WriteLine(string.Join(",", e));
// rather silly way of doing things; null is shorter
string s = default;
WriteLine(s == null);
// comparison with default is OK if type can be inferred
if (s == default)
{
WriteLine("Yes, s is default/null");
}
// ternary operations
var x = a > 0 ? default : 1.5;
WriteLine(x.GetType().Name);
}
}
}
Ref Assemblies
利用Refelection反编译后,代码实现部分只有null返回,其他部分被隐藏了。
Infer Tuple Names
cs
using System;
using System.Linq;
namespace CSharpDemos
{
using static System.Console;
public class InferTupleNames
{
// Tuple projection initializers
public static void Main(string[] args)
{
// reminder: tuples
var me = (name: "Evan", age: 37);
WriteLine(me);
var alsoMe = (me.age, me.name);
WriteLine(alsoMe.Item1); // typical
WriteLine(alsoMe.name); // new
var result = new[] {"March", "April", "May"} // explicit name not required
.Select(m => (
/*Length:*/ m/*?*/.Length, // optionally nullable
FirstChar: m[0])) // some names (e.g., ToString) disallowed
.Where(t => t.Length == 5); // note how .Length is available here
WriteLine(string.Join(",", result));
// tuples produced by deconstruction
var now = DateTime.UtcNow;
var u = (now.Hour, now.Minute);
var v = ((u.Hour, u.Minute) = (11, 12));
WriteLine(v.Minute);
}
}
}
Pattern-Matching with Generics
cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CSharpDemos
{
using static System.Console;
public class Animal
{
}
public class Pig : Animal
{
}
public class PatternMatchingWithGenerics
{
public static void Cook<T>(T animal)
where T : Animal
{
// note the red squiggly!
// cast is redundant here
if ((object)animal is Pig pig)
{
// cook and eat it
Write("We cooked and ate the pig...");
}
switch (/*(object)*/animal)
{
case Pig pork:
WriteLine(" and it tastes delicious!");
break;
}
}
/// <summary>
/// Need to fall back to C# 7 for this.
/// </summary>
static void Main(string[] args)
{
var pig = new Pig();
Cook(pig);
}
}
}
C# 7.2 in Visual Studio 2017 15.5
Leading Digit Separators
cs
class LeadingUnderscoresNumericSeparators
{
static void Main(string[] args)
{
// binary
var x = 0b_1111_0000;
// hex
var y = 0x_baad_f00d;
}
}
'Private Protected' Access Modifier
cs
public class Base
{
private int a;
protected internal int b; // derived classes or classes in same assembly
private protected int c; // containing class or derived classes in same assembly only
}
class Derived : Base
{
public Derived()
{
c = 333; // fine
b = 3; // no
}
}
class Foo
{
static void Main(string[] args)
{
Base pp = new Base();
var d = new Derived();
d.b = 3;
// d.c is a no-go
}
}
Non-Trailing Named Arguments
cs
static void doSomething(int foo, int bar)
{
}
static void Main(string[] args)
{
doSomething(foo: 33, 44);
// still illegal
//doSomething(33, foo:44)
}
Reference Semantics on Value Types值类型的引用语义
'In' Parameters
'Ref Readonly' Variables
'Ref Struct' and Span
cs
struct Point
{
public double X, Y;
public Point(double x, double y)
{
X = x;
Y = y;
}
public void Reset()
{
X = Y = 0;
}
// we don't want to recreate origin as new Point(), so...
private static Point origin = new Point();
public static ref readonly Point Origin => ref origin;
public override string ToString()
{
return $"({X},{Y})";
}
}
public class RefSemanticsValueTypes
{
// IN PARAMETERS
void changeMe(ref Point p)
{
p.X++;
}
// structs are passed by reference (i.e. address, so 32 or 64 bits)
// 'in' is effectively by-ref and read-only
double MeasureDistance(in Point p1, in Point p2)
{
// cannot assign to in parameter
// p1 = new Point();
// cannot pass as ref or out method
// obvious
// changeMe(ref p2);
p2.Reset(); // instance operations happen on a copy!
var dx = p1.X - p2.X;
var dy = p1.Y - p2.Y;
return Math.Sqrt(dx * dx + dy * dy);
}
// cannot create overloads that differ only in presence?
// yeah you can, but
//double MeasureDistance(Point p1, Point p2)
//{
// return 0.0;
//}
public RefSemanticsValueTypes()
{
var p1 = new Point(1, 1);
var p2 = new Point(4, 5);
var distance = MeasureDistance(p1, p2);
// ^^^^ call ambiguous
Console.WriteLine($"Distance between {p1} and {p2} is {distance}");
// can also pass in temporaries
var distFromOrigin = MeasureDistance(p1, new Point());
var alsoDistanceFromOrigin = MeasureDistance(p2, Point.Origin);
// REF READONLY RETURNS
// make an ordinary by-value copy
Point copyOfOrigin = Point.Origin;
// it's readonly, you cannot do a ref!
//ref var messWithOrigin = ref Point.Origin;
ref readonly var originRef = ref Point.Origin;
// won't work
//originRef.X = 123;
}
// REF STRUCTS
// a value type that MUST be stack-allocated
// can never be created on the heap
// created specifically for Span<T>
class CannotDoThis
{
Span<byte> stuff;
}
static void Main(string[] args)
{
new RefSemanticsValueTypes();
unsafe
{
// managed
byte* ptr = stackalloc byte[100];
Span<byte> memory = new Span<byte>(ptr, 100);
// unmanaged
IntPtr unmanagedPtr = Marshal.AllocHGlobal(123);
Span<byte> unmanagedMemory = new Span<byte>(unmanagedPtr.ToPointer(), 123);
Marshal.FreeHGlobal(unmanagedPtr);
}
// implicit cast
char[] stuff = "hello".ToCharArray();
Span<char> arrayMemory = stuff;
// string is immutable so we can make a readonly span
ReadOnlySpan<char> more = "hi there!".AsSpan();
Console.WriteLine($"Our span has {more.Length} elements");
arrayMemory.Fill('x');
Console.WriteLine(stuff);
arrayMemory.Clear();
Console.WriteLine(stuff);
}
}
C# 7.3 in Visual Studio 2017 15.5
Performance Improvement
-
- Fixed-sized buffers
- Ref local variables maybe reassigned
- stackalloc arrays support initializers
csint* pArr1=stackalloc int[3]{1,2,3} int* pArr2=stackalloc int[]{1,2,3}
Features Enhancements
- Attributes on backing fields of auto-props (auto-props的后置字段上的属性)
cs
[field:SomeCleverAttribute]
public float SomeProperty{get;set;}
Extensioned expression variables in initializers
cs
public class B
{
public B(int i,out int j)
{
j=i;
}
}
public class D:B
{
public D(int i):B(i,out var j)
{
Console.WriteLine($"j = {j}}");
}
}
-
- Tutple support == and !=
- Imrpove overload resolution rules for method groups
New Compiler Features
-
- --deterministic
- --publicsign
What's new in C# 8
Nullable Reference Types
Method 1: 通过引用ReSharper.Annotations
[CanBeNull] Foo foo 会被解析为 Foo? foo
[CanBeNull] string middleName会被解析为string? middleName
cs
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[CanBeNull] public string MiddleName { get; set; }//CanBeNull is coming from ReSharper.Annotations on NuGet
public Employee(string firstName, string lastName, [CanBeNull] string middleName) => (FirstName, LastName, MiddleName) = (firstName, lastName, middleName);
public string FullName => $"{FirstName} {MiddleName} {LastName}";
}
Method 2: 通过使用string?
cs
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string? MiddleName { get; set; }
//[CanBeNull] public string MiddleName { get; set; }//CanBeNull is coming from ReSharper.Annotations on NuGet
//public Employee(string firstName, string lastName, [CanBeNull] string middleName) => (FirstName, LastName, MiddleName) = (firstName, lastName, middleName);
public Employee(string firstName, string lastName, string? middleName) => (FirstName, LastName, MiddleName) = (firstName, lastName, middleName);
public string FullName => $"{FirstName} {MiddleName} {LastName}";
}
以下代码会提示一个Warning:MiddleName maybe null here. CS8602:Dereference of a possible null reference.
cs
public string FullName => $"{FirstName} {MiddleName[0]} {LastName}";
If nullability is enabled
cs
string? ≠ Nullable<string>
string? is still a string, but we need null checks.
cs
public string FullName => $"{FirstName} {MiddleName?[0]} {LastName}";
cs
void Test()
{
string? str = GetString();
//Will get warning below
char c = str[0];
//no warning
if (str != null)
{
char c2 = str[0];
}
}
Override Null Checks
2 ways to stop null checks
-
- Keep the variable non-nullable
cs
public string MiddleName { get; set; } = string.Empty;
-
- Write expression with a bang(!)
cs
public string MiddleName { get; set; } = null!;
cs
//Warning
(null as Employee).FullName
//No Warning
(null as Employee)!.FullName
//No Warning. !可以随便输入多少个,都是合法的
(null as Employee)!!!!!!!!!.FullName
Diable nullable check
Edit **csproj file
cs
<Nullable>disable</Nullable>
Enable nullable check
Edit **csproj file,it is default setting.
cs
<Nullable>enable</Nullable>
Code in below are no warning.
cs
//No warning
Type t = Type.GetType(nameof(Employee));
string name = t.Name;
//No warning
Type? t2 = Type.GetType(nameof(Employee));
string name = t2.Name;
Index and Range
Points into an array
- Value
- IsFromEnd
cs
Index ids = 2;//implict conversion
Index idx2 = new Index(0, false);
var idx3 = ^0;//Index (0, true)
// -1 is not last
cs
//Index
var items = new[] { 1, 2, 3, 4 };
items[^2] = 33;//^2 去倒数第二个数值
Console.WriteLine($"{string.Join(",", items)}");//1,2,33,4
//Range from a[X...Y] means from a[X] to a[Y].
//If X > Y will through ArgumentOutOfRangeException
items = new[] { 1, 2, 3, 4 };
var items2 = items[0..2];//从第一个开始取值,总共取两个值
Console.WriteLine($"{string.Join(",", items2)}");//1,2
//Index + Range
var items3 = items[0..^0];//从第一个开始取值,取到最后一个值
Console.WriteLine($"{string.Join(",", items3)}");//1,2,3,4
//Array slices yield copies
var test = items[..2];//Create a copy
Console.WriteLine($"{string.Join(",", test)}");//1,2
var test2 = items.AsSpan<int>();//{1,2,3,4}
Console.WriteLine("Span");
foreach (var item in test2)
{
Console.WriteLine($"{item}");
}
Default Interface Members
Extension to create Default function in Interface
cs
public interface IHuman
{
string Name { get; set; }
}
public static class HumanExtension
{
public static void SayHello(this IHuman human)
{
Console.WriteLine($"Hello, I am {human.Name}");
}
}
Default function in iterface
It is same behavior with above code.
cs
public interface IHuman
{
string Name { get; set; }
public void SayHello()
{
Console.WriteLine($"Hello, I am {Name}");
}
}
Call demo:
cs
//Human human2 = new Human("Alex");
//human2.SayHello();//Compiler error
IHuman human = new Human("Alex");
human.SayHello();//Hello, I am Alex
((IHuman)new Human("Alex")).SayHello();//Hello, I am Alex
Interface override
cs
public class IFrieldlyHuman : IHuman//IHuman is same with code in above
{
public string Name { get; set; }
public void SayHello()
{
Console.WriteLine($"Greeting, I am {Name}");
}
}
public class Human2 : IFrieldlyHuman
{
public Human2(string name)
{
Name = name;
}
}
//call demo
((IFrieldlyHuman)new Human2("Alex")).SayHello();//Greeting, I am Alex
Pattern Matching
哪个人设计的这个绕的写法。。。
cs
struct PhoneNumer
{
public int Code, Number;
}
private void TestPhone()
{
var phoneNumer = new PhoneNumer();
string? origin;
//哪个人设计的这种写法,难读的要命
origin = phoneNumer switch
{
{ Number: 110 } => "Police",
{ Code: 86 } => "China",
{ } => null
};
//个人还是喜欢下面的写法
switch (phoneNumer)
{
case { Number: 110 }:
origin = "Police";
break;
case { Code: 86 }:
origin = "China";
break;
default:
origin = null;
break;
}
}
What's new in C# 9 (. NET 5)
Record Types
cs
var p = new Person() { Name = "Evan", Age = 37 };
var p2 = new Person() { Name = "Evan", Age = 37 };
Console.WriteLine(p);//Print: Person { Name = Evan, Age = 37, Address = }
Console.WriteLine(p2);//Print: Person { Name = Evan, Age = 37, Address = }
Console.WriteLine($"p==p2? {p == p2}");//Print: p==p2? true
var address = new Address() { AreaCode = 123456, Stress = "星火北路8号" };
var address2 = new Address() { AreaCode = 123456, Stress = "药谷大道8号" };
Console.WriteLine($"address==address2? {address == address2}");//Print: address==address2? false
p.Address = address;
p2.Address = address;
Console.WriteLine($"p==p2? {p == p2}");//Print: p==p2? true
p2.Address = address2;
Console.WriteLine($"p==p2? {p == p2}");//Print: p==p2? false
public record Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int AreaCode { get; set; }
public string Stress { get; set; }
}
浅拷贝with
cs
Car car = new() { Engine = "V6", Color = new CarColor() { Name = "Black", Metallic = false } };
Car upgradeCar = car with { Engine = "V8" };//Clone()=shallow copy. 浅拷贝
upgradeCar.Color.Metallic = true;
Console.WriteLine(car);//Print: Car { Engine = V6, Color = CarColor { Name = Black, Metallic = True } }
Console.WriteLine(upgradeCar);//Print: Car { Engine = V8, Color = CarColor { Name = Black, Metallic = True } }
public record Car
{
public string Engine { get; set; }
public CarColor Color { get; set; }
}
public record CarColor
{
public string Name { get; set; }
public bool Metallic { get; set; }
}
Top-level Calls
Program.cs 没有namespace了。
cs
Console.WriteLine("Hello, World!");
Foo();
void Foo()
{
Console.WriteLine("Hello, Foo!");
}
Initial Setters
cs
public class Demo
{
//readonly filed only can set value in initial or in ctor
public readonly string Filed = "ABC";
public Demo(string filed)
{
Filed = filed;
}
}
cs
public class Demo
{
//Inital only can set value in the class it ctor, not by call or function
public int Age { get; init; }
public Demo(int filed)
{
Age = filed;
}
//Below code will get error
public void ChangeValue(int newValue)
{
Age = newValue;
}
}
public class CallDemo
{
void Main()
{
//Below code will get error
var p = new Demo() { Age = 37 };
}
}
Pttern Matching Improvement
cs
object obj;
if (obj is not null)
{
}
if (obj is not string)//same as !(obj is string)
{
}
int temperature = 40;
var feel = temperature switch
{
< 0 => "冷",
>= 0 and < 20 => "温暖",
>= 20 and not 40 => "热",
40 or 666 => "热死了"
};
Console.WriteLine(feel);
public static bool IsLetter(this char c) =>
c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
public static bool IsLetterOrSeparator(this char c) =>
c is (>= 'a' and <= 'z') or (>= 'A' and <= 'Z') or ';' or ',';
Target-Typed New
cs
Person p=new Person();
var p2=new Person();
Person p=new();
Source Generators
Microsoft.CodeAnalysis.Analyzers, NugGet, ISourceGenerator
T4
cs
using Microsoft.CodeAnalysis;
namespace Gen
{
[Generator]
public class Generator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
var source=@"class Foo{public string Bar=""bar"";}";
context.AddSource("Gen.cs",source)
}
public void Execute(GeneratorInitializationContext context)
{
}
}
}
//更改.csproj
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>c:\temp</CompilerGeneratedFilesOutputPath>
//Build 完成后,在c:\temp\Gen\GenGenerator 会生成一个Gen.cs文件
Partial Method Syntax and Modules Initializers(部分方法语法和模块初始化器)
Startup.cs in API
What's new in C# 10 (. NET 6)
Visual Studio go to x64.
Record Structs
Same as Record class, but value types
Synthesuized members合成的成员
- Constructure,Deconstructure,Equals/==/!=, GetHashCode, PrintMembers, ToString
Predictable differences to record classes
- Eg: Equals() does not do null check
Performance can be significantly better than ordinary structs
- Also better than value Tuple
- Default Equals()/GetHaskCode() implementations are bad (boxing etc.)
- Record struct can be 20x faster with 100% less allocations
Restrictions
- Cannot have a clone member
- Instance field cannot be unsafe
- annot declare a destructor
cs
//small, composite value types
record struct Point<T>(T x, T y){...}
Point<int> p=new (100,200);
//Record structs are mutable(可变的)
//Mutable structs are dangerous
player.Position.X++;// does nothing. means don't change anything
//Recommend:
record struct Point2<T>(T x, T y){...}
p.X++;//will not compile
Global Using Directives
using System has been replaced because we have global using System.
我们可以创建一个GlobalUsings.cs,把常用的引用放在里面。
.NET6 project included several global usings that are implicit(隐式的):
cs
<ImplicitUsings>enable</ImplicitUsings>
File-Scoped Namespace Declarations
vs有工具去选择""Tofile-scoped namespace""
Extended Property Patterns
cs
static void Test()
{
object obj;
//Check properties using dot notation
if (obj is Developer { Manager.FirstName: "Sam" } d)
{
Console.WriteLine(d.Manager.FirstName);
}
//Check multiple patterns and arbitrary(任意的) depth
if (obj is Developer { Manager.FirstName.Length: 5, Manager.yearOfWork: 10 } d2)
{
Console.WriteLine(d2.Manager.FirstName);
}
}
public class Developer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Manager Manager { get; set; }
}
public class Manager
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int yearOfWork { get; set; }
}
Generic Attributes通用属性
cs
//Current approacgh to taking a type in an attribute
class MyAttribute: Attribute
{
MyAttribute(Type type){...}//伪代码
}
//usege
[My(typeof(Foo))]
//Now we can use type parameters instead:
class My Attribute
{
...
}
//usege
[My<float>] public void Bar(){...}
//Type parameters from containing type cannot be used in attributes.eg:
public class Program<T>
{
[SomeAttr<T>] void Foo(){...}//got error
[SomeAttr<List<T>>] void Foo(){...}//got error
}
Lambda Improvements,提高很多
cs
//Add attributes to lambda
var f=[A]{}=>{...};
//Demo:
var f=[return:A] x=>x;//syntax error at '=>', wrong demo
var f=[return:A] (x)=>x;//[A] lambda. Correct demo
//Multiple attributes
var f=[a1,a2][a3]()=>{};
var f2=([a1][a2,a3] int x)=>x;
//Attributes not support in delegate. will get error in below code:
f=[a] delegate {return 1;}// syntax error at 'delegate'
f=delegate ([a] int x) {return 1;}// syntax error at '['
//Collection initializers also use [a] syntax,so, the parser will differences:
var f=new C {[A]=x};//means: f[A]=x
var f2=new C {[A] x=>x};//means: f2[0]=[A] x=>x
//? (conditional element), cannot go in front:
x=b? [A];// correct
y=b? [A] ()=>{}:z;//error, syntax error at '('
//Explict return type
//You can specify an explicit return type before the parameters
f=T()=>{};//correct
f= ref int (ref int x) => ref x;//correct
f=static void (_) => {};//correct
//Not support delegate{} syntax
f= delegate int {retur 1;};// syntax error
f= delegate int (int x) {return x;};//syntax error
//Exact method type inference from lambda return:
static void F<T> (Func<T,T> f) {...}
F(int (i) => i);//Func<int,int>
//Varaibles conversion not allowed from lambda return type to delegate return type:
Func<object> f = string ()=> 'Evan';//error
Func<object?> f2 = object()=>x; //Warning
//Lambda expressions with ref return types are allowed within expressions (without additional parens圆括号)
d= ref int () =>x;// equals: d=(ref int () => x)
F(ref int() => x);// equals: F((ref int() => x))
//Var cannot be used as explicit return type
d=var (var x)=>x;//Error: contextula keyword 'var' cannot be used as explicit lambda return type
//Lambda will be infered to Action/Func<>
var f =()=>1;// System.Func<int>
var f2 = string() => "Evan";// System.Func<string>
var f3 = delegate (object o) (object o) {};// System.Action<object>
Enhanced #line directives
debug/diagnostics/if**
cs
#if ANYCPU
...
#endif
#if DEBUG
...
#endif