<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C# 對類型系統(tǒng)擴展性的改進

          共 6225字,需瀏覽 13分鐘

           ·

          2022-05-16 12:27


          ?前言

          C# 對類系統(tǒng)進行改進一直都沒有停過,這是一個長期的過程。C# 8 之后則主要圍繞擴展性方面進行各種改進,目前即將發(fā)布的 C# 11 中自然也包含該方面的進度。這些改進當然還沒有做完,本文則介紹一下已經(jīng)推出和即將推出的關(guān)于這方面改進的新特性。

          接口

          我們從最初的最初開始說起。接口(interface)在 C# 的類型系統(tǒng)中是一個非常關(guān)鍵的部分,用來對行為進行抽象,例如可以抽象“能被從字符串解析為整數(shù)”這件事情的接口可以定義為:

          interface?IIntParsable
          {
          ????int?Parse(string?text);
          }

          這樣一切實現(xiàn)了該接口的類型就可以直接轉(zhuǎn)換為?IIntParsable,然后調(diào)用其?Parse?方法把?string?解析成?int,用來根據(jù)字符串來創(chuàng)建整數(shù):

          class?IntFactory?:?IIntParsable
          {
          ????public?int?Parse(string?text)?{?...?}
          }

          但是這樣顯然通用性不夠,如果我們不想創(chuàng)建?int,而是想創(chuàng)建其他類型的實例的話,就需要定義無數(shù)個類型不同而抽象的事情相同的接口,或者將?Parse?的返回值改成?object,這樣就能通用了,但是對值類型會造成裝箱和拆箱導(dǎo)致性能問題,并且調(diào)用方也無法在編譯時知道?Parse?出來的到底是個什么類型的東西。

          泛型接口

          為了解決上面的這一問題,C# 進一步引入了泛型。泛型的引入允許接口定義類型參數(shù),因此對于上面的接口而言,不再需要為不同類型重復(fù)定義接口,而只需要定義一個泛型接口即可:

          interface?IParsable<T>
          {
          ????T?Parse(string?text);
          }

          這樣,當一個類型需要實現(xiàn)?IParsable?時,就可以這么實現(xiàn)了:

          class?IntFactory?:?IParsable<int>
          {
          ????public?int?Parse(string?text)?{?...?}
          }

          由此,我們誕生了各式各樣的工廠,例如上面這個?IntFactory?用來根據(jù)?string?來創(chuàng)建?int。

          基于這些東西,甚至發(fā)展出了一個專門的工廠模式。但是這么做還有一個問題,假如我在接口中添加了一個新的方法?Foo,那么所有實現(xiàn)了這個接口的類型就不得不實現(xiàn)這個新的?Foo,否則會造成編譯失敗。

          接口的方法默認實現(xiàn)

          為了解決上述問題,C# 為接口引入了默認接口實現(xiàn),允許用戶為接口添加默認的方法實現(xiàn)。有了默認實現(xiàn)之后,即使開發(fā)者為一個接口添加了新的方法,只要提供一個默認實現(xiàn),就不會導(dǎo)致類型錯誤而編譯失?。?/span>

          interface?IParsable<T>
          {
          ????T?Parse(string?text);
          ????public?void?Foo()?{?...?}
          }

          這樣一來,IParsable?就有?Foo?方法了。不過要注意的是,這個?Foo?方法不同于?Parse?方法,Foo?如果沒有被實現(xiàn),則不是虛方法,也就是說它的實現(xiàn)在接口上,而不會帶到?jīng)]有實現(xiàn)這個接口的類上。如果不給類實現(xiàn)?Foo?無法調(diào)用的,除非把類型強制轉(zhuǎn)換到接口上:

          class?IntFactory?:?IParsable<int>
          {
          ????public?int?Parse(string?text)?{?...?}
          }

          interface?IParsable<T>
          {
          ????T?Parse(string?text);
          ????public?void?Foo()?{?...?}
          }

          var?parser?=?new?IntFactory();
          parser.Foo();?//?錯誤
          ((IParsable<int>)parser).Foo();?//?沒問題

          接口的靜態(tài)方法默認實現(xiàn)

          既然接口能默認實現(xiàn)方法了,那擴充一下讓接口支持實現(xiàn)靜態(tài)方法也是沒有問題的:

          interface?IParsable<T>
          {
          ????T?Parse(string?text);
          ????public?void?Foo()?{?...?}
          ????public?static?void?Bar()?{?...?}
          }

          不過,接口中的這樣的靜態(tài)方法同樣不是虛方法,只有在接口上才能進行調(diào)用,并且也不能被其他類型實現(xiàn)。跟類中的靜態(tài)方法一樣,想要調(diào)用的時候,只需要:

          IParsable<int>.Bar();

          即可。你可能會好奇這個和多繼承有什么區(qū)別,C# 中接口的默認實現(xiàn)都是非虛的,并且還無法訪問字段和不公開的方法,只當作一個向前兼容的設(shè)施即可,因此不必擔心 C++ 的多繼承問題會出現(xiàn)在 C# 里面。

          接口的虛靜態(tài)方法

          將接口的靜態(tài)方法作為非虛方法顯然有一定的局限性:

          • 只能在接口上調(diào)用靜態(tài)方法,卻不能在實現(xiàn)了接口的類上調(diào)用,實用性不高

          • 類沒法重寫接口靜態(tài)方法的實現(xiàn),進而沒法用來抽象運算符重載和各類工廠方法

          因此,從 C# 10 開始,引入了抽象/虛靜態(tài)方法的概念,允許接口定義抽象靜態(tài)方法;在 C# 11 中則會允許定義虛靜態(tài)方法。這樣一來,之前的?IParsable?的例子中,我們就可以改成:

          interface?IParsable<T>
          {
          ????abstract?static?T?Parse(string?text);
          }

          然后我們可以對該接口進行實現(xiàn):

          struct?Int32?:?IParsable
          {
          ????public?static?int?Parse(string?text)?{?...?}
          }

          如此一來,我們組合泛型約束,誕生了一種全新的設(shè)計模式完全代替了原來需要創(chuàng)建工廠實例的工廠模式:

          T?CreateInstance(string?text)?where?T?:?IParsable
          {
          ????return?T.Parse(text);
          }

          原來需要專門寫一個工廠類型來做的事情,現(xiàn)在只需要一個函數(shù)就能完成同樣甚至更強大的功能,不僅能省掉工廠自身的分配,編寫起來也更加簡單了,并且還能用到運算符上!原本的工廠模式被我們徹底扔進垃圾桶。

          我們還可以將各種接口組合起來應(yīng)用在泛型參數(shù)上,例如我們想編寫一個通用的方法用來計算?a * b + c,但是我們不知道其類型,現(xiàn)在只需要簡單的:

          V?Calculate(T?a,?U?b,?V?c)
          ????where?T?:?IMultiplyOperators
          ????where?U?:?IAdditionOperators
          {
          ????return?a?*?b?+?c;
          }

          其中?IAdditionOperators?和?IMultiplyOperators?都是 .NET 7 自帶的接口,三個類型參數(shù)分別是左操作數(shù)類型、右操作數(shù)類型和返回值類型,并且給所有可以實現(xiàn)的自帶類型都實現(xiàn)了。于是我們調(diào)用的時候只需要簡單的?Calculate(1, 2, 3)?就能得到?5;而如果是?Calculate(1.0, 1.5, 2.0)?則可以得到?3.5

          角色和擴展

          至此,接口自身的演進就已經(jīng)完成了。接下來就是 C# 的下一步計劃:改進類型系統(tǒng)的擴展性。下面的東西預(yù)計會在接下來的幾年(C# 12 或者之后)到來。C# 此前一直是一門面向?qū)ο笳Z言,因此擴展性當然可以通過繼承和多態(tài)來做到,但是這么做有很大的問題:

          • 繼承理論本身的問題:例如根據(jù)繼承原則,正方形類型繼承自長方形,而長方形又繼承自四邊形,但是長方形其實不需要獨立的四邊長度、正方形也不存在長寬的說法,這造成了實現(xiàn)上的冗余和定義上的不準確

          • 對類而言,只有單繼承,沒法將多個父類組合起來繼承到自類上

          • 與值類型不兼容,因為值類型不支持繼承

          • 對接口而言,雖然類型可以實現(xiàn)多個接口,但是如果要為一個類型添加新的接口,則需要修改類型原來的定義,而無法進行擴展

          最初為了支持給類型擴展新的方法,C# 引入了擴展方法功能,滿足了大多數(shù)情況的使用,但是局限性很大:

          • 擴展方法只能是靜態(tài)方法,無法訪問被擴展類型內(nèi)部的私有成員

          • 擴展方法不支持索引器,也不支持屬性,更不支持運算符

          社區(qū)中也一直存在不少意見希望能讓 C# 支持擴展一切,C# 8 的時候官方還實現(xiàn)了這個功能,但是最終在發(fā)布之前砍掉了。

          為什么?因為有了更好和更通用的做法。既然我們已經(jīng)有了以上對接口的改進,我們何必再去給一個局限性很大的擴展方法縫縫補補呢?因此,角色和擴展誕生了。

          在這個模式里,接口將成為核心,同時徹底拋棄了繼承。接口由于自身的特點,在 C# 中也天然成為了 Rust 中?dyn trait?以及 Haskell 中?type class?的等價物。

          注意:以下的東西目前都處于設(shè)計階段,因此下述內(nèi)容只是對目前設(shè)計的介紹,最終的設(shè)計和實現(xiàn)可能會隨著對相關(guān)特性的進一步討論而發(fā)生變化,但是總體方向不會變。

          角色

          一個角色在 C# 中可以采用如下方式定義:

          role?Name?:?UnderlyingType,?Interface,?...?where?T?:?Constraint

          這樣一來,如果我們想給一個已有的類型?Foo?實現(xiàn)一個有著接口?IBar?的角色,我們就可以這么寫:

          role?Bar?:?Foo,?IBar?{?...?}

          這樣我們就創(chuàng)建了一個角色?Bar,這個?Bar?則只實現(xiàn)了?IBar,而不會暴露?Foo?中的其他成員。且不同于繼承,Foo?和?Bar?本質(zhì)上是同一個類型,只是擁有著不同的角色,他們之前可以相互轉(zhuǎn)換。舉一些現(xiàn)實的例子,假設(shè)我們有一個接口?IPerson

          interface?IPerson
          {
          ????int?Id?{?get;?}
          ????string?Name?{?get;?}
          ????int?Age?{?get;?}
          }

          然后我們有一個類型?Data?使用字典存儲了很多數(shù)據(jù),并且?Data?自身具有一個?Id

          class?Data
          {
          ????public?int?Id?{?get;?}
          ????public?Dictionary<string,?string>?Values?{?get;?}?=??...;
          }

          那我們就可以給?Data?創(chuàng)建一個?Person?的角色:

          role?Person?:?Data,?IPerson
          {
          ????public?string?Name?=>?this.Values["name"];
          ????public?int?Age?=>?int.Parse(this.Values["age"]);
          }

          其中,無需實現(xiàn)?Id,因為它已經(jīng)在?Data?中包含了。最終,這個?Person?就是一個只實現(xiàn)了?IPerson?的?Data,它只暴露了?Id、Name?和?Age?屬性,而不會暴露來自?Data?的?Values?屬性。

          以及,它可以被傳到任何接受?Person、Data?或者?IPerson?的地方。我們還可以組合多個接口來創(chuàng)建這樣的角色,例如:

          interface?IHasAge
          {
          ????int?Age?{?get;?}
          }

          interface?IHasName
          {
          ????string?Name?{?get;?}
          }

          role?Person?:?Data,?IHasAge,?IHasName
          {
          ????//?...
          }

          這樣我們把?IPerson?拆成了?IHasAge?和?IHasName?的組合。另外,在不實現(xiàn)接口的情況下,角色也可以用來作為類型的輕量級封裝:

          role?Person?:?Data
          {
          ????public?string?Name?=>?this.Values["name"];
          ????public?int?Age?=>?int.Parse(this.Values["age"]);
          }

          如此一來,Person?將成為一種提供以“人”的方式訪問?Data?的方法的類型??梢哉f,角色就是對同一個“data”的不同的“view”,一個類型的所有角色和它自身都是同樣的類型,在本質(zhì)上和繼承是完全不同的!與其他語言的概念類比的話,角色就等同于 concepts,這也意味著 C# 向 structural typing 邁出了一大步。

          擴展

          有了角色之后,為了解決擴展性的問題,C# 將會引入擴展。有時候我們不想通過角色來訪問一個對象里的東西,我們可以直接在外部擴展已有的類型。

          extension?DataExtension?:?Data
          {
          ????public?string?Name?=>?this.Values["name"];
          ????public?string?ToJson()?{?...?}
          }

          這樣,Data?類型就有了名為?Name?的屬性和?ToJson?的方法,可以直接調(diào)用。除了屬性和方法之外,擴展一個索引器自然也不在話下。

          其中的?ToJson?類似以前的擴展方法,不過如此一來,以前 C# 的擴展方法特性已經(jīng)徹底被新的擴展特性取代,而且是上位替代,功能性和靈活性上遠超原來的擴展方法。我們還可以給類型擴展實現(xiàn)接口:

          extension?DataExtension?:?Data,?IHasName
          {
          ????public?string?Name?=>?this.Values["name"];
          }

          這樣一來,Data?就實現(xiàn)了?IHasName,可以傳遞到任何接受?IHasName?的地方。甚至借助接口的虛靜態(tài)方法和泛型,我們可以給所有的整數(shù)類型擴展一個遍歷器,用來按字節(jié)遍歷底層的表示:

          extension?ByteEnumerator?:?T,?IEnumerable<byte>?where?T?:?unmanaged,?IShiftOperators
          {
          ????public?IEnumerator<byte>?GetEnumerator()
          ????{
          ????????for?(var?i?=?sizeof(T);?i?>?0;?i--)
          ????????{
          ????????????yield?return?unchecked((byte)this?>>?((i?-?1)?*?8));
          ????????}
          ????}
          }

          foreach?(var?b?in?11223344556677L)
          {
          ????Console.WriteLine(b);
          }

          配合接口的靜態(tài)方法,我們甚至能給已有的類型擴展實現(xiàn)運算符!

          extension?MyExtension?:?Foo,?IAdditionOperators
          {
          ????public?static?Foo?operator+(Foo?left,?Foo?right)?{?...?}
          }

          var?foo1?=?new?Foo(...);
          var?foo2?=?new?Foo(...);
          var?result?=?foo1?+?foo2;

          總結(jié)

          C# 從 8 版本開始逐漸開始對接口進行操刀,最終的目的其實就是為了實現(xiàn)角色和擴展,改善類型系統(tǒng)的擴展性。

          到了 C# 11,C# 對接口部分的改造已經(jīng)全部完成,接下來就是角色和擴展了。當然,目前還為時尚早,具體的設(shè)計和實現(xiàn)也可能會變化。最終,借助接口、泛型、角色和擴展,C# 的類型系統(tǒng)將擁有等同于 Haskell 的?type class?那樣的強大表達力和擴展性。

          而且由于是靜態(tài)類型,從頭到尾都不需要擔心任何的類型安全問題。也可以預(yù)想到,隨著這些特性的推出,將會有不少已有的設(shè)計模式因為有了更好的做法而被取代和淘汰。

          轉(zhuǎn)自:hez2010

          鏈接:zhuanlan.zhihu.com/p/507890541

          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操操天天| 国产成人+综合亚洲+天堂 | 亚洲无码高清在线播放 | 欧美激情爱爱网址 | 久草毛片|