C# 11 的新特性和改進前瞻

来源:https://www.cnblogs.com/hez2010/archive/2022/07/10/whats-new-in-csharp-11.html
-Advertisement-
Play Games

前言 .NET 7 的開發還剩下一個多月就要進入 RC,C# 11 的新特性和改進也即將敲定。在這個時間點上,不少新特性都已經實現完畢併合併入主分支 C# 11 包含的新特性和改進非常多,類型系統相比之前也有了很大的增強,在確保靜態類型安全的同時大幅提升了語言表達力。 那麼本文就按照方向從 5 個大 ...


前言

.NET 7 的開發還剩下一個多月就要進入 RC,C# 11 的新特性和改進也即將敲定。在這個時間點上,不少新特性都已經實現完畢併合併入主分支

C# 11 包含的新特性和改進非常多,類型系統相比之前也有了很大的增強,在確保靜態類型安全的同時大幅提升了語言表達力。

那麼本文就按照方向從 5 個大類來進行介紹,一起來提前看看 C# 11 的新特性和改進都有什麼。

1. 類型系統的改進

抽象和虛靜態方法

C# 11 開始將 abstractvirtual 引入到靜態方法中,允許開發者在介面中編寫抽象和虛靜態方法。

介面與抽象類不同,介面用來抽象行為,通過不同類型實現介面來實現多態;而抽象類則擁有自己的狀態,通過各子類型繼承父類型來實現多態。這是兩種不同的範式。

在 C# 11 中,虛靜態方法的概念被引入,在介面中可以編寫抽象和虛靜態方法了。

interface IFoo
{
    // 抽象靜態方法
    abstract static int Foo1();

    // 虛靜態方法
    virtual static int Foo2()
    {
        return 42;
    }
}

struct Bar : IFoo
{
    // 隱式實現介面方法
    public static int Foo1()
    {
        return 7;
    }
}

Bar.Foo1(); // ok

由於運算符也屬於靜態方法,因此從 C# 11 開始,也可以用介面來對運算符進行抽象了。

interface ICanAdd<T> where T : ICanAdd<T>
{
    abstract static T operator +(T left, T right);
}

這樣我們就可以給自己的類型實現該介面了,例如實現一個二維的點 Point

record struct Point(int X, int Y) : ICanAdd<Point>
{
    // 隱式實現介面方法
    public static Point operator +(Point left, Point right)
    {
        return new Point(left.X + right.X, left.Y + right.Y);
    }
}

然後我們就可以對兩個 Point 進行相加了:

var p1 = new Point(1, 2);
var p2 = new Point(2, 3);
Console.WriteLine(p1 + p2); // Point { X = 3, Y = 5 }

除了隱式實現介面之外,我們也可以顯式實現介面:

record struct Point(int X, int Y) : ICanAdd<Point>
{
    // 顯式實現介面方法
    static Point ICanAdd<Point>.operator +(Point left, Point right)
    {
        return new Point(left.X + right.X, left.Y + right.Y);
    }
}

不過用顯示實現介面的方式的話,+ 運算符沒有通過 public 公開暴露到類型 Point 上,因此我們需要通過介面來調用 + 運算符,這可以利用泛型約束來做到:

var p1 = new Point(1, 2);
var p2 = new Point(2, 3);
Console.WriteLine(Add(p1, p2)); // Point { X = 3, Y = 5 }

T Add<T>(T left, T right) where T : ICanAdd<T>
{
    return left + right;
}

對於不是運算符的情況,則可以利用泛型參數來調用介面上的抽象和靜態方法:

void CallFoo1<T>() where T : IFoo
{
    T.Foo1();
}

Bar.Foo1(); // error
CallFoo<Bar>(); // ok

struct Bar : IFoo
{
    // 顯式實現介面方法
    static void IFoo.Foo1()
    {
        return 7;
    }
}

此外,介面可以基於另一個介面擴展,因此對於抽象和虛靜態方法而言,我們可以利用這個特性在介面上實現多態。

CallFoo<Bar1>(); // 5 5
CallFoo<Bar2>(); // 6 4
CallFoo<Bar3>(); // 3 7
CallFooFromIA<Bar4>(); // 1
CallFooFromIB<Bar4>(); // 2

void CallFoo<T>() where T : IC
{
    CallFooFromIA<T>();
    CallFooFromIB<T>();
}

void CallFooFromIA<T>() where T : IA
{
    Console.WriteLine(T.Foo());
}

void CallFooFromIB<T>() where T : IB
{
    Console.WriteLine(T.Foo());
}

interface IA
{
    virtual static int Foo()
    {
        return 1;
    }
}

interface IB
{
    virtual static int Foo()
    {
        return 2;
    }
}

interface IC : IA, IB
{
    static int IA.Foo()
    {
        return 3;
    }

    static int IB.Foo()
    {
        return 4;
    }
}

struct Bar1 : IC
{
    public static int Foo()
    {
        return 5;
    }
}

struct Bar2 : IC
{
    static int IA.Foo()
    {
        return 6;
    }
}

struct Bar3 : IC
{
    static int IB.Foo()
    {
        return 7;
    }
}

struct Bar4 : IA, IB { }

同時,.NET 7 也利用抽象和虛靜態方法,對基礎庫中的數值類型進行了改進。在 System.Numerics 中新增了大量的用於數學的泛型介面,允許用戶利用泛型編寫通用的數學計算代碼:

using System.Numerics;

V Eval<T, U, V>(T a, U b, V c) 
    where T : IAdditionOperators<T, U, U>
    where U : IMultiplyOperators<U, V, V>
{
    return (a + b) * c;
}

Console.WriteLine(Eval(3, 4, 5)); // 35
Console.WriteLine(Eval(3.5f, 4.5f, 5.5f)); // 44

泛型 attribute

C# 11 正式允許用戶編寫和使用泛型 attribute,因此我們可以不再需要使用 Type 來在 attribute 中存儲類型信息,這不僅支持了類型推導,還允許用戶通過泛型約束在編譯時就能對類型進行限制。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
class FooAttribute<T> : Attribute where T : INumber<T>
{
    public T Value { get; }
    public FooAttribute(T v)
    {
        Value = v;
    }
}

[Foo<int>(3)] // ok
[Foo<float>(4.5f)] // ok
[Foo<string>("test")] // error
void MyFancyMethod() { }

ref 欄位和 scoped ref

C# 11 開始,開發者可以在 ref struct 中編寫 ref 欄位,這允許我們將其他對象的引用存儲在一個 ref struct 中:

int x = 1;
Foo foo = new(ref x);
foo.X = 2;
Console.WriteLine(x); // 2

ref struct Foo
{
    public ref int X;
    
    public Foo(ref int x)
    {
        X = ref x;
    }
}

可以看到,上面的代碼中將 x 的引用保存在了 Foo 中,因此對 foo.X 的修改會反映到 x 上。

如果用戶沒有對 Foo.X 進行初始化,則預設是空引用,可以利用 Unsafe.IsNullRef 來判斷一個 ref 是否為空:

ref struct Foo
{
    public ref int X;
    public bool IsNull => Unsafe.IsNullRef(ref X);
    
    public Foo(ref int x)
    {
        X = ref x;
    }
}

這裡可以發現一個問題,那就是 ref field 的存在,可能會使得一個 ref 指向的對象的生命周期被擴展而導致錯誤,例如:

Foo MyFancyMethod()
{
    int x = 1;
    Foo foo = new(ref x);
    return foo; // error
}

ref struct Foo
{
    public Foo(ref int x) { }
}

上述代碼編譯時會報錯,因為 foo 引用了局部變數 x,而局部變數 x 在函數返回後生命周期就結束了,但是返回 foo 的操作使得 foo 的生命周期比 x 的生命周期更長,這會導致無效引用的問題,因此編譯器檢測到了這一點,不允許代碼通過編譯。

但是上述代碼中,雖然 foo 確實引用了 x,但是 foo 對象本身並沒有長期持有 x 的引用,因為在構造函數返回後就不再持有對 x 的引用了,因此這裡按理來說不應該報錯。於是 C# 11 引入了 scoped 的概念,允許開發者顯式標註 ref 的生命周期,標註了 scopedref 表示這個引用的生命周期不會超過當前函數的生命周期:

Foo MyFancyMethod()
{
    int x = 1;
    Foo foo = new(ref x);
    return foo; // ok
}

ref struct Foo
{
    public Foo(scoped ref int x) { }
}

這樣一來,編譯器就知道 Foo 的構造函數不會使得 Foo 在構造函數返回後仍然持有 x 的引用,因此上述代碼就能安全通過編譯了。如果我們試圖讓一個 scoped ref 逃逸出當前函數的話,編譯器就會報錯:

ref struct Foo
{
    public ref int X;
    public Foo(scoped ref int x)
    {
        X = ref x; // error
    }
}

如此一來,就實現了引用安全。

利用 ref 欄位,我們可以很方便地實現各種零開銷設施,例如提供一個多種方法訪問顏色數據的 ColorView

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

var color = new Color { R = 1, G = 2, B = 3, A = 4 };
color.RawOfU32[0] = 114514;
color.RawOfU16[1] = 19198;
color.RawOfU8[2] = 10;
Console.WriteLine(color.A); // 74

[StructLayout(LayoutKind.Explicit)]
struct Color
{
    [FieldOffset(0)] public byte R;
    [FieldOffset(1)] public byte G;
    [FieldOffset(2)] public byte B;
    [FieldOffset(3)] public byte A;

    [FieldOffset(0)] public uint Rgba;

    public ColorView<byte> RawOfU8 => new(ref this);
    public ColorView<ushort> RawOfU16 => new(ref this);
    public ColorView<uint> RawOfU32 => new(ref this);
}

ref struct ColorView<T> where T : unmanaged
{
    private ref Color color;
    public ColorView(ref Color color)
    {
        this.color = ref color;
    }
    
    [DoesNotReturn] private static ref T Throw() => throw new IndexOutOfRangeException();

    public ref T this[uint index]
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get
        {
            unsafe
            {
                return ref (sizeof(T) * index >= sizeof(Color) ?
                    ref Throw() :
                    ref Unsafe.Add(ref Unsafe.AsRef<T>(Unsafe.AsPointer(ref color)), (int)index));
            }
        }
    }
}

在欄位中,ref 還可以配合 readonly 一起使用,用來表示不可修改的 ref,例如:

  • ref int:一個 int 的引用
  • readonly ref int:一個 int 的只讀引用
  • ref readonly int:一個只讀 int 的引用
  • readonly ref readonly int:一個只讀 int 的只讀引用

這將允許我們確保引用的安全,使得引用到只讀內容的引用不會被意外更改。

當然,C# 11 中的 ref 欄位和 scoped 支持只是其完全形態的一部分,更多的相關內容仍在設計和討論,併在後續版本中推出。

文件局部類型

C# 11 引入了新的文件局部類型可訪問性符號 file,利用該可訪問性符號,允許我們編寫只能在當前文件中使用的類型:

// A.cs

file class Foo
{
    // ...
}

file struct Bar
{
    // ...
}

如此一來,如果我們在與 FooBar 的不同文件中使用這兩個類型的話,編譯器就會報錯:

// A.cs
var foo = new Foo(); // ok
var bar = new Bar(); // ok

// B.cs
var foo = new Foo(); // error
var bar = new Bar(); // error

這個特性將可訪問性的粒度精確到了文件,對於代碼生成器等一些要放在同一個項目中,但是又不想被其他人接觸到的代碼而言將會特別有用。

required 成員

C# 11 新增了 required 成員,標記有 required 的成員將會被要求使用時必須要進行初始化,例如:

var foo = new Foo(); // error
var foo = new Foo { X = 1 }; // ok

struct Foo
{
    public required int X;
}

開發者還可以利用 SetsRequiredMembers 這個 attribute 來對方法進行標註,表示這個方法會初始化 required 成員,因此用戶在使用時可以不需要再進行初始化:

using System.Diagnostics.CodeAnalysis;

var p = new Point(); // error
var p = new Point { X = 1, Y = 2 }; // ok
var p = new Point(1, 2); // ok

struct Point
{
    public required int X;
    public required int Y;

    [SetsRequiredMembers]
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
}

利用 required 成員,我們可以要求其他開發者在使用我們編寫的類型時必須初始化一些成員,使其能夠正確地使用我們編寫的類型,而不會忘記初始化一些成員。

2. 運算改進

checked 運算符

C# 自古以來就有 checkedunchecked 概念,分別表示檢查和不檢查算術溢出:

byte x = 100;

byte y = 200;
unchecked
{
    byte z = (byte)(x + y); // ok
}

checked
{
    byte z = (byte)(x + y); // error
}

在 C# 11 中,引入了 checked 運算符概念,允許用戶分別實現用於 checkedunchecked 的運算符:

struct Foo
{
    public static Foo operator +(Foo left, Foo right) { ... }
    public static Foo operator checked +(Foo left, Foo right) { ... }
}

var foo1 = new Foo(...);
var foo2 = new Foo(...);
var foo3 = unchecked(foo1 + foo2); // 調用 operator +
var foo4 = checked(foo1 + foo2); // 調用 operator checked +

對於自定義運算符而言,實現 checked 的版本是可選的,如果沒有實現 checked 的版本,則都會調用 unchecked 的版本。

無符號右移運算符

C# 11 新增了 >>> 表示無符號的右移運算符。此前 C# 的右移運算符 >> 預設是有符號的右移,即:右移操作保留符號位,因此對於 int 而言,將會有如下結果:

1 >> 1 = -1
1 >> 2 = -1
1 >> 3 = -1
1 >> 4 = -1
// ...

而新的 >>> 則是無符號右移運算符,使用後將會有如下結果:

1 >>> 1 = 2147483647
1 >>> 2 = 1073741823
1 >>> 3 = 536870911
1 >>> 4 = 268435455
// ...

這省去了我們需要無符號右移時,需要先將數值轉換為無符號數值後進行計算,再轉換回來的麻煩,也能避免不少因此導致的意外錯誤。

移位運算符放開類型限制

C# 11 開始,移位運算符的右操作數不再要求必須是 int,類型限制和其他運算符一樣被放開了,因此結合上面提到的抽象和虛靜態方法,允許我們聲明泛型的移位運算符了:

interface ICanShift<T> where T : ICanShift<T>
{
    abstract static T operator <<(T left, T right);
    abstract static T operator >>(T left, T right);
}

當然,上述的場景是該限制被放開的主要目的。然而,相信不少讀者讀到這裡心中都可能會萌生一個邪惡的想法,沒錯,就是 cincout!雖然這種做法在 C# 中是不推薦的,但該限制被放開後,開發者確實能編寫類似的代碼了:

using static OutStream;
using static InStream;

int x = 0;
_ = cin >> To(ref x); // 有 _ = 是因為 C# 不允許運算式不經過賦值而單獨成為一條語句
_ = cout << "hello" << " " << "world!";

public class OutStream
{
    public static OutStream cout = new();
    public static OutStream operator <<(OutStream left, string right)
    {
        Console.WriteLine(right);
        return left;
    }
}

public class InStream
{
    public ref struct Ref<T>
    {
        public ref T Value;
        public Ref(ref T v) => Value = ref v;
    }
    public static Ref<T> To<T>(ref T v) => new (ref v);
    public static InStream cin = new();
    public static InStream operator >>(InStream left, Ref<int> right)
    {
        var str = Console.Read(...);
        right.Value = int.Parse(str);
    }
}

IntPtr、UIntPtr 支持數值運算

C# 11 中,IntPtrUIntPtr 都支持數值運算了,這極大的方便了我們對指針進行操作:

UIntPtr addr = 0x80000048;
IntPtr offset = 0x00000016;
UIntPtr newAddr = addr + (UIntPtr)offset; // 0x8000005E

當然,如同 Int32intInt64long 的關係一樣,C# 中同樣存在 IntPtrUIntPtr 的等價簡寫,分別為 nintnuint,n 表示 native,用來表示這個數值的位數和當前運行環境的記憶體地址位數相同:

nuint addr = 0x80000048;
nint offset = 0x00000016;
nuint newAddr = addr + (nuint)offset; // 0x8000005E

3. 模式匹配改進

列表模式匹配

C# 11 中新增了列表模式,允許我們對列表進行匹配。在列表模式中,我們可以利用 [ ] 來包括我們的模式,用 _ 代指一個元素,用 .. 代表 0 個或多個元素。在 .. 後可以聲明一個變數,用來創建匹配的子列表,其中包含 .. 所匹配的元素。

例如:

var array = new int[] { 1, 2, 3, 4, 5 };
if (array is [1, 2, 3, 4, 5]) Console.WriteLine(1); // 1
if (array is [1, 2, 3, ..]) Console.WriteLine(2); // 2
if (array is [1, _, 3, _, 5]) Console.WriteLine(3); // 3
if (array is [.., _, 5]) Console.WriteLine(4); // 4
if (array is [1, 2, 3, .. var remaining])
{
    Console.WriteLine(remaining[0]); // 4
    Console.WriteLine(remaining.Length); // 2
}

當然,和其他的模式一樣,列表模式同樣是支持遞歸的,因此我們可以將列表模式與其他模式組合起來使用:

var array = new string[] { "hello", ",", "world", "~" };
if (array is ["hello", _, { Length: 5 }, { Length: 1 } elem, ..])
{
    Console.WriteLine(elem); // ~
}

除了在 if 中使用模式匹配以外,在 switch 中也同樣能使用:

var array = new string[] { "hello", ",", "world", "!" };

switch (array)
{
    case ["hello", _, { Length: 5 }, { Length: 1 } elem, ..]:
        // ...
        break;
    default:
        // ...
        break;
}

var value = array switch
{
    ["hello", _, { Length: 5 }, { Length: 1 } elem, ..] => 1,
    _ => 2
};

Console.WriteLine(value); // 1

對 Span<char> 的模式匹配

在 C# 中,Span<char>ReadOnlySpan<char> 都可以看作是字元串的切片,因此 C# 11 也為這兩個類型添加了字元串模式匹配的支持。例如:

int Foo(ReadOnlySpan<char> span)
{
    if (span is "abcdefg") return 1;
    return 2;
}

Foo("abcdefg".AsSpan()); // 1
Foo("test".AsSpan()); // 2

如此一來,使用 Span<char> 或者 ReadOnlySpan<char> 的場景也能夠非常方便地進行字元串匹配了,而不需要利用 SequenceEquals 或者編寫迴圈進行處理。

4. 字元串處理改進

原始字元串

C# 中自初便有 @ 用來表示不需要轉義的字元串,但是用戶還是需要將 " 寫成 "" 才能在字元串中包含引號。C# 11 引入了原始字元串特性,允許用戶利用原始字元串在代碼中插入大量的無需轉移的文本,方便開發者在代碼中以字元串的方式塞入代碼文本等。

原始字元串需要被至少三個 " 包裹,例如 """""""" 等等,前後的引號數量要相等。另外,原始字元串的縮進由後面引號的位置來確定,例如:

var str = """
    hello
    world
    """;

此時 str 是:

hello
world

而如果是下麵這樣:

var str = """
    hello
    world
""";

str 則會成為:

    hello
    world

這個特性非常有用,例如我們可以非常方便地在代碼中插入 JSON 代碼了:

var json = """
    {
        "a": 1,
        "b": {
            "c": "hello",
            "d": "world"
        },
        "c": [1, 2, 3, 4, 5]
    }
    """;
Console.WriteLine(json);
/*
{
    "a": 1,
    "b": {
        "c": "hello",
        "d": "world"
    },
    "c": [1, 2, 3, 4, 5]
}
*/

UTF-8 字元串

C# 11 引入了 UTF-8 字元串,我們可以用 u8 尾碼來創建一個 ReadOnlySpan<byte>,其中包含一個 UTF-8 字元串:

var str1 = "hello world"u8; // ReadOnlySpan<byte>
var str2 = "hello world"u8.ToArray(); // byte[]

UTF-8 對於 Web 場景而言非常有用,因為在 HTTP 協議中,預設編碼就是 UTF-8,而 .NET 則預設是 UTF-16 編碼,因此在處理 HTTP 協議時,如果沒有 UTF-8 字元串,則會導致大量的 UTF-8 和 UTF-16 字元串的相互轉換,從而影響性能。

有了 UTF-8 字元串後,我們就能非常方便的創建 UTF-8 字面量來使用了,不再需要手動分配一個 byte[] 然後在裡面一個一個硬編碼我們需要的字元。

字元串插值允許換行

C# 11 開始,字元串的插值部分允許換行,因此如下代碼變得可能:

var str = $"hello, the leader is {group
                                    .GetLeader()
                                    .GetName()}.";

這樣一來,當插值的部分代碼很長時,我們就能方便的對代碼進行格式化,而不需要將所有代碼擠在一行。

5. 其他改進

struct 自動初始化

C# 11 開始,struct 不再強制構造函數必須要初始化所有的欄位,對於沒有初始化的欄位,編譯器會自動做零初始化:

struct Point
{
    public int X;
    public int Y;

    public Point(int x)
    {
        X = x;
        // Y 自動初始化為 0
    }
}

支持對其他參數名進行 nameof

C# 11 允許了開發者在參數中對其他參數名進行 nameof,例如在使用 CallerArgumentExpression 這一 attribute 時,此前我們需要直接硬編碼相應參數名的字元串,而現在只需要使用 nameof 即可:

void Assert(bool condition, [CallerArgumentExpression(nameof(condition))] string expression = "")
{
    // ...
}

這將允許我們在進行代碼重構時,修改參數名 condition 時自動修改 nameof 裡面的內容,方便的同時減少出錯。

自動緩存靜態方法的委托

C# 11 開始,從靜態方法創建的委托將會被自動緩存,例如:

void Foo()
{
    Call(Console.WriteLine);
}

void Call(Action action)
{
    action();
}

此前,每執行一次 Foo,就會從 Console.WriteLine 這一靜態方法創建一個新的委托,因此如果大量執行 Foo,則會導致大量的委托被重覆創建,導致大量的記憶體被分配,效率極其低下。在 C# 11 開始,將會自動緩存靜態方法的委托,因此無論 Foo 被執行多少次,Console.WriteLine 的委托只會被創建一次,節省了記憶體的同時大幅提升了性能。

總結

從 C# 8 開始,C# 團隊就在不斷完善語言的類型系統,在確保靜態類型安全的同時大幅提升語言表達力,從而讓類型系統成為編寫程式的得力助手,而不是礙手礙腳的限制。

本次更新還完善了數值運算相關的內容,使得開發者利用 C# 編寫數值計算方法時更加得心應手。

另外,模式匹配的探索旅程也終於接近尾聲,引入列表模式之後,剩下的就只有字典模式和活動模式了,模式匹配是一個非常強大的工具,允許我們像對字元串使用正則表達式那樣非常方便地對數據進行匹配。

總的來說 C# 11 的新特性和改進內容非常多,每一項內容都對 C# 的使用體驗有著不小的提升。在未來的 C# 中還計划著角色和擴展等更加令人激動的新特性,讓我們拭目以待。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Reentrant 2 前兩篇寫完了後我自己研究了下,還有有很多疑惑和問題,這篇就繼續以自問自答的方式寫 如果沒看過第1篇的可以先看看那個https://www.cnblogs.com/sunankang/p/16458795.html public final void acquire(int a ...
  • Aspose簡介 Aspose.Total是Aspose公司旗下全套文件格式處理解決方案,提供最完整、最高效的文檔處理解決方案集,無需任何其他軟體安裝和依賴。主要提供.net、java、C++d三個開發語言的工具包,通過它,可以對辦公文檔格式的轉換和文檔內容的線上編輯,如:Word, Excel, ...
  • Java面向對象(一) 一、面向過程(POP)與面向對象(OOP) 二者都是一種思想,面向對象是相對於面向過程而言的。面向過程,強調的是功能行為,以函數為最小單位,考慮怎麼做。面向對象,將功能封裝進對 象,強調具備了功能的對象,以類/對象為最小單位,考慮誰來做。 面向對象的三大特征: 封裝 繼承 多 ...
  • 稀疏組織 當一個數組中大部分元素為0,或者為同一個值的數組時,可以用稀疏數組來保存該數組 稀疏數組,記錄一共有幾行幾列,有多少個不同值 把具有不同值的元素和行里了及值記錄在一個小規模的數組中,從而縮小程式的規模! 我們定義一下原始數組: 原始數組如下: 0 0 3 0 0 0 0 0 0 4 0 0 ...
  • 歡迎關註公眾號:bin的技術小屋,本文圖片載入不出來的話可查看公眾號原文 本系列Netty源碼解析文章基於 4.1.56.Final版本 1. 前文回顧 在前邊的系列文章中,筆者為大家詳細剖析了 Reactor 模型在 netty 中的創建,啟動,運行,接收連接,接收數據,發送數據的完整流程,在詳細 ...
  • 一年一度的面試高峰期又來了,技術學習群的很多朋友問我有沒有關於JVM基礎面試題,網上各種面試題沒有分類很混亂,無法系統性參考學習。 於是,我就把之前整理的以及我面試過的真題和答案都整理了一份分享給大家。共計108道面試題! 持續根據技術群反饋不端更新,將涵蓋內容包括: Java設計模式、Spring ...
  • Arrays類的常用方法 代碼示例: public class ArrayDemo { public static void main(String[] args) { int[] a={1,2,57,8,1,58,4,51,5,45,15,1}; System.out.println(a); // ...
  • Java Development Kit 簡稱 JDK,任何需要開發 Java 程式的環境都需要進行安裝 JDK。 JDK 下載地址:https://www.oracle.com/java/technologies/downloads Windows 電腦安裝 JDK 1. 下載 JDK 在 JDK ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...