What is new in C# 7,8,9,10
目录
What's new in C# 7
C# 7 in Visual Studio 2017
Out Variables
Pattern Matching
Tuples (System.ValueTuple)
Deconstruct解构
Local Functions
Ref Returns and Locals
Expression Bodied Members
Throw Expressions
Generated Async Return Types
Literal Improvements
C# 7.1 in Visual Studio 2017.3
Compilation Issues (how to switch to C#7.1)
Async Main
Default Expressions
Ref Assemblies
Infer Tuple Names
Pattern-Matching with Generics
C# 7.2 in Visual Studio 2017 15.5
Leading Digit Separators
'Private Protected' Access Modifier
Non-Trailing Named Arguments
Reference Semantics on Value Types值类型的引用语义
C# 7.3 in Visual Studio 2017 15.5
Performance Improvement
Features Enhancements
Extensioned expression variables in initializers
New Compiler Features
What's new in C# 8
Nullable Reference Types
Method 1: 通过引用ReSharper.Annotations
Method 2: 通过使用string?
Override Null Checks
Diable nullable check
Enable nullable check
Index and Range
Default Interface Members
Extension to create Default function in Interface
Default function in iterface
Pattern Matching
What's new in C# 9 (. NET 5)
Record Types
浅拷贝with
Top-level Calls
Initial Setters
Pttern Matching Improvement
Target-Typed New
Source Generators
Partial Method Syntax and Modules Initializers(部分方法语法和模块初始化器)
What's new in C# 10 (. NET 6)
Record Structs
Global Using Directives
File-Scoped Namespace Declarations
Extended Property Patterns
Generic Attributes通用属性
Lambda Improvements,提高很多
Enhanced #line directives
What's new in C# 7
C# 7 in Visual Studio 2017
Out Variables
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
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)
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解构
// 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体内,或者方法体外,放在方法体内是,所在的位置可以在调用前或者调用后面。
建议放在前面,方便代码维护。
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);
}
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
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
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
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
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
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去解决编译错误。
static async Task Main(string[] args)
{
Console.WriteLine("ABC");
}
Async Main
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
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
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
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
class LeadingUnderscoresNumericSeparators
{
static void Main(string[] args)
{
// binary
var x = 0b_1111_0000;
// hex
var y = 0x_baad_f00d;
}
}
'Private Protected' Access Modifier
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
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
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
-
int* 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的后置字段上的属性)
[field:SomeCleverAttribute]
public float SomeProperty{get;set;}
Extensioned expression variables in initializers
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
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?
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.
public string FullName => $"{FirstName} {MiddleName[0]} {LastName}";
If nullability is enabled
string? ≠ Nullable<string>
string? is still a string, but we need null checks.
public string FullName => $"{FirstName} {MiddleName?[0]} {LastName}";
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
- 1. Keep the variable non-nullable
public string MiddleName { get; set; } = string.Empty;
- 2. Write expression with a bang(!)
public string MiddleName { get; set; } = null!;
//Warning
(null as Employee).FullName
//No Warning
(null as Employee)!.FullName
//No Warning. !可以随便输入多少个,都是合法的
(null as Employee)!!!!!!!!!.FullName
Diable nullable check
Edit **csproj file
<Nullable>disable</Nullable>
Enable nullable check
Edit **csproj file,it is default setting.
<Nullable>enable</Nullable>
Code in below are no warning.
//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
Index ids = 2;//implict conversion
Index idx2 = new Index(0, false);
var idx3 = ^0;//Index (0, true)
// -1 is not last
//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
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.
public interface IHuman
{
string Name { get; set; }
public void SayHello()
{
Console.WriteLine($"Hello, I am {Name}");
}
}
Call demo:
//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
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
哪个人设计的这个绕的写法。。。
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
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
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了。
Console.WriteLine("Hello, World!");
Foo();
void Foo()
{
Console.WriteLine("Hello, Foo!");
}
Initial Setters
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;
}
}
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
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
Person p=new Person();
var p2=new Person();
Person p=new();
Source Generators
Microsoft.CodeAnalysis.Analyzers, NugGet, ISourceGenerator
T4
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
//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(隐式的):
<ImplicitUsings>enable</ImplicitUsings>
File-Scoped Namespace Declarations
vs有工具去选择“”Tofile-scoped namespace“”
Extended Property Patterns
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通用属性
//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,提高很多
//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**
#if ANYCPU
...
#endif
#if DEBUG
...
#endif