主頁 > .NET開發 > C# 模式匹配完全指南

C# 模式匹配完全指南

2022-03-05 06:01:10 .NET開發

前言

自從 2017 年 C# 7.0 版本開始引入宣告模式和常數模式匹配開始,到 2022 年的 C# 11 為止,最后一個板塊串列模式和切片模式匹配也已經補齊,當初計劃的模式匹配內容已經基本全部完成,

C# 在模式匹配方面下一步計劃則是支持活動模式(active pattern),這一部分將在本文最后進行介紹,而在介紹未來的模式匹配計劃之前,本文主題是對截止 C# 11 模式匹配的(不)完全指南,希望能對各位開發者們提升代碼撰寫效率、可讀性和質量有所幫助,

模式匹配

要使用模式匹配,首先要了解什么是模式,在使用正則運算式匹配字串時,正則運算式自己就是一個模式,而對字串使用這段正則運算式進行匹配的程序就是模式匹配,而在代碼中也是同樣的,我們對物件采用某種模式進行匹配的程序就是模式匹配,

C# 11 支持的模式有很多,包含:

  • 宣告模式(declaration pattern)
  • 型別模式(type pattern)
  • 常數模式(constant pattern)
  • 關系模式(relational pattern)
  • 邏輯模式(logical pattern)
  • 屬性模式(property pattern)
  • 位置模式(positional pattern)
  • var 模式(var pattern)
  • 丟棄模式(discard pattern)
  • 串列模式(list pattern)
  • 切片模式(slice pattern)

而其中,不少模式都支持遞回,也就意味著可以模式嵌套模式,以此來實作更加強大的匹配功能,

如果你不清楚這些模式的話,可以訪問 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/patterns 進行了解,

模式匹配可以通過 switch 運算式來使用,也可以在普通的 switch 陳述句中作為 case 使用,還可以在 if 條件中通過 is 來使用,本文主要在 switch 運算式中使用模式匹配,

那么接下來就對這些模式進行介紹,

實體:運算式計算器

為了更直觀地介紹模式匹配,我們接下來利用模式匹配來撰寫一個運算式計算器,

為了撰寫運算式計算器,首先我們需要對運算式進行抽象:

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public abstract T Eval(params (string Name, T Value)[] args);
}

我們用上面這個 Expr<T> 來表示一個運算式,其中 T 是運算元的型別,然后進一步將運算式分為常數運算式 ConstantExpr、引數運算式 ParameterExpr、一元運算式 UnaryExpr、二元運算式 BinaryExpr 和三元運算式 TernaryExpr,最后提供一個 Eval 方法,用來計算運算式的值,該方法可以傳入一個 args 來提供運算式計算所需要的引數,

有了一、二元運算式自然也需要運算子,例如加減乘除等,我們也同時定義 Operator 來表示運算子:

public abstract record Operator
{
    public record UnaryOperator(Operators Operator) : Operator;
    public record BinaryOperator(BinaryOperators Operator) : Operator;
}

然后設定允許的運算子,其中前三個是一元運算子,后面的是二元運算子:

public enum Operators
{
    [Description("~")] Inv, [Description("-")] Min, [Description("!")] LogicalNot,
    [Description("+")] Add, [Description("-")] Sub, [Description("*")] Mul, [Description("/")] Div,
    [Description("&")] And, [Description("|")] Or, [Description("^")] Xor,
    [Description("==")] Eq, [Description("!=")] Ne,
    [Description(">")] Gt, [Description("<")] Lt, [Description(">=")] Ge, [Description("<=")] Le,
    [Description("&&")] LogicalAnd, [Description("||")] LogicalOr,
}

你可以能會好奇對 T 的運算能如何實作邏輯與或非,關于這一點,我們直接使用 0 來代表 false,非 0 代表 true

接下來就是分別實作各類運算式的時間!

常數運算式

常數運算式很簡單,它保存一個常數值,因此只需要在構造方法中將用戶提供的值存盤下來,它的 Eval 實作也只需要簡單回傳存盤的值即可:

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public class ConstantExpr : Expr<T>
    {
        public ConstantExpr(T value) => Value = https://www.cnblogs.com/hez2010/archive/2022/03/04/value;

        public T Value { get; }
        public void Deconstruct(out T value) => value = Value;

        public override T Eval(params (string Name, T Value)[] args) => Value;
    }
}

引數運算式

引數運算式用來定義運算式計算程序中的引數,允許用戶在對運算式執行 Eval 計算結果的時候傳參,因此只需要存盤引數名,它的 Eval 實作需要根據引數名在 args 中找出對應的引數值:

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public class ParameterExpr : Expr<T>
    {
        public ParameterExpr(string name) => Name = name;

        public string Name { get; }
        public void Deconstruct(out string name) => name = Name;

        // 對 args 進行模式匹配
        public override T Eval(params (string Name, T Value)[] args) => args switch
        {
            // 如果 args 有至少一個元素,那我們把第一個元素拿出來存為 (name, value),
            // 然后判斷 name 是否和本引數運算式中存盤的引數名 Name 相同,
            // 如果相同則回傳 value,否則用 args 除去第一個元素剩下的引數繼續匹配,
            [var (name, value), .. var tail] => name == Name ? value : Eval(tail),
            // 如果 args 是空串列,則說明在 args 中沒有找到名字和 Name 相同的引數,拋出例外
            [] => throw new InvalidOperationException($"Expected an argument named {Name}.")
        };
    }
}

模式匹配會從上往下依次進行匹配,直到匹配成功為止,

上面的代碼中你可能會好奇 [var (name, value), .. var tail] 是個什么模式,這個模式整體看是串列模式,并且串列模式內組合使用宣告模式、位置模式和切片模式,例如:

  • []:匹配一個空串列,
  • [1, _, 3]:匹配一個長度是 3,并且首尾元素分別是 1、3 的串列,其中 _ 是丟棄模式,表示任意元素,
  • [_, .., 3]:匹配一個末元素是 3,并且 3 不是首元素的串列,其中 .. 是切片模式,表示任意切片,
  • [1, ..var tail]:匹配一個首元素是 1 的串列,并且將除了首元素之外元素的切片賦值給 tail,其中 var tail 是宣告模式,用于將匹配結果賦值給變數,
  • [var head, ..var tail]:匹配一個串列,將它第一個元素賦值給 head,剩下元素的切片賦值給 tail,這個切片里可以沒有元素,
  • [var (name, value), ..var tail]:匹配一個串列,將它第一個元素賦值給 (name, value),剩下元素的切片賦值給 tail,這個切片里可以沒有元素,其中 (name, value) 是位置模式,用于將第一個元素的解構結果根據位置分別賦值給 namevalue,也可以寫成 (var name, var value)

一元運算式

一元運算式用來處理只有一個運算元的計算,例如非、取反等,

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public class UnaryExpr : Expr<T>
    {
        public UnaryExpr(UnaryOperator op, Expr<T> expr) => (Op, Expr) = (op, expr);

        public UnaryOperator Op { get; }
        public Expr<T> Expr { get; }
        public void Deconstruct(out UnaryOperator op, out Expr<T> expr) => (op, expr) = (Op, Expr);

        // 對 Op 進行模式匹配
        public override T Eval(params (string Name, T Value)[] args) => Op switch
        {
            // 如果 Op 是 UnaryOperator,則將其解構結果賦值給 op,然后對 op 進行匹配,op 是一個列舉,而 .NET 中的列舉值都是整數
            UnaryOperator(var op) => op switch
            {
                // 如果 op 是 Operators.Inv
                Operators.Inv => ~Expr.Eval(args),
                // 如果 op 是 Operators.Min
                Operators.Min => -Expr.Eval(args),
                // 如果 op 是 Operators.LogicalNot
                Operators.LogicalNot => Expr.Eval(args) == T.Zero ? T.One : T.Zero,
                // 如果 op 的值大于 LogicalNot 或者小于 0,表示不是一元運算子
                > Operators.LogicalNot or < 0 => throw new InvalidOperationException($"Expected an unary operator, but got {op}.")
            },
            // 如果 Op 不是 UnaryOperator
            _ => throw new InvalidOperationException("Expected an unary operator.")
        };
    }
}

上面的代碼中,首先利用了 C# 元組可作為左值的特性,分別使用一行代碼就做完了構造方法和解構方法的賦值:(Op, Expr) = (op, expr)(op, expr) = (Op, Expr),如果你好奇能否利用這個特性交換多個變數,答案是可以!

Eval 中,首先將型別模式、位置模式和宣告模式組合成 UnaryOperator(var op),表示匹配 UnaryOperator 型別、并且能解構出一個元素的東西,如果匹配則將解構出來的那個元素賦值給 op

然后我們接著對解構出來的 op 進行匹配,這里用到了常數模式,例如 Operators.Inv 用來匹配 op 是否是 Operators.Inv,常數模式可以使用各種常數對物件進行匹配,

這里的 > Operators.LogicalNot< 0 則是關系模式,分別用于匹配大于 Operators.LogicalNot 的值和小于 0 的指,然后利用邏輯模式 or 將兩個模式組合起來表示或的關系,邏輯模式除了 or 之外還有 andnot

由于我們在上面窮舉了列舉中所有的一元運算子,因此也可以將 > Operators.LogicalNot or < 0 換成丟棄模式 _ 或者 var 模式 var foo,兩者都用來匹配任意的東西,只不過前者匹配到后直接丟棄,而后者宣告了個變數 foo 將匹配到的值放到里面:

op switch
{
    // ...
    _ => throw new InvalidOperationException($"Expected an unary operator, but got {op}.")
}

op switch
{
    // ...
    var foo => throw new InvalidOperationException($"Expected an unary operator, but got {foo}.")
}

二元運算式

二元運算式用來表示運算元有兩個的運算式,有了一元運算式的撰寫經驗,二元運算式如法炮制即可,

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public class BinaryExpr : Expr<T>
    {
        public BinaryExpr(BinaryOperator op, Expr<T> left, Expr<T> right) => (Op, Left, Right) = (op, left, right);

        public BinaryOperator Op { get; }
        public Expr<T> Left { get; }
        public Expr<T> Right { get; }
        public void Deconstruct(out BinaryOperator op, out Expr<T> left, out Expr<T> right) => (op, left, right) = (Op, Left, Right);

        public override T Eval(params (string Name, T Value)[] args) => Op switch
        {
            BinaryOperator(var op) => op switch
            {
                Operators.Add => Left.Eval(args) + Right.Eval(args),
                Operators.Sub => Left.Eval(args) - Right.Eval(args),
                Operators.Mul => Left.Eval(args) * Right.Eval(args),
                Operators.Div => Left.Eval(args) / Right.Eval(args),
                Operators.And => Left.Eval(args) & Right.Eval(args),
                Operators.Or => Left.Eval(args) | Right.Eval(args),
                Operators.Xor => Left.Eval(args) ^ Right.Eval(args),
                Operators.Eq => Left.Eval(args) == Right.Eval(args) ? T.One : T.Zero,
                Operators.Ne => Left.Eval(args) != Right.Eval(args) ? T.One : T.Zero,
                Operators.Gt => Left.Eval(args) > Right.Eval(args) ? T.One : T.Zero,
                Operators.Lt => Left.Eval(args) < Right.Eval(args) ? T.One : T.Zero,
                Operators.Ge => Left.Eval(args) >= Right.Eval(args) ? T.One : T.Zero,
                Operators.Le => Left.Eval(args) <= Right.Eval(args) ? T.One : T.Zero,
                Operators.LogicalAnd => Left.Eval(args) == T.Zero || Right.Eval(args) == T.Zero ? T.Zero : T.One,
                Operators.LogicalOr => Left.Eval(args) == T.Zero && Right.Eval(args) == T.Zero ? T.Zero : T.One,
                < Operators.Add or > Operators.LogicalOr => throw new InvalidOperationException($"Unexpected a binary operator, but got {op}.")
            },
            _ => throw new InvalidOperationException("Unexpected a binary operator.")
        };
    }
}

同理,也可以將 < Operators.Add or > Operators.LogicalOr 換成丟棄模式或者 var 模式,

三元運算式

三元運算式包含三個運算元:條件運算式 Cond、為真的運算式 Left、為假的運算式 Right,該運算式中會根據 Cond 是否為真來選擇取 Left 還是 Right,實作起來較為簡單:

public abstract partial class Expr<T> where T : IBinaryNumber<T>
{
    public class TernaryExpr : Expr<T>
    {
        public TernaryExpr(Expr<T> cond, Expr<T> left, Expr<T> right) => (Cond, Left, Right) = (cond, left, right);

        public Expr<T> Cond { get; }
        public Expr<T> Left { get; }
        public Expr<T> Right { get; }
        public void Deconstruct(out Expr<T> cond, out Expr<T> left, out Expr<T> right) => (cond, left, right) = (Cond, Left, Right);

        public override T Eval(params (string Name, T Value)[] args) => Cond.Eval(args) == T.Zero ? Right.Eval(args) : Left.Eval(args);
    }
}

完成,我們用了僅僅幾十行代碼就完成了全部的核心邏輯!這便是模式匹配的強大之處:簡潔、直觀且高效,

運算式判等

至此為止,我們已經完成了所有的運算式構造、解構和計算的實作,接下來我們為每一個運算式實作判等邏輯,即判斷兩個運算式(字面上)是否相同,

例如 a == b ? 2 : 4a == b ? 2 : 5 不相同,a == b ? 2 : 4c == d ? 2 : 4 不相同,而 a == b ? 2 : 4a == b ? 2 : 4 相同,

為了實作該功能,我們重寫每一個運算式的 EqualsGetHashCode 方法,

常數運算式

常數運算式判等只需要判斷常數值是否相等即可:

public override bool Equals(object? obj) => obj is ConstantExpr(var value) && value =https://www.cnblogs.com/hez2010/archive/2022/03/04/= Value;
public override int GetHashCode() => Value.GetHashCode();

引數運算式

引數運算式判等只需要判斷引數名是否相等即可:

public override bool Equals(object? obj) => obj is ParameterExpr(var name) && name == Name;
public override int GetHashCode() => Name.GetHashCode();

一元運算式

一元運算式判等,需要判斷被比較的運算式是否是一元運算式,如果也是的話則判斷運算子和運算元是否相等:

public override bool Equals(object? obj) => obj is UnaryExpr({ Operator: var op }, var expr) && (op, expr).Equals((Op.Operator, Expr));
public override int GetHashCode() => (Op, Expr).GetHashCode();

上面的代碼中用到了屬性模式 { Operator: var op },用來匹配屬性的值,這里直接組合了宣告模式將屬性 Operator 的值賦值給了 expr,另外,C# 中的元組可以組合起來進行判等操作,因此不需要寫 op.Equals(Op.Operator) && expr.Equals(Expr),而是可以直接寫 (op, expr).Equals((Op.Operator, Expr))

二元運算式

和一元運算式差不多,區別在于這次多了一個運算元:

public override bool Equals(object? obj) => obj is BinaryExpr({ Operator: var op }, var left, var right) && (op, left, right).Equals((Op.Operator, Left, Right));
public override int GetHashCode() => (Op, Left, Right).GetHashCode();

三元運算式

和二元運算式差不多,只不過運算子 Op 變成了運算元 Cond

public override bool Equals(object? obj) => obj is TernaryExpr(var cond, var left, var right) && cond.Equals(Cond) && left.Equals(Left) && right.Equals(Right);
public override int GetHashCode() => (Cond, Left, Right).GetHashCode();

到此為止,我們為所有的運算式都實作了判等,

一些工具方法

我們多載一些 Expr<T> 的運算子方便我們使用:

public static Expr<T> operator ~(Expr<T> operand) => new UnaryExpr(new(Operators.Inv), operand);
public static Expr<T> operator !(Expr<T> operand) => new UnaryExpr(new(Operators.LogicalNot), operand);
public static Expr<T> operator -(Expr<T> operand) => new UnaryExpr(new(Operators.Min), operand);
public static Expr<T> operator +(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Add), left, right);
public static Expr<T> operator -(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Sub), left, right);
public static Expr<T> operator *(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Mul), left, right);
public static Expr<T> operator /(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Div), left, right);
public static Expr<T> operator &(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.And), left, right);
public static Expr<T> operator |(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Or), left, right);
public static Expr<T> operator ^(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Xor), left, right);
public static Expr<T> operator >(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Gt), left, right);
public static Expr<T> operator <(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Lt), left, right);
public static Expr<T> operator >=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Ge), left, right);
public static Expr<T> operator <=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Le), left, right);
public static Expr<T> operator ==(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Eq), left, right);
public static Expr<T> operator !=(Expr<T> left, Expr<T> right) => new BinaryExpr(new(Operators.Ne), left, right);
public static implicit operator Expr<T>(T value) => new ConstantExpr(value);
public static implicit operator Expr<T>(string name) => new ParameterExpr(name);
public static implicit operator Expr<T>(bool value) => new ConstantExpr(value ? T.One : T.Zero);

public override bool Equals(object? obj) => base.Equals(obj);
public override int GetHashCode() => base.GetHashCode();

由于多載了 ==!=,編譯器為了保險起見提示我們重寫 EqualsGetHashCode,這里實際上并不需要重寫,因此直接呼叫 base 上的方法保持默認行為即可,

然后撰寫兩個擴展方法用來方便構造三元運算式,和從 Description 中獲取運算子的名字:

public static class Extensions
{
    public static Expr<T> Switch<T>(this Expr<T> cond, Expr<T> left, Expr<T> right) where T : IBinaryNumber<T> => new Expr<T>.TernaryExpr(cond, left, right);
    public static string? GetName<T>(this T op) where T : Enum => typeof(T).GetMember(op.ToString()).FirstOrDefault()?.GetCustomAttribute<DescriptionAttribute>()?.Description;
}

由于有引數運算式參與時需要我們提前提供引數值才能呼叫 Eval 進行計算,因此我們寫一個互動式的 Eval 來在計算程序中遇到引數運算式時提示用戶輸入值,起名叫做 InteractiveEval

public T InteractiveEval()
{
    var names = Array.Empty<string>();
    return Eval(GetArgs(this, ref names, ref names));
}
private static T GetArg(string name, ref string[] names)
{
    Console.Write($"Parameter {name}: ");
    string? str;
    do { str = Console.ReadLine(); }
    while (str is null);
    names = names.Append(name).ToArray();
    return T.Parse(str, NumberStyles.Number, null);
}
private static (string Name, T Value)[] GetArgs(Expr<T> expr, ref string[] assigned, ref string[] filter) => expr switch
{
    TernaryExpr(var cond, var left, var right) => GetArgs(cond, ref assigned, ref assigned).Concat(GetArgs(left, ref assigned,ref assigned)).Concat(GetArgs(right, ref assigned, ref assigned)).ToArray(),
    BinaryExpr(_, var left, var right) => GetArgs(left, ref assigned, ref assigned).Concat(GetArgs(right, ref assigned, refassigned)).ToArray(),
    UnaryExpr(_, var uexpr) => GetArgs(uexpr, ref assigned, ref assigned),
    ParameterExpr(var name) => filter switch
    {
        [var head, ..] when head == name => Array.Empty<(string Name, T Value)>(),
        [_, .. var tail] => GetArgs(expr, ref assigned, ref tail),
        [] => new[] { (name, GetArg(name, ref assigned)) }
    },
    _ => Array.Empty<(string Name, T Value)>()
};

這里在 GetArgs 方法中,模式 [var head, ..] 后面跟了一個 when head == name,這里的 when 用來給模式匹配指定額外的條件,僅當條件滿足時才匹配成功,因此 [var head, ..] when head == name 的含義是,匹配至少含有一個元素的串列,并且將頭元素賦值給 head,且僅當 head == name 時匹配才算成功,

最后我們再重寫 ToString 方法方便輸出運算式,就全部大功告成了,

測驗

接下來讓我測驗測驗我們撰寫的運算式計算器:

Expr<int> a = 4;
Expr<int> b = -3;
Expr<int> x = "x";
Expr<int> c = !((a + b) * (a - b) > x);
Expr<int> y = "y";
Expr<int> z = "z";
Expr<int> expr = (c.Switch(y, z) - a > x).Switch(z + a, y / b);
Console.WriteLine(expr);
Console.WriteLine(expr.InteractiveEval());

運行后得到輸出:

((((! ((((4) + (-3)) * ((4) - (-3))) > (x))) ? (y) : (z)) - (4)) > (x)) ? ((z) + (4)) : ((y) / (-3))

然后我們給 xyz 分別設定成 42、27 和 35,即可得到運算結果:

Parameter x: 42
Parameter y: 27
Parameter z: 35
-9

再測測運算式判等邏輯:

Expr<int> expr1, expr2, expr3;
{
    Expr<int> a = 4;
    Expr<int> b = -3;
    Expr<int> x = "x";
    Expr<int> c = !((a + b) * (a - b) > x);
    Expr<int> y = "y";
    Expr<int> z = "z";
    expr1 = (c.Switch(y, z) - a > x).Switch(z + a, y / b);
}

{
    Expr<int> a = 4;
    Expr<int> b = -3;
    Expr<int> x = "x";
    Expr<int> c = !((a + b) * (a - b) > x);
    Expr<int> y = "y";
    Expr<int> z = "z";
    expr2 = (c.Switch(y, z) - a > x).Switch(z + a, y / b);
}

{
    Expr<int> a = 4;
    Expr<int> b = -3;
    Expr<int> x = "x";
    Expr<int> c = !((a + b) * (a - b) > x);
    Expr<int> y = "y";
    Expr<int> w = "w";
    expr3 = (c.Switch(y, w) - a > x).Switch(w + a, y / b);
}

Console.WriteLine(expr1.Equals(expr2));
Console.WriteLine(expr1.Equals(expr3));

得到輸出:

True
False

活動模式

在未來,C# 將會引入活動模式,該模式允許用戶自定義模式匹配的方法,例如:

static bool Even<T>(this T value) where T : IBinaryInteger<T> => value % 2 == 0;

上述代碼定義了一個 T 的擴展方法 Even,用來匹配 value 是否為偶數,于是我們便可以這么使用:

var x = 3;
var y = x switch
{
    Even() => "even",
    _ => "odd"
};

此外,該模式還可以和解構模式結合,允許用戶自定義解構行為,例如:

static bool Int(this string value, out int result) => int.TryParse(value, out result);

然后使用的時候:

var x = "3";
var y = x switch
{
    Int(var result) => result,
    _ => 0
};

即可對 x 這個字串進行匹配,如果 x 可以被決議為 int,就取決議結果 result,否則取 0,

后記

模式匹配極大的方便了我們撰寫出簡潔且可讀性高的高質量代碼,并且會自動幫我們做窮舉檢查,防止我們漏掉情況,此外,使用模式匹配時,編譯器也會幫我們優化代碼,減少完成匹配所需要的比較次數,最終減少分支并提升運行效率,

本文中的例子為了覆寫到全部的模式,不一定采用了最優的寫法,這一點各位讀者們也請注意,

本文中的運算式計算器全部代碼可以前往我的 GitHub 倉庫獲取:https://github.com/hez2010/PatternMatchingExpr

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/437842.html

標籤:.NET技术

上一篇:實作一個簡單檔案服務

下一篇:實作一個簡單檔案服務

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more