主頁 > .NET開發 > 由C# yield return引發的思考

由C# yield return引發的思考

2023-05-31 09:55:31 .NET開發

前言

    當我們撰寫 C# 代碼時,經常需要處理大量的資料集合,在傳統的方式中,我們往往需要先將整個資料集合加載到記憶體中,然后再進行操作,但是如果資料集合非常大,這種方式就會導致記憶體占用過高,甚至可能導致程式崩潰,

    C# 中的yield return機制可以幫助我們解決這個問題,通過使用yield return,我們可以將資料集合按需生成,而不是一次性生成整個資料集合,這樣可以大大減少記憶體占用,并且提高程式的性能,

    在本文中,我們將深入討論 C# 中yield return的機制和用法,幫助您更好地理解這個強大的功能,并在實際開發中靈活使用它,

使用方式

上面我們提到了yield return將資料集合按需生成,而不是一次性生成整個資料集合,接下來通過一個簡單的示例,我們看一下它的作業方式是什么樣的,以便加深對它的理解

foreach (var num in GetInts())
{
    Console.WriteLine("外部遍歷了:{0}", num);
}

IEnumerable<int> GetInts()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("內部遍歷了:{0}", i);
        yield return i;
    }
}

首先,在GetInts方法中,我們使用yield return關鍵字來定義一個迭代器,這個迭代器可以按需生成整數序列,在每次回圈時,使用yield return回傳當前的整數,通過1foreach回圈來遍歷 GetInts方法回傳的整數序列,在迭代時GetInts方法會被執行,但是不會將整個序列加載到記憶體中,而是在需要時,按需生成序列中的每個元素,在每次迭代時,會輸出當前迭代的整數對應的資訊,所以輸出的結果為

內部遍歷了:0
外部遍歷了:0
內部遍歷了:1
外部遍歷了:1
內部遍歷了:2
外部遍歷了:2
內部遍歷了:3
外部遍歷了:3
內部遍歷了:4
外部遍歷了:4

可以看到,整數序列是按需生成的,并且在每次生成時都會輸出相應的資訊,這種方式可以大大減少記憶體占用,并且提高程式的性能,當然從c# 8開始異步迭代的方式同樣支持

await foreach (var num in GetIntsAsync())
{
    Console.WriteLine("外部遍歷了:{0}", num);
}

async IAsyncEnumerable<int> GetIntsAsync()
{
    for (int i = 0; i < 5; i++)
    {
        await Task.Yield();
        Console.WriteLine("內部遍歷了:{0}", i);
        yield return i;
    }
}

和上面不同的是,如果需要用異步的方式,我們需要回傳IAsyncEnumerable型別,這種方式的執行結果和上面同步的方式執行的結果是一致的,我們就不做展示了,上面我們的示例都是基于回圈持續迭代的,其實使用yield return的方式還可以按需的方式去輸出,這種方式適合靈活迭代的方式,如下示例所示

foreach (var num in GetInts())
{
    Console.WriteLine("外部遍歷了:{0}", num);
}

IEnumerable<int> GetInts()
{
    Console.WriteLine("內部遍歷了:0");
    yield return 0;

    Console.WriteLine("內部遍歷了:1");
    yield return 1;

    Console.WriteLine("內部遍歷了:2");
    yield return 2;
}

foreach回圈每次會呼叫GetInts()方法,GetInts()方法的內部便使用yield return關鍵字回傳一個結果,每次遍歷都會去執行下一個yield return,所以上面代碼輸出的結果是

內部遍歷了:0
外部遍歷了:0
內部遍歷了:1
外部遍歷了:1
內部遍歷了:2
外部遍歷了:2

探究本質

上面我們展示了yield return如何使用的示例,它是一種延遲加載的機制,它可以讓我們逐個地處理資料,而不是一次性地將所有資料讀取到記憶體中,接下來我們就來探究一下神奇操作的背后到底是如何實作的,方便讓大家更清晰的了解迭代體系相關,

foreach本質

首先我們來看一下foreach為什么可以遍歷,也就是如果可以被foreach遍歷的物件,被遍歷的操作需要滿足哪些條件,這個時候我們可以反編譯工具來看一下編譯后的代碼是什么樣子的,相信大家最熟悉的就是List<T>集合的遍歷方式了,那我們就用List<T>的示例來演示一下

List<int> ints = new List<int>();
foreach(int item in ints)
{
    Console.WriteLine(item);
}

上面的這段代碼很簡單,我們也沒有給它任何初始化的資料,這樣可以排除干擾,讓我們能更清晰的看到反編譯的結果,排除其他干擾,它反編譯后的代碼是這樣的

List<int> list = new List<int>();
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        int current = enumerator.Current;
        Console.WriteLine(current);
    }
}
finally
{
    ((IDisposable)enumerator).Dispose();
}

可以反編譯代碼的工具有很多,我用的比較多的一般是ILSpydnSpydotPeek和在線c#反編譯網站sharplab.io,其中dnSpy還可以除錯反編譯的代碼,

通過上面的反編譯之后的代碼我們可以看到foreach會被編譯成一個固定的結構,也就是我們經常提及的設計模式中的迭代器模式結構

Enumerator enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
   var current = enumerator.Current;
}

通過這段固定的結構我們總結一下foreach的作業原理

  • 可以被foreach的物件需要要包含GetEnumerator()方法
  • 迭代器物件包含MoveNext()方法和Current屬性
  • MoveNext()方法回傳bool型別,判斷是否可以繼續迭代,Current屬性回傳當前的迭代結果,

我們可以看一下List<T>類可迭代的原始碼結構是如何實作的

public class List<T> : IList<T>, IList, IReadOnlyList<T>
{
    public Enumerator GetEnumerator() => new Enumerator(this);
 
    IEnumerator<T> IEnumerable<T>.GetEnumerator() => Count == 0 ? SZGenericArrayEnumerator<T>.Empty : GetEnumerator();
 
    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<T>)this).GetEnumerator();

    public struct Enumerator : IEnumerator<T>, IEnumerator
    {
        public T Current => _current!;
        public bool MoveNext()
        {
        }
    }
}

這里涉及到了兩個核心的介面IEnumerable<IEnumerator,他們兩個定義了可以實作迭代的能力抽象,實作方式如下

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

public interface IEnumerator
{
    bool MoveNext();
    object Current{ get; }
    void Reset();
}

如果類實作IEnumerable介面并實作了GetEnumerator()方法便可以被foreach,迭代的物件是IEnumerator型別,包含一個MoveNext()方法和Current屬性,上面的介面是原始物件的方式,這種操作都是針對object型別集合物件,我們實際開發程序中大多數都是使用的泛型集合,當然也有對應的實作方式,如下所示

public interface IEnumerable<out T> : IEnumerable
{
    new IEnumerator<T> GetEnumerator();
}

public interface IEnumerator<out T> : IDisposable, IEnumerator
{
    new T Current{ get; }
}

可以被foreach迭代并不意味著一定要去實作IEnumerable介面,這只是給我們提供了一個可以被迭代的抽象的能力,只要類中包含GetEnumerator()方法并回傳一個迭代器,迭代器里包含回傳bool型別的MoveNext()方法和獲取當前迭代物件的Current屬性即可,

yield return本質

上面我們看到了可以被foreach迭代的本質是什么,那么yield return的回傳值可以被IEnumerable<T>接收說明其中必有蹊蹺,我們反編譯一下我們上面的示例看一下反編譯之后代碼,為了方便大家對比反編譯結果,這里我把上面的示例再次粘貼一下

foreach (var num in GetInts())
{
    Console.WriteLine("外部遍歷了:{0}", num);
}

IEnumerable<int> GetInts()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine("內部遍歷了:{0}", i);
        yield return i;
    }
}

它的反編譯結果,這里咱們就不全部展示了,只展示一下核心的邏輯

//foeach編譯后的結果
IEnumerator<int> enumerator = GetInts().GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        int current = enumerator.Current;
        Console.WriteLine("外部遍歷了:{0}", current);
    }
}
finally
{
    if (enumerator != null)
    {
        enumerator.Dispose();
    }
}

//GetInts方法編譯后的結果
private IEnumerable<int> GetInts()
{
    <GetInts>d__1 <GetInts>d__ = new <GetInts>d__1(-2);
    <GetInts>d__.<>4__this = this;
    return <GetInts>d__;
}

這里我們可以看到GetInts()方法里原來的代碼不見了,而是多了一個<GetInts>d__1 l型別,也就是說yield return本質是語法糖,我們看一下<GetInts>d__1類的實作

//生成的類即實作了IEnumerable介面也實作了IEnumerator介面
//說明它既包含了GetEnumerator()方法,也包含MoveNext()方法和Current屬性
private sealed class <>GetIntsd__1 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IEnumerator, IDisposable
{
    private int <>1__state;
    //當前迭代結果
    private int <>2__current;
    private int <>l__initialThreadId;
    public C <>4__this;
    private int <i>5__1;

    //當前迭代到的結果
    int IEnumerator<int>.Current
    {
        get{ return <>2__current; }
    }

    //當前迭代到的結果
    object IEnumerator.Current
    {
        get{ return <>2__current; }
    }

    //建構式包含狀態欄位,變向說明靠狀態機去實作核心流程流轉
    public <GetInts>d__1(int <>1__state)
    {
        this.<>1__state = <>1__state;
        <>l__initialThreadId = Environment.CurrentManagedThreadId;
    }

    //核心方法MoveNext
    private bool MoveNext()
    {
        int num = <>1__state;
        if (num != 0)
        {
            if (num != 1)
            {
                return false;
            }
            //控制狀態
            <>1__state = -1;
            //自增 也就是代碼里回圈的i++
            <i>5__1++;
        }
        else
        {
            <>1__state = -1;
            <i>5__1 = 0;
        }
        //回圈終止條件 上面回圈里的i<5
        if (<i>5__1 < 5)
        {
            Console.WriteLine("內部遍歷了:{0}", <i>5__1);
            //把當前迭代結果賦值給Current屬性
            <>2__current = <i>5__1;
            <>1__state = 1;
            //說明可以繼續迭代
            return true;
        }
        //迭代結束
        return false;
    }

    //IEnumerator的MoveNext方法
    bool IEnumerator.MoveNext()
    {
        return this.MoveNext();
    }

    //IEnumerable的IEnumerable方法
    IEnumerator<int> IEnumerable<int>.IEnumerable()
    {
        //實體化<GetInts>d__1實體
        <GetInts>d__1 <GetInts>d__;
        if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
        {
            <>1__state = 0;
            <GetInts>d__ = this;
        }
        else
        {
            //給狀態機初始化
            <GetInts>d__ = new <GetInts>d__1(0);
            <GetInts>d__.<>4__this = <>4__this;
        }
        //因為<GetInts>d__1實作了IEnumerator介面所以可以直接回傳
        return <GetInts>d__;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        //因為<GetInts>d__1實作了IEnumerator介面所以可以直接轉換
        return ((IEnumerable<int>)this).GetEnumerator();
    }

    void IEnumerator.Reset()
    {
    }

    void IDisposable.Dispose()
    {
    }
}

通過它生成的類我們可以看到,該類即實作了IEnumerable介面也實作了IEnumerator介面說明它既包含了GetEnumerator()方法,也包含MoveNext()方法和Current屬性,用這一個類就可以滿足可被foeach迭代的核心結構,我們手動寫的for代碼被包含到了MoveNext()方法里,它包含了定義的狀態機制代碼,并且根據當前的狀態機代碼將迭代移動到下一個元素,我們大概講解一下我們的for代碼被翻譯到MoveNext()方法里的執行流程

  • 首次迭代時<>1__state被初始化成0,代表首個被迭代的元素,這個時候Current初始值為0,回圈控制變數<i>5__1初始值也為0,
  • 判斷是否滿足終止條件,不滿足則執行回圈里的邏輯,并更改裝填機<>1__state為1,代表首次迭代執行完成,
  • 回圈控制變數<i>5__1繼續自增并更改并更改裝填機<>1__state為-1,代表可持續迭代,并回圈執行回圈體的自定義邏輯,
  • 不滿足迭代條件則回傳false,也就是代表了MoveNext()以不滿足迭代條件while (enumerator.MoveNext())邏輯終止,

上面我們還展示了另一種yield return的方式,就是同一個方法里包含多個yield return的形式

IEnumerable<int> GetInts()
{
    Console.WriteLine("內部遍歷了:0");
    yield return 0;

    Console.WriteLine("內部遍歷了:1");
    yield return 1;

    Console.WriteLine("內部遍歷了:2");
    yield return 2;
}

上面這段代碼反編譯的結果如下所示,這里咱們只展示核心的方法MoveNext()的實作

private bool MoveNext()
{
    switch (<>1__state)
    {
        default:
            return false;
        case 0:
            <>1__state = -1;
            Console.WriteLine("內部遍歷了:0");
            <>2__current = 0;
            <>1__state = 1;
            return true;
        case 1:
            <>1__state = -1;
            Console.WriteLine("內部遍歷了:1");
            <>2__current = 1;
            <>1__state = 2;
            return true;
        case 2:
            <>1__state = -1;
            Console.WriteLine("內部遍歷了:2");
            <>2__current = 2;
            <>1__state = 3;
            return true;
        case 3:
            <>1__state = -1;
            return false;
    }
}

通過編譯后的代碼我們可以看到,多個yield return的形式會被編譯成switch...case的形式,有幾個yield return則會編譯成n+1case,多出來的一個case則代表的MoveNext()終止條件,也就是回傳false的條件,其它的case則回傳true表示可以繼續迭代,

IAsyncEnumerable介面

上面我們展示了同步yield return方式,c# 8開始新增了IAsyncEnumerable<T>介面,用于完成異步迭代,也就是迭代器邏輯里包含異步邏輯的場景,IAsyncEnumerable<T>介面的實作代碼如下所示

public interface IAsyncEnumerable<out T>
{
    IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}

public interface IAsyncEnumerator<out T> : IAsyncDisposable
{
    ValueTask<bool> MoveNextAsync();
    T Current { get; }
}

它最大的不同則是同步的IEnumerator包含的是MoveNext()方法回傳的是boolIAsyncEnumerator介面包含的是MoveNextAsync()異步方法,回傳的是ValueTask<bool>型別,所以上面的示例代碼

await foreach (var num in GetIntsAsync())
{
    Console.WriteLine("外部遍歷了:{0}", num);
}

所以這里的await雖然是加在foreach上面,但是實際作用的則是每一次迭代執行的MoveNextAsync()方法,可以大致理解為下面的作業方式

IAsyncEnumerator<int> enumerator = list.GetAsyncEnumerator();
while (enumerator.MoveNextAsync().GetAwaiter().GetResult())
{
   var current = enumerator.Current;
}

當然,實際編譯成的代碼并不是這個樣子的,我們在之前的文章<研究c#異步操作async await狀態機的總結>一文中講解過async await會被編譯成IAsyncStateMachine異步狀態機,所以IAsyncEnumerator<T>結合yield return的實作比同步的方式更加復雜而且包含更多的代碼,不過實作原理可以結合同步的方式類比一下,但是要同時了解異步狀態機的實作,這里咱們就不過多展示異步yield return的編譯后實作了,有興趣的同學可以自行了解一下,

foreach增強

c# 9增加了對foreach的增強的功能,即通過擴展方法的形式,對原本具備包含foreach能力的物件增加GetEnumerator()方法,使得普通類在不具備foreach的能力的情況下也可以使用來迭代,它的使用方式如下

Foo foo = new Foo();
foreach (int item in foo)
{
    Console.WriteLine(item);
}

public class Foo
{
    public List<int> Ints { get; set; } = new List<int>();
}

public static class Bar
{
    //給Foo定義擴展方法
    public static IEnumerator<int> GetEnumerator(this Foo foo)
    {
        foreach (int item in foo.Ints)
        {
            yield return item;
        }
    }
}

這個功能確實比較強大,滿足開放封閉原則,我們可以在不修改原始代碼的情況,增強代碼的功能,可以說是非常的實用,我們來看一下它的編譯后的結果是啥

Foo foo = new Foo();
IEnumerator<int> enumerator = Bar.GetEnumerator(foo);
try
{
    while (enumerator.MoveNext())
    {
        int current = enumerator.Current;
        Console.WriteLine(current);
    }
}
finally
{
    if (enumerator != null)
    {
        enumerator.Dispose();
    }
}

這里我們看到擴展方法GetEnumerator()本質也是語法糖,會把擴展能力編譯成擴展類.GetEnumerator(被擴展實體)的方式,也就是我們寫代碼時候的原始方式,只是編譯器幫我們生成了它的呼叫方式,接下來我們看一下GetEnumerator()擴展方法編譯成了什么

public static IEnumerator<int> GetEnumerator(Foo foo)
{
    <GetEnumerator>d__0 <GetEnumerator>d__ = new <GetEnumerator>d__0(0);
    <GetEnumerator>d__.foo = foo;
    return <GetEnumerator>d__;
}

看到這個代碼是不是覺得很眼熟了,不錯和上面yield return本質這一節里講到的語法糖生成方式是一樣的了,同樣的編譯時候也是生成了一個對應類,這里的類是<GetEnumerator>d__0,我們看一下該類的結構

private sealed class <GetEnumerator>d__0 : IEnumerator<int>, IEnumerator, IDisposable
{
    private int <>1__state;
    private int <>2__current;
    public Foo foo;
    private List<int>.Enumerator <>s__1;
    private int <item>5__2;

    int IEnumerator<int>.Current
    {
        get{ return <>2__current; }
    }

    object IEnumerator.Current
    {
        get{ return <>2__current; }
    }

    public <GetEnumerator>d__0(int <>1__state)
    {
        this.<>1__state = <>1__state;
    }

    private bool MoveNext()
    {
        try
        {
            int num = <>1__state;
            if (num != 0)
            {
                if (num != 1)
                {
                    return false;
                }
                <>1__state = -3;
            }
            else
            {
                <>1__state = -1;
                //因為示例中的Ints我們使用的是List<T>
                <>s__1 = foo.Ints.GetEnumerator();
                <>1__state = -3;
            }
            //因為上面的擴展方法里使用的是foreach遍歷方式
            //這里也被編譯成了實際生產方式
            if (<>s__1.MoveNext())
            {
                <item>5__2 = <>s__1.Current;
                <>2__current = <item>5__2;
                <>1__state = 1;
                return true;
            }
            <>m__Finally1();
            <>s__1 = default(List<int>.Enumerator);
            return false;
        }
        catch
        {
            ((IDisposable)this).Dispose();
            throw;
        }
    }

    bool IEnumerator.MoveNext()
    {
        return this.MoveNext();
    }

    void IDisposable.Dispose()
    {
    }

    void IEnumerator.Reset()
    {
    }

    private void <>m__Finally1()
    {
    }
}

看到編譯器生成的代碼,我們可以看到yield return生成的代碼結構都是一樣的,只是MoveNext()里的邏輯取決于我們寫代碼時候的具體邏輯,不同的邏輯生成不同的代碼,這里咱們就不在講解它生成的代碼了,因為和上面咱們講解的代碼邏輯是差不多的,

總結

    通過本文我們介紹了c#中的yield return語法,并探討了由它帶來的一些思考,我們通過一些簡單的例子,展示了yield return的使用方式,知道了迭代器來是如何按需處理大量資料,同時,我們通過分析foreach迭代和yield return語法的本質,講解了它們的實作原理和底層機制,好在涉及到的知識整體比較簡單,仔細閱讀相關實作代碼的話相信會了解背后的實作原理,這里就不過多贅述了,

    當你遇到挑戰和困難時,請不要輕易放棄,無論你面對的是什么,只要你肯努力去嘗試,去探索,去追求,你一定能夠克服困難,走向成功,記住,成功不是一蹴而就的,它需要我們不斷努力和堅持,相信自己,相信自己的能力,相信自己的潛力,你一定能夠成為更好的自己,

??歡迎掃碼關注我的公眾號??

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

標籤:C#

上一篇:第二單元 常用快捷鍵,注釋,變數

下一篇:返回列表

標籤雲
其他(160059) Python(38189) JavaScript(25466) Java(18161) C(15235) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7219) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5344) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4580) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1950) C++(1927) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1879) .NETCore(1862) 谷歌表格(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# yield return引發的思考

    ### 前言 當我們撰寫 C# 代碼時,經常需要處理大量的資料集合。在傳統的方式中,我們往往需要先將整個資料集合加載到記憶體中,然后再進行操作。但是如果資料集合非常大,這種方式就會導致記憶體占用過高,甚至可能導致程式崩潰。 C# 中的`yield return`機制可以幫助我們解決這個問題。通過使用`y ......

    uj5u.com 2023-05-31 09:55:31 more
  • 第二單元 常用快捷鍵,注釋,變數

    1. 注釋 不寫注釋的程式員都是耍流氓!!名字瞎起是更是土匪!!! 1. 單行文本注釋 static void Main(string[] args) { // 這是單行文本注釋,向控制臺輸出一段字串 Console.WriteLine("Hello World!"); Console.ReadL ......

    uj5u.com 2023-05-30 06:39:57 more
  • 每個.NET開發都應該知道的10個.NET庫

    <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-05-26 15:08:19 more
  • 每個.NET開發都應該知道的10個.NET庫

    <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-05-26 15:07:21 more
  • C# 面向物件

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

    uj5u.com 2023-05-23 10:57:48 more
  • C# 面向物件

    本文介紹了面向物件編程(OOP)的概念及其在編程語言C#中的應用。面向物件編程將系統抽象為物件的集合,每個物件代表系統的特定方面,并提供公共介面供其他代碼呼叫。通過面向物件編程,可以實作代碼的模塊化和重用,提高程式的可讀性和可維護性。文章還簡要介紹了類和物件的概念,類中常用成員的定義及使用方法,以及... ......

    uj5u.com 2023-05-23 10:28:28 more
  • C# 中的“智能列舉”:如何在列舉中增加行為

    目錄 列舉的基本用法回顧 列舉常見的設計模式運用 介紹 智能列舉 代碼示例 業務應用 小結 列舉的基本用法回顧 以下是一個常見的 C# 列舉(enum)的示例: enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday ......

    uj5u.com 2023-05-17 08:07:06 more
  • OData WebAPI實踐-與ABP vNext集成

    <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-05-16 15:55:06 more
  • C#異步方法async/await的三種回傳型別

    <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-05-16 15:48:31 more
  • ABP - 模塊加載機制

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

    uj5u.com 2023-05-16 15:38:00 more