整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          c# 10 教程:7 集合

          c# 10 教程:7 集合

          NET 提供了一組用于存儲(chǔ)和管理對(duì)象集合的標(biāo)準(zhǔn)類型。其中包括可調(diào)整大小的列表、鏈表、排序和未排序的字典以及數(shù)組。其中,只有數(shù)組構(gòu)成 C# 語(yǔ)言的一部分;其余集合只是您像其他任何集合一樣實(shí)例化的類。

          我們可以將集合的 .NET BCL 中的類型分為以下:

          • 定義標(biāo)準(zhǔn)收集協(xié)議的接口
          • 即用型集合類(列表、字典等)
          • 用于編寫(xiě)特定于應(yīng)用程序的集合的基類

          本章涵蓋了這些類別中的每一個(gè),并增加了一個(gè)關(guān)于用于確定元素相等性和順序的類型的部分。

          集合命名空間如下所示:

          Namespace

          包含

          系統(tǒng).集合

          非泛型集合類和接口

          系統(tǒng).集合.專業(yè)

          強(qiáng)類型非泛型集合類

          System.Collections.Generic

          泛型集合類和接口

          System.Collections.ObjectModel

          自定義集合的代理和基礎(chǔ)

          System.Collections.Parallel

          線程安全集合(請(qǐng)參閱)

          列舉

          在計(jì)算中,有許多不同類型的集合,從簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)(如數(shù)組或鏈表)到更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(如紅/黑樹(shù)和哈希表)。盡管這些數(shù)據(jù)結(jié)構(gòu)的內(nèi)部實(shí)現(xiàn)和外部差異很大,但遍歷集合內(nèi)容的能力幾乎是普遍的需求。.NET BCL 通過(guò)一對(duì)接口(IEnumerable、IEnumerator 及其泛型對(duì)應(yīng)項(xiàng))支持這一需求,這些接口允許不同的數(shù)據(jù)結(jié)構(gòu)公開(kāi)通用遍歷 API。這些是 中所示的一組更大的集合接口的一部分。


          集合接口

          IEnumerable 和 IEnumerator

          IEnumerator 接口定義基本的低級(jí)協(xié)議,通過(guò)該協(xié)議以只進(jìn)方式遍歷或枚舉集合中的元素。其聲明如下:

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

          MoveNext 將當(dāng)前元素或“光標(biāo)”前進(jìn)到下一個(gè)位置,如果集合中沒(méi)有更多元素,則返回 false。Current 返回當(dāng)前位置的元素(通常從對(duì)象強(qiáng)制轉(zhuǎn)換為更具體的類型)。在檢索第一個(gè)元素之前必須調(diào)用 MoveNext,這是為了允許空集合。Reset 方法(如果實(shí)現(xiàn))將移回起始位置,從而允許再次枚舉集合。重置主要用于組件對(duì)象模型 (COM) 互操作性;通常避免直接調(diào)用它,因?yàn)樗皇瞧毡橹С值模ú⑶沂遣槐匾模驗(yàn)閷?shí)例化新的枚舉器通常同樣容易)。

          集合通常不枚舉器;相反,它們接口 IEnumerable 提供枚舉器:

          public interface IEnumerable
          {
            IEnumerator GetEnumerator();
          }

          通過(guò)定義重新調(diào)整枚舉器的單個(gè)方法,IEnumerable 提供了靈活性,因?yàn)榈壿嬁梢詮?fù)制到另一個(gè)類。此外,這意味著多個(gè)使用者可以一次枚舉集合,而不會(huì)相互干擾。您可以將 IEnumerable 視為“IEnumeratorProvider”,它是集合類實(shí)現(xiàn)的最基本的接口。

          以下示例說(shuō)明了 IEnumerable 和 IEnumerator 的低級(jí)用法:

          string s="Hello";
          
          // Because string implements IEnumerable, we can call GetEnumerator():
          IEnumerator rator=s.GetEnumerator();
          
          while (rator.MoveNext())
          {
            char c=(char) rator.Current;
            Console.Write (c + ".");
          }
          
          // Output:  H.e.l.l.o.

          但是,很少以這種方式直接調(diào)用枚舉器上的方法,因?yàn)?C# 提供了一個(gè)語(yǔ)法快捷方式:foreach 語(yǔ)句。這是使用 foreach 重寫(xiě)的相同示例:

          string s="Hello";      // The String class implements IEnumerable
          
          foreach (char c in s)
            Console.Write (c + ".");

          IEnumerable<T> 和 IEnumerator<T>

          IEnumerator 和 IEnumerable 幾乎總是與其擴(kuò)展泛型版本一起實(shí)現(xiàn):

          public interface IEnumerator<T> : IEnumerator, IDisposable
          {
            T Current { get; }
          }
          
          public interface IEnumerable<T> : IEnumerable
          {
            IEnumerator<T> GetEnumerator();
          }

          通過(guò)定義 Current 和 GetEnumerator 的類型化版本,這些接口增強(qiáng)了靜態(tài)類型安全性,避免了使用值類型元素裝箱的開(kāi)銷,并且對(duì)使用者來(lái)說(shuō)更方便。數(shù)組自動(dòng)實(shí)現(xiàn) IEnumerable<T>(其中 T 是數(shù)組的成員類型)。

          由于改進(jìn)了靜態(tài)類型安全性,使用字符數(shù)組調(diào)用以下方法將生成編譯時(shí)錯(cuò)誤:

          void Test (IEnumerable<int> numbers) { ... }

          集合類的標(biāo)準(zhǔn)做法是公開(kāi) IEnumerable<T>同時(shí)通過(guò)顯式接口實(shí)現(xiàn)“隱藏”非泛型 IEnumerable。這樣,如果您直接調(diào)用 GetEnumerator(),您將獲得類型安全的泛型 IEnumerator<T> 。但是,有時(shí)由于向后兼容性的原因,此規(guī)則會(huì)被破壞(泛型在 C# 2.0 之前不存在)。一個(gè)很好的例子是數(shù)組 - 它們必須返回非泛型(很好的表達(dá)方式是“經(jīng)典”)IEnumerator,以防止破壞早期代碼。要獲取泛型 IEnumerator<T> ,必須強(qiáng)制轉(zhuǎn)換以公開(kāi)顯式接口:

          int[] data={ 1, 2, 3 };
          var rator=((IEnumerable <int>)data).GetEnumerator();

          幸運(yùn)的是,由于 foreach ,您很少需要編寫(xiě)此類代碼。

          IEnumerable<T> 和 IDisposable

          IEnumerator<T> 繼承自 IDisposable 。這允許枚舉器保存對(duì)資源(如數(shù)據(jù)庫(kù)連接)的引用,并確保在枚舉完成(或中途放棄)時(shí)釋放這些資源。foreach 語(yǔ)句可識(shí)別此詳細(xì)信息并翻譯以下內(nèi)容

          foreach (var element in somethingEnumerable) { ... }

          進(jìn)入邏輯等價(jià)物:

          using (var rator=somethingEnumerable.GetEnumerator())
            while (rator.MoveNext())
            {
              var element=rator.Current;
              ...
            }

          何時(shí)使用非泛型接口

          鑒于泛型集合接口(如 IEnumerable<T> 的額外類型安全性,問(wèn)題出現(xiàn)了:您是否需要使用非泛型 IEnumerable(或 ICollection 或 IList)?

          在 IEnumerable 的情況下,您必須將此接口與 IEnumerable<T> 一起實(shí)現(xiàn),因?yàn)楹笳咴醋郧罢摺5牵嬲龔念^開(kāi)始實(shí)現(xiàn)這些接口的情況非常罕見(jiàn):在幾乎所有情況下,都可以采用使用迭代器方法、Collection<T> 和 LINQ 的更高級(jí)別的方法。

          那么,作為消費(fèi)者呢?在幾乎所有情況下,您都可以完全使用通用接口進(jìn)行管理。不過(guò),非泛型接口偶爾仍然很有用,因?yàn)樗鼈兡軌蚩缢性仡愋偷募咸峁╊愋徒y(tǒng)一。例如,以下方法以計(jì)算任何集合中的元素:

          public static int Count (IEnumerable e)
          {
            int count=0;
            foreach (object element in e)
            {
              var subCollection=element as IEnumerable;
              if (subCollection !=null)
                count +=Count (subCollection);
              else
                count++;
            }
            return count;
          }

          由于 C# 提供泛型接口的協(xié)方差,因此讓此方法改為接受 IEnumerable<object> 似乎是有效的。但是,對(duì)于值類型元素和未實(shí)現(xiàn) IEnumerable<T 的舊集合,這將失敗> - 一個(gè)例子是 Windows 窗體中的 ControlCollection。

          (稍微切線,您可能已經(jīng)注意到我們示例中的潛在錯(cuò)誤:引用將導(dǎo)致無(wú)限遞歸并使方法崩潰。我們可以通過(guò)使用 HashSet 最容易地解決這個(gè)問(wèn)題(參見(jiàn))。

          使用塊確保處置 - 有關(guān)IDisposable的更多信息,請(qǐng)參閱。

          實(shí)現(xiàn)枚舉接口

          出于以下一個(gè)或多個(gè)原因,您可能希望實(shí)現(xiàn) IEnumerable 或 IEnumerable<T>:

          • 支持 foreach 語(yǔ)句
          • 與任何期望標(biāo)準(zhǔn)集合的內(nèi)容進(jìn)行互操作
          • 滿足更復(fù)雜的集合接口的要求
          • 支持集合初始值設(shè)定項(xiàng)

          若要實(shí)現(xiàn) IEnumerable/IEnumerable<T> ,必須提供一個(gè)枚舉器。您可以通過(guò)以下三種方式之一執(zhí)行此操作:

          • 如果類正在“包裝”另一個(gè)集合,則通過(guò)返回包裝集合的枚舉器
          • 通過(guò)使用收益回報(bào)的迭代器
          • 通過(guò)實(shí)例化您自己的 IEnumerator / IEnumerator<T> 實(shí)現(xiàn)

          注意

          您還可以對(duì)現(xiàn)有集合進(jìn)行子類化:Collection<T> 就是為此目的而設(shè)計(jì)的(請(qǐng)參閱)。另一種方法是使用 LINQ 查詢運(yùn)算符,我們將在第 中介紹。

          返回另一個(gè)集合的枚舉器只是在內(nèi)部集合上調(diào)用 GetEnumerator 的問(wèn)題。但是,這僅在最簡(jiǎn)單的方案中可行,其中內(nèi)部集合中的項(xiàng)正是必需的。更靈活的方法是使用 C# 的 yield return 語(yǔ)句編寫(xiě)迭代器。是一種 C# 語(yǔ)言功能,可幫助編寫(xiě)集合,其方式與 語(yǔ)句幫助使用集合的方式相同。迭代器自動(dòng)處理 IEnumerable 和 IEnumerator 或其泛型版本的實(shí)現(xiàn)。下面是一個(gè)簡(jiǎn)單的示例:

          public class MyCollection : IEnumerable
          {
            int[] data={ 1, 2, 3 };
          
            public IEnumerator GetEnumerator()
            {
              foreach (int i in data)
                yield return i;
            }
          }

          注意“黑魔法”:GetEnumerator 似乎根本不返回枚舉器!分析yield return 語(yǔ)句后,編譯器在后臺(tái)編寫(xiě)一個(gè)隱藏的嵌套枚舉器類,然后重構(gòu) GetEnumerator 以實(shí)例化并返回該類。迭代器功能強(qiáng)大且簡(jiǎn)單(廣泛用于實(shí)現(xiàn) LINQ-to-Object 的標(biāo)準(zhǔn)查詢運(yùn)算符)。

          按照這種方法,我們還可以實(shí)現(xiàn)通用接口 IEnumerable<T> :

          public class MyGenCollection : IEnumerable<int>
          {
            int[] data={ 1, 2, 3 };
          
            public IEnumerator<int> GetEnumerator()
            {
              foreach (int i in data)
                yield return i;
            }
          
            // Explicit implementation keeps it hidden:
            IEnumerator IEnumerable.GetEnumerator()=> GetEnumerator();
          }

          因?yàn)?IEnumerable<T> 繼承自 IEnumerable,我們必須同時(shí)實(shí)現(xiàn) GetEnumerator 的泛型和非泛型版本。根據(jù),我們已經(jīng)顯式實(shí)現(xiàn)了非通用版本。它可以調(diào)用通用 GetEnumerator,因?yàn)?IEnumerator<T> 繼承自 。

          我們剛剛編寫(xiě)的類適合作為編寫(xiě)更復(fù)雜的集合的基礎(chǔ)。但是,如果我們不需要簡(jiǎn)單的 IEnumerable<T> 實(shí)現(xiàn),則 yield return 語(yǔ)句允許更容易的變化。您可以將迭代邏輯移動(dòng)到返回泛型 IEnumerable<T 的方法中,而不是編寫(xiě)類>讓編譯器處理其余的工作。下面是一個(gè)示例:

          public static IEnumerable <int> GetSomeIntegers()
          {
            yield return 1;
            yield return 2;
            yield return 3;
          }

          這是我們使用的方法:

          foreach (int i in Test.GetSomeIntegers())
            Console.WriteLine (i);

          編寫(xiě) GetEnumerator 的最后一種方法是編寫(xiě)一個(gè)直接實(shí)現(xiàn) IEnumerator 的類。這正是編譯器在后臺(tái)解析迭代器時(shí)所做的。(幸運(yùn)的是,你很少需要自己走這么遠(yuǎn)。下面的示例定義一個(gè)經(jīng)過(guò)硬編碼以包含整數(shù) 1、2 和 3 的集合:

          public class MyIntList : IEnumerable
          {
            int[] data={ 1, 2, 3 };
          
            public IEnumerator GetEnumerator()=> new Enumerator (this);
          
            class Enumerator : IEnumerator       // Define an inner class
            {                                    // for the enumerator.
              MyIntList collection;
              int currentIndex=-1;
          
              public Enumerator (MyIntList items)=> this.collection=items;
          
              public object Current
              {
                get
                {
                  if (currentIndex==-1)
                    throw new InvalidOperationException ("Enumeration not started!");
                  if (currentIndex==collection.data.Length)
                    throw new InvalidOperationException ("Past end of list!");
                  return collection.data [currentIndex];
                }
              }
          
              public bool MoveNext()
              {
                if (currentIndex >=collection.data.Length - 1) return false;
                return ++currentIndex < collection.data.Length;
              }
          
              public void Reset()=> currentIndex=-1;
            }
          }

          注意

          實(shí)現(xiàn)重置是可選的 - 您可以改為拋出 NotSupportedException 。

          請(qǐng)注意,對(duì) MoveNext 的第一次調(diào)用應(yīng)移動(dòng)到列表中的第一個(gè)(而不是第二個(gè))項(xiàng)。

          為了在功能上與迭代器相提并論,我們還必須實(shí)現(xiàn) IEnumerator<T> .下面是一個(gè)示例,為簡(jiǎn)潔起見(jiàn),省略了邊界檢查:

          class MyIntList : IEnumerable<int>
          {
            int[] data={ 1, 2, 3 };
          
            // The generic enumerator is compatible with both IEnumerable and
            // IEnumerable<T>. We implement the nongeneric GetEnumerator method
            // explicitly to avoid a naming conflict.
          
            public IEnumerator<int> GetEnumerator()=> new Enumerator(this);
            IEnumerator IEnumerable.GetEnumerator()=> new Enumerator(this);
          
            class Enumerator : IEnumerator<int>
            {
              int currentIndex=-1;
              MyIntList collection;
          
              public Enumerator (MyIntList items)=> this.items=items;
          
              public int Current=> collection.data [currentIndex];
              object IEnumerator.Current=> Current;
          
              public bool MoveNext()=> ++currentIndex < collection.data.Length;
          
              public void Reset()=> currentIndex=-1;
          
              // Given we don't need a Dispose method, it's good practice to
              // implement it explicitly, so it's hidden from the public interface.
              void IDisposable.Dispose() {}
            }
          }

          泛型示例更快,因?yàn)?IEnumerator<int>。當(dāng)前不需要從 int 轉(zhuǎn)換為對(duì)象,因此避免了裝箱的開(kāi)銷。

          ICollection 和 IList 接口

          盡管枚舉接口為集合的只進(jìn)迭代提供了協(xié)議,但它們不提供確定集合大小、按索引訪問(wèn)成員、搜索或修改集合的機(jī)制。對(duì)于此類功能,.NET 定義了 ICollection 、IList 和 IDictionary 接口。每個(gè)都有通用和非通用版本;但是,非通用版本主要用于舊版支持。

          顯示了這些接口的繼承層次結(jié)構(gòu)。總結(jié)它們的最簡(jiǎn)單方法如下:

          IEnumerable<T> (和 IEnumerable)

          提供最少的功能(僅限枚舉)

          ICollection<T> (和ICollection)

          提供中等功能(例如,Count 屬性)

          IList<T> IDictionary<K,V>及其非通用版本

          提供最大功能(包括按索引/鍵進(jìn)行“隨機(jī)”訪問(wèn))

          注意

          很少需要這些接口中的任何一個(gè)。在幾乎所有需要編寫(xiě)集合類的情況下,都可以改為子類 Collection<T>(請(qǐng)參閱)。LINQ 提供了另一個(gè)涵蓋許多的選項(xiàng)。

          通用和非通用版本在超出預(yù)期的方式上有所不同,尤其是在 ICollection 的情況下。造成這種情況的原因主要是歷史的:因?yàn)榉盒秃髞?lái)出現(xiàn),泛型接口是在事后諸葛亮的情況下開(kāi)發(fā)的,導(dǎo)致了不同的(和更好的)成員選擇。因此,ICollection<T> 不擴(kuò)展 ICollection,IList<T> 不擴(kuò)展 IList < 和 IDictionaryTKey, TValue> 不擴(kuò)展 IDictionary。當(dāng)然,如果有益的話,集合類本身可以自由地實(shí)現(xiàn)接口的兩個(gè)版本(通常是這樣)。

          注意

          IList<T>不擴(kuò)展IList的另一個(gè)更微妙的原因是,強(qiáng)制轉(zhuǎn)換為IList<T>將返回一個(gè)同時(shí)具有Add(T)和Add(object)成員的接口。這將有效地破壞靜態(tài)類型安全性,因?yàn)槟梢允褂萌魏晤愋偷膶?duì)象調(diào)用 Add。

          本節(jié)介紹 ICollection<T> 、IList<T> 及其非通用版本;接口。

          注意

          在整個(gè) .NET 庫(kù)中應(yīng)用單詞和的方式?jīng)]有的基本原理。例如,由于 IList<T> 是 ICollection<T> 的更實(shí)用版本,您可能期望類 List<T> 相應(yīng)地比類 Collection<T> 功能更強(qiáng)大。事實(shí)并非如此。最好將術(shù)語(yǔ)和視為廣義同義詞,除非涉及特定類型。

          ICollection<T> 和 ICollection

          ICollection<T> 是可數(shù)對(duì)象集合的標(biāo)準(zhǔn)接口。它提供了確定集合大小(Count)、確定集合中是否存在項(xiàng)(包含)、將集合復(fù)制到數(shù)組(ToArray)以及確定集合是否為只讀(IsReadOnly)的功能。對(duì)于可寫(xiě)集合,還可以從集合中添加、刪除和清除項(xiàng)目。并且因?yàn)樗鼣U(kuò)展了 IEnumerable<T> ,它也可以通過(guò) foreach 遍歷:

          public interface ICollection<T> : IEnumerable<T>, IEnumerable
          {
            int Count { get; }
          
            bool Contains (T item);
            void CopyTo (T[] array, int arrayIndex);
            bool IsReadOnly { get; }
          
            void Add(T item);
            bool Remove (T item);
            void Clear();
          }

          非泛型 ICollection 在提供可計(jì)數(shù)集合方面類似,但它不提供更改列表或檢查元素的功能:

          public interface ICollection : IEnumerable
          {
             int Count { get; }
             bool IsSynchronized { get; }
             object SyncRoot { get; }
             void CopyTo (Array array, int index);
          }

          非泛型接口還定義了幫助同步的屬性()— 這些屬性在泛型版本中被轉(zhuǎn)儲(chǔ),因?yàn)榫€程安全不再被視為集合的固有屬性。

          這兩個(gè)接口都相當(dāng)容易實(shí)現(xiàn)。如果實(shí)現(xiàn)只讀 ICollection<T> ,則 添加、刪除 和 Clear 方法應(yīng)引發(fā) NotSupportedException 。

          這些接口通常與 IList 或 IDictionary 接口一起實(shí)現(xiàn)。

          IList<T> 和 IList

          IList<T> 是可按位置索引的集合的標(biāo)準(zhǔn)接口。除了繼承自ICollection<T>和IEnumerable<T>的功能外,它還提供了按位置(通過(guò)索引器)讀取或?qū)懭朐匾约鞍次恢貌迦?刪除元素的功能:

          public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
          {
            T this [int index] { get; set; }
            int IndexOf (T item);
            void Insert (int index, T item);
            void RemoveAt (int index);
          }

          IndexOf 方法對(duì)列表執(zhí)行線性搜索,如果未找到指定的項(xiàng),則返回 ?1。

          IList 的非泛型版本具有更多成員,因?yàn)樗鼜?ICollection 繼承較少:

          public interface IList : ICollection, IEnumerable
          {
            object this [int index] { get; set }
            bool IsFixedSize { get; }
            bool IsReadOnly  { get; }
            int  Add      (object value);
            void Clear();
            bool Contains (object value);
            int  IndexOf  (object value);
            void Insert   (int index, object value);
            void Remove   (object value);
            void RemoveAt (int index);
          }

          非泛型 IList 接口上的 Add 方法返回一個(gè)整數(shù),這是新添加項(xiàng)的索引。相比之下,ICollection<T> 上的 Add 方法具有 void 返回類型。

          通用 List<T> 類是 IList<T> 和 IList 的典型實(shí)現(xiàn)。C# 數(shù)組還同時(shí)實(shí)現(xiàn)泛型和非泛型 IList (盡管添加或刪除元素的方法通過(guò)顯式接口實(shí)現(xiàn)隱藏,并在調(diào)用時(shí)引發(fā) NotSupportedException)。

          警告

          如果您嘗試通過(guò) IList 的索引器訪問(wèn)多維數(shù)組,則會(huì)引發(fā) ArgumentException。在編寫(xiě)如下方法時(shí),這是一個(gè)陷阱:

          public object FirstOrNull (IList list)
          {
            if (list==null || list.Count==0) return null;
            return list[0];
          }

          這可能看起來(lái)很無(wú)懈可擊,但如果使用多維數(shù)組調(diào)用,它將引發(fā)異常。您可以使用此表達(dá)式在運(yùn)行時(shí)測(cè)試多維數(shù)組(詳見(jiàn)):

          list.GetType().IsArray && list.GetType().GetArrayRank()>1

          IReadOnlyCollection<T> 和 IReadOnlyList<T>

          .NET 還定義了集合和列表接口,這些接口僅公開(kāi)只讀操作所需的成員:

          public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
          {
            int Count { get; }
          }
          
          public interface IReadOnlyList<out T> : IReadOnlyCollection<T>,
                                                  IEnumerable<T>, IEnumerable
          {
            T this[int index] { get; }
          }

          由于這些接口的類型參數(shù)僅用于輸出位置,因此將其標(biāo)記為。例如,這允許將貓列表視為只讀的動(dòng)物列表。相反,T 沒(méi)有被標(biāo)記為與 ICollection<T> 和 IList<T> 的協(xié)變,因?yàn)?T 同時(shí)用于輸入和輸出位置。

          注意

          這些接口表示集合或列表的只讀;基礎(chǔ)實(shí)現(xiàn)可能仍然是可寫(xiě)的。大多數(shù)可寫(xiě)()集合同時(shí)實(shí)現(xiàn)只讀和讀/寫(xiě)接口。

          除了允許您協(xié)變地使用集合之外,只讀接口還允許類公開(kāi)私有可寫(xiě)集合的只讀視圖。我們?cè)凇敝醒菔玖诉@一點(diǎn),以及更好的解決方案。

          IReadOnlyList<T>映射到Windows運(yùn)行時(shí)類型IVectorView<T>。

          數(shù)組類

          Array 類是所有單維和多維數(shù)組的隱式基類,它是實(shí)現(xiàn)標(biāo)準(zhǔn)集合接口的最基本類型之一。Array 類提供類型統(tǒng)一,因此一組通用方法可用于所有數(shù)組,無(wú)論其聲明或基礎(chǔ)元素類型如何。

          由于數(shù)組是如此基本,因此 C# 為它們的聲明和初始化提供了顯式語(yǔ)法,我們?cè)诘?章和第 章中對(duì)此進(jìn)行了介紹。使用 C# 的語(yǔ)法聲明數(shù)組時(shí),CLR 會(huì)隱式子類型 Array 類,從而合成適合數(shù)組維度和元素類型的。此偽類型實(shí)現(xiàn)類型化泛型集合接口,如 IList<string> 。

          CLR 還在構(gòu)造時(shí)專門(mén)處理數(shù)組類型,在內(nèi)存中為它們分配一個(gè)連續(xù)的空間。這使得索引到數(shù)組中非常高效,但可以防止以后調(diào)整它們的大小。

          Array 以泛型和非泛型形式實(shí)現(xiàn) IList<T> 的集合接口。IList<T>本身是顯式實(shí)現(xiàn)的,但是,為了使數(shù)組的公共接口中沒(méi)有諸如Add或Move之類的方法,這些方法會(huì)在固定長(zhǎng)度的集合(如數(shù)組)上引發(fā)異常。Array 類實(shí)際上提供了一個(gè)靜態(tài) Resize 方法,盡管這種方法的工作原理是創(chuàng)建一個(gè)新數(shù)組,然后復(fù)制每個(gè)元素。除了效率低下之外,程序中其他位置對(duì)數(shù)組的引用仍將指向原始版本。對(duì)于可調(diào)整大小的集合,更好的解決方案是使用 List<T> 類(在下一節(jié)中介紹)。

          數(shù)組可以包含值類型或引用類型元素。值類型元素存儲(chǔ)在數(shù)組中,因此三個(gè)長(zhǎng)整數(shù)(每個(gè) 8 個(gè)字節(jié))的數(shù)組將占用 24 個(gè)字節(jié)的連續(xù)內(nèi)存。但是,引用類型元素在數(shù)組中占用的空間僅與引用一樣多(4 位環(huán)境中為 32 個(gè)字節(jié),8 位環(huán)境中為 64 個(gè)字節(jié))。 說(shuō)明了以下程序在內(nèi)存中的效果:

          StringBuilder[] builders=new StringBuilder [5];
          builders [0]=new StringBuilder ("builder1");
          builders [1]=new StringBuilder ("builder2");
          builders [2]=new StringBuilder ("builder3");
          
          long[] numbers=new long [3];
          numbers [0]=12345;
          numbers [1]=54321;



          內(nèi)存中的數(shù)組

          因?yàn)?Array 是一個(gè)類,所以數(shù)組始終是(本身)引用類型,而不管數(shù)組的元素類型如何。這意味著語(yǔ)句 arrayB=arrayA 會(huì)產(chǎn)生兩個(gè)引用同一數(shù)組的變量。同樣,兩個(gè)不同的數(shù)組總是無(wú)法通過(guò)相等性測(cè)試,除非您使用來(lái)比較數(shù)組的每個(gè)元素:

          object[] a1={ "string", 123, true };
          object[] a2={ "string", 123, true };
          
          Console.WriteLine (a1==a2);                          // False
          Console.WriteLine (a1.Equals (a2));                    // False
          
          IStructuralEquatable se1=a1;
          Console.WriteLine (se1.Equals (a2,
           StructuralComparisons.StructuralEqualityComparer));   // True

          數(shù)組可以通過(guò)調(diào)用 Clone 方法復(fù)制:arrayB=arrayA.Clone() 。但是,這會(huì)導(dǎo)致淺層克隆,這意味著僅復(fù)制數(shù)組本身表示的內(nèi)存。如果數(shù)組包含值類型對(duì)象,則復(fù)制值本身;如果數(shù)組包含引用類型對(duì)象,則僅復(fù)制引用(導(dǎo)致兩個(gè)數(shù)組的成員引用相同的對(duì)象)。 演示了將以下代碼添加到示例中的效果:

          StringBuilder[] builders2=builders;
          StringBuilder[] shallowClone=(StringBuilder[]) builders.Clone();


          淺拷貝陣列

          要?jiǎng)?chuàng)建深層副本(為其復(fù)制引用類型子對(duì)象),必須遍歷數(shù)組并手動(dòng)克隆每個(gè)元素。相同的規(guī)則適用于其他 .NET 集合類型。

          盡管 Array 主要設(shè)計(jì)用于 32 位索引器,但它對(duì) 64 位索引器的支持也有限(允許數(shù)組理論上最多尋址 264 elements) via several methods that accept both Int32 and Int64 parameters. These overloads are useless in practice because the CLR does not permit any object—including arrays—to exceed two gigabytes in size (whether running on a 32- or 64-bit environment).

          注意

          Array 類上許多期望作為實(shí)例方法的方法實(shí)際上是靜態(tài)方法。這是一個(gè)奇怪的設(shè)計(jì)決策,這意味著在 Array 上查找方法時(shí),您應(yīng)該同時(shí)檢查靜態(tài)方法和實(shí)例方法。

          構(gòu)造和索引

          創(chuàng)建和索引數(shù)組的最簡(jiǎn)單方法是通過(guò) C# 的語(yǔ)言構(gòu)造:

          int[] myArray={ 1, 2, 3 };
          int first=myArray [0];
          int last=myArray [myArray.Length - 1];

          或者,您可以通過(guò)調(diào)用 Array.CreateInstance 來(lái)動(dòng)態(tài)實(shí)例化數(shù)組。這允許您在運(yùn)行時(shí)指定元素類型和秩(維數(shù)),以及通過(guò)指定下限來(lái)允許非零數(shù)組。非零數(shù)組與 .NET 公共語(yǔ)言規(guī)范 (CLS) 不兼容,不應(yīng)作為庫(kù)中的公共成員公開(kāi),而庫(kù)中可能由用 F# 或 Visual Basic 編寫(xiě)的程序使用。

          GetValue 和 SetValue 方法允許您訪問(wèn)動(dòng)態(tài)創(chuàng)建的數(shù)組中的元素(它們也適用于普通數(shù)組):

           // Create a string array 2 elements in length:
           Array a=Array.CreateInstance (typeof(string), 2);
           a.SetValue ("hi", 0);                             //  → a[0]="hi";
           a.SetValue ("there", 1);                          //  → a[1]="there";
           string s=(string) a.GetValue (0);               //  → s=a[0];
          
           // We can also cast to a C# array as follows:
           string[] cSharpArray=(string[]) a;
           string s2=cSharpArray [0];

          動(dòng)態(tài)創(chuàng)建的零索引數(shù)組可以強(qiáng)制轉(zhuǎn)換為匹配或兼容類型(與標(biāo)準(zhǔn)數(shù)組方差規(guī)則兼容)的 C# 數(shù)組。例如,如果 Apple 子類 Fruit ,則可以將 Apple[] 轉(zhuǎn)換為 Fruit[]。這就引出了為什么 object[] 沒(méi)有用作統(tǒng)一數(shù)組類型而不是 Array 類的問(wèn)題。答案是 object[] 與多維和值類型數(shù)組(以及非基于零的數(shù)組)都不兼容。int[] 數(shù)組不能強(qiáng)制轉(zhuǎn)換為對(duì)象 []。因此,我們需要 Array 類來(lái)實(shí)現(xiàn)完整類型統(tǒng)一。

          GetValue 和 SetValue 也適用于編譯器創(chuàng)建的數(shù)組,它們?cè)诰帉?xiě)可以處理任何類型和等級(jí)的數(shù)組的方法時(shí)很有用。對(duì)于多維數(shù)組,它們接受索引器:

          public object GetValue (params int[] indices)
          public void   SetValue (object value, params int[] indices)

          以下方法打印任何數(shù)組的第一個(gè)元素,而不考慮等級(jí):

           void WriteFirstValue (Array a)
           {
             Console.Write (a.Rank + "-dimensional; ");
          
             // The indexers array will automatically initialize to all zeros, so
             // passing it into GetValue or SetValue will get/set the zero-based
             // (i.e., first) element in the array.
          
             int[] indexers=new int[a.Rank];
             Console.WriteLine ("First value is " +  a.GetValue (indexers));
           }
          
           void Demo()
           {
             int[]  oneD={ 1, 2, 3 };
             int[,] twoD={ {5,6}, {8,9} };
          
             WriteFirstValue (oneD);   // 1-dimensional; first value is 1
             WriteFirstValue (twoD);   // 2-dimensional; first value is 5
           }

          注意

          對(duì)于使用未知類型但已知等級(jí)的數(shù)組,泛型提供了一種更簡(jiǎn)單、更有效的解決方案:

          void WriteFirstValue<T> (T[] array)
          {
            Console.WriteLine (array[0]);
          }

          如果元素的類型與數(shù)組不兼容,則 SetValue 將引發(fā)異常。

          當(dāng)一個(gè)數(shù)組被實(shí)例化時(shí),無(wú)論是通過(guò)語(yǔ)言語(yǔ)法還是Array.CreateInstance,它的元素都會(huì)被自動(dòng)初始化。對(duì)于具有引用類型元素的數(shù)組,這意味著寫(xiě)入 null;對(duì)于具有值類型元素的數(shù)組,這意味著調(diào)用值類型的默認(rèn)構(gòu)造函數(shù)(有效地將成員“清零”)。Array 類還通過(guò) Clear 方法按需提供此功能:

          public static void Clear (Array array, int index, int length);

          此方法不會(huì)影響數(shù)組的大小。這與通常使用Clear(例如在ICollection<T>中)形成鮮明對(duì)比。清除),從而集合減少到零元素。

          列舉

          數(shù)組很容易用 foreach 語(yǔ)句枚舉:

          int[] myArray={ 1, 2, 3};
          foreach (int val in myArray)
            Console.WriteLine (val);

          還可以使用靜態(tài) Array.ForEach 方法進(jìn)行枚舉,該方法定義如下:

          public static void ForEach<T> (T[] array, Action<T> action);

          這將使用具有以下簽名的操作委托:

          public delegate void Action<T> (T obj);

          下面是用 Array.ForEach 重寫(xiě)的第一個(gè)示例:

          Array.ForEach (new[] { 1, 2, 3 }, Console.WriteLine);

          長(zhǎng)度和等級(jí)

          數(shù)組提供以下用于查詢長(zhǎng)度和秩的方法和屬性:

          public int  GetLength      (int dimension);
          public long GetLongLength  (int dimension);
          
          public int  Length       { get; }
          public long LongLength   { get; }
          
          public int GetLowerBound (int dimension);
          public int GetUpperBound (int dimension);
          
          public int Rank { get; }    // Returns number of dimensions in array

          GetLength 和 GetLongLength 返回給定維度的長(zhǎng)度(一維數(shù)組為 0),Length 和 LongLength 返回?cái)?shù)組中元素的總數(shù) — 包括所有維度。

          GetLowerBound 和 GetUpperBound 對(duì)于非零索引數(shù)組很有用。對(duì)于任何給定維度,GetUpperBound 返回的結(jié)果與將 GetLowerBound 添加到 GetLength 的結(jié)果相同。

          搜索

          Array 類提供了一系列用于在一維數(shù)組中查找元素的方法:

          二進(jìn)制搜索方法

          用于快速搜索特定項(xiàng)目的排序數(shù)組

          索引/上一個(gè)索引方法

          用于搜索特定項(xiàng)目的未排序數(shù)組

          Find / FindLast / FindIndex / FindLastIndex / FindAll / Exists / TrueForAll

          用于在未排序數(shù)組中搜索滿足給定謂詞<T的項(xiàng)目>

          如果未找到指定的值,則任何數(shù)組搜索方法都不會(huì)引發(fā)異常。相反,如果未找到項(xiàng)目,則返回整數(shù)的方法返回 ?1(假設(shè)為零索引數(shù)組),返回泛型類型的方法返回類型的默認(rèn)值(例如,0 表示 int,或 null 表示字符串)。

          二叉搜索方法很快,但它們僅適用于排序數(shù)組,并且要求比較元素的,而不僅僅是。為此,二叉搜索方法可以接受 IComparer 或 IComparer<T> 對(duì)象對(duì)排序決策進(jìn)行仲裁(參見(jiàn))。這必須與最初對(duì)數(shù)組進(jìn)行排序時(shí)使用的任何比較器一致。如果未提供比較器,則類型的默認(rèn)排序算法將根據(jù)其實(shí)現(xiàn) IComparable/IComparable<T> 應(yīng)用。

          IndexOf 和 LastIndexOf 方法對(duì)數(shù)組執(zhí)行簡(jiǎn)單的枚舉,返回與給定值匹配的第一個(gè)(或最后一個(gè))元素的位置。

          基于謂詞的搜索方法允許方法委托或 lambda 表達(dá)式對(duì)給定元素是否為“匹配”進(jìn)行仲裁。謂詞只是一個(gè)接受對(duì)象并返回 true 或 false 的委托:

          public delegate bool Predicate<T> (T object);

          在下面的示例中,我們?cè)谧址當(dāng)?shù)組中搜索包含字母“a”的名稱:

          string[] names={ "Rodney", "Jack", "Jill" };
          string match=Array.Find (names, ContainsA);
          Console.WriteLine (match);     // Jack
          
          ContainsA (string name) { return name.Contains ("a"); }

          下面是使用 lambda 表達(dá)式縮短的相同代碼:

          string[] names={ "Rodney", "Jack", "Jill" };
          string match=Array.Find (names, n=> n.Contains ("a"));     // Jack

          FindAll 返回滿足謂詞的所有項(xiàng)的數(shù)組。事實(shí)上,它等效于 System.Linq 命名空間中的 Enumerable.Where,只是 FindAll 返回匹配項(xiàng)的數(shù)組,而不是相同的 IEnumerable<T>。

          如果任何數(shù)組成員滿足給定謂詞,則 Exists 返回 true,并且等效于 System.Linq.Enumerable 中的 Any 。

          如果所有項(xiàng)都滿足謂詞,則 TrueForAll 返回 true,并且等效于 System.Linq.Enumerable 中的 All。

          排序

          數(shù)組具有以下內(nèi)置排序方法:

          // For sorting a single array:
          
          public static void Sort<T> (T[] array);
          public static void Sort    (Array array);
          
          // For sorting a pair of arrays:
          
          public static void Sort<TKey,TValue> (TKey[] keys, TValue[] items);
          public static void Sort              (Array keys, Array items);

          這些方法中的每一個(gè)都還重載以接受以下內(nèi)容:

          int index                 // Starting index at which to begin sorting
          int length                // Number of elements to sort
          IComparer<T> comparer     // Object making ordering decisions
          Comparison<T> comparison  // Delegate making ordering decisions

          下面說(shuō)明了排序的最簡(jiǎn)單用法:

          int[] numbers={ 3, 2, 1 };
          Array.Sort (numbers);                     // Array is now { 1, 2, 3 }

          接受一對(duì)數(shù)組的方法通過(guò)重新排列每個(gè)數(shù)組的項(xiàng)目來(lái)工作,將排序決策基于第一個(gè)數(shù)組。在下一個(gè)示例中,數(shù)字及其相應(yīng)的單詞都按數(shù)字順序排序:

          int[] numbers={ 3, 2, 1 };
          string[] words={ "three", "two", "one" };
          Array.Sort (numbers, words);
          
          // numbers array is now { 1, 2, 3 }
          // words   array is now { "one", "two", "three" }

          Array.Sort要求數(shù)組中的元素實(shí)現(xiàn)IComparable(參見(jiàn)中的)。這意味著可以對(duì)大多數(shù)內(nèi)置 C# 類型(如前面示例中的整數(shù))進(jìn)行排序。如果元素在本質(zhì)上不具有可比性,或者您希望覆蓋默認(rèn)排序,則必須為 Sort 提供用于報(bào)告兩個(gè)元素的相對(duì)位置的自定義比較提供程序。有一些方法可以做到這一點(diǎn):

          • 通過(guò)實(shí)現(xiàn) IComparer / IComparer<T>的幫助器對(duì)象(請(qǐng)參閱)
          • 通過(guò)比較委托:
          • public delegate int Comparison<T> (T x, T y);

          比較委托遵循與 IComparer<T> 相同的語(yǔ)義。比較:如果 x 在 y 之前,則返回一個(gè)負(fù)整數(shù);如果 x 在 y 之后,則返回一個(gè)正整數(shù);如果 x 和 y 具有相同的排序位置,則返回 0。

          在下面的示例中,我們對(duì)整數(shù)數(shù)組進(jìn)行排序,使奇數(shù)排在第一位:

          int[] numbers={ 1, 2, 3, 4, 5 };
          Array.Sort (numbers, (x, y)=> x % 2==y % 2 ? 0 : x % 2==1 ? -1 : 1);
          
          // numbers array is now { 1, 3, 5, 2, 4 }

          注意

          作為調(diào)用 Sort 的替代方法,您可以使用 LINQ 的 OrderBy 和 ThenBy 運(yùn)算符。與 Array.Sort 不同,LINQ 運(yùn)算符不會(huì)更改原始數(shù)組,而是以新的 IEnumerable<T> 序列發(fā)出排序結(jié)果。

          反轉(zhuǎn)元素

          以下 Array 方法反轉(zhuǎn)數(shù)組中所有(或部分)元素的順序:

          public static void Reverse (Array array);
          public static void Reverse (Array array, int index, int length);

          復(fù)制

          Array 提供了四種執(zhí)行淺拷貝的方法:克隆、復(fù)制到、拷貝和約束拷貝。前兩個(gè)是實(shí)例方法;后兩個(gè)是靜態(tài)方法。

          Clone 方法返回一個(gè)全新的(淺拷貝)數(shù)組。CopyTo 和 Copy 方法復(fù)制數(shù)組的連續(xù)子集。復(fù)制多維矩形數(shù)組需要將多維索引映射到線性索引。例如,1 × 1 數(shù)組中的中間正方形(位置 [3,3]) 用索引 4 表示,計(jì)算結(jié)果為 :1 * 3 + 1。源范圍和目標(biāo)范圍可以重疊而不會(huì)引起問(wèn)題。

          ConscuredCopy 執(zhí)行操作:如果無(wú)法成功復(fù)制所有請(qǐng)求的元素(例如,由于類型錯(cuò)誤),則操作將回滾。

          Array 還提供了一個(gè) AsReadOnly 方法,該方法返回一個(gè)包裝器,以防止元素被重新分配。

          轉(zhuǎn)換和調(diào)整大小

          Array.ConvertAll創(chuàng)建并返回一個(gè)元素類型為T(mén)Output的新數(shù)組,調(diào)用提供的轉(zhuǎn)換器委托來(lái)復(fù)制元素。轉(zhuǎn)換器定義如下:

          public delegate TOutput Converter<TInput,TOutput> (TInput input)

          下面將浮點(diǎn)數(shù)數(shù)組轉(zhuǎn)換為整數(shù)數(shù)組:

          float[] reals={ 1.3f, 1.5f, 1.8f };
          int[] wholes=Array.ConvertAll (reals, r=> Convert.ToInt32 (r));
          
          // wholes array is { 1, 2, 2 }

          Resize 方法的工作原理是創(chuàng)建一個(gè)新數(shù)組并復(fù)制元素,通過(guò)引用參數(shù)返回新數(shù)組。但是,其他對(duì)象中對(duì)原始數(shù)組的任何引用將保持不變。

          注意

          System.Linq 命名空間提供了適用于數(shù)組轉(zhuǎn)換的擴(kuò)展方法的附加自助餐。這些方法返回一個(gè)IEnumerable<T>,你可以通過(guò)Enumerable的ToArray方法將其轉(zhuǎn)換回?cái)?shù)組。

          列表、隊(duì)列、堆棧和集

          .NET 提供了一組基本的具體集合類,用于實(shí)現(xiàn)本章中所述的接口。本節(jié)重點(diǎn)介紹類似列表的集合(相對(duì)于我們?cè)谥薪榻B的集合)。與我們之前討論的接口一樣,您通常可以選擇每種類型的泛型或非泛型版本。在靈活性和性能方面,泛型類勝出,使其非泛型類除了向后兼容性外是多余的。這與集合接口的情況不同,對(duì)于集合接口,非泛型版本偶爾仍然有用。

          在本節(jié)中描述的類中,泛型 List 類是最常用的。

          List<T> 和 ArrayList

          泛型 List 類和非泛型 ArrayList 類提供動(dòng)態(tài)大小的對(duì)象數(shù)組,是最常用的集合類之一。ArrayList 實(shí)現(xiàn)了 IList ,而 List<T> 同時(shí)實(shí)現(xiàn)了 IList 和 IList<T>(以及只讀版本,IReadOnlyList<T> )。與數(shù)組不同,所有接口都是公開(kāi)實(shí)現(xiàn)的,并且 Add 和 Remove 等方法公開(kāi)并按預(yù)期工作。

          在內(nèi)部,List<T> 和 ArrayList 通過(guò)維護(hù)一個(gè)內(nèi)部對(duì)象數(shù)組來(lái)工作,在達(dá)到容量時(shí)替換為更大的數(shù)組。附加元素是有效的(因?yàn)槟┪餐ǔS幸粋€(gè)空閑插槽),但插入元素可能很慢(因?yàn)椴迦朦c(diǎn)之后的所有元素都必須移動(dòng)才能形成空閑插槽),刪除元素(尤其是在開(kāi)始附近)也是如此。與數(shù)組一樣,如果對(duì)已排序的列表使用 BinarySearch 方法,則搜索是有效的,但在其他方面效率低下,因?yàn)楸仨殕为?dú)檢查每個(gè)項(xiàng)目。

          注意

          如果 T 是值類型,則 List<T> 比 ArrayList 快幾倍,因?yàn)?List<T> 避免了裝箱和取消裝箱元素的開(kāi)銷。

          List<T> 和 ArrayList 提供了接受現(xiàn)有元素集合的構(gòu)造函數(shù):這些構(gòu)造函數(shù)將現(xiàn)有集合中的每個(gè)元素復(fù)制到新的 List<T> 或 ArrayList 中:

          public class List<T> : IList<T>, IReadOnlyList<T>
          {
            public List ();
            public List (IEnumerable<T> collection);
            public List (int capacity);
          
            // Add+Insert
            public void Add         (T item);
            public void AddRange    (IEnumerable<T> collection);
            public void Insert      (int index, T item);
            public void InsertRange (int index, IEnumerable<T> collection);
          
            // Remove
            public bool Remove      (T item);
            public void RemoveAt    (int index);
            public void RemoveRange (int index, int count);
            public int  RemoveAll   (Predicate<T> match);
          
            // Indexing
            public T this [int index] { get; set; }
            public List<T> GetRange (int index, int count);
            public Enumerator<T> GetEnumerator();
          
            // Exporting, copying and converting:
            public T[] ToArray();
            public void CopyTo (T[] array);
            public void CopyTo (T[] array, int arrayIndex);
            public void CopyTo (int index, T[] array, int arrayIndex, int count);
            public ReadOnlyCollection<T> AsReadOnly();
            public List<TOutput> ConvertAll<TOutput> (Converter <T,TOutput>
                                                      converter);
            // Other:
            public void Reverse();            // Reverses order of elements in list.
            public int Capacity { get;set; }  // Forces expansion of internal array.
            public void TrimExcess();         // Trims internal array back to size.
            public void Clear();              // Removes all elements, so Count=0.
          }
          
          public delegate TOutput Converter <TInput, TOutput> (TInput input);

          除了這些成員之外,List<T>還提供了所有Array搜索和排序方法的實(shí)例版本。

          下面的代碼演示了 List 的屬性和方法(有關(guān)搜索和排序的示例,請(qǐng)參閱):

          var words=new List<string>();    // New string-typed list
          
          words.Add ("melon");
          words.Add ("avocado");
          words.AddRange (new[] { "banana", "plum" } );
          words.Insert (0, "lemon");                           // Insert at start
          words.InsertRange (0, new[] { "peach", "nashi" });   // Insert at start
          
          words.Remove ("melon");
          words.RemoveAt (3);                         // Remove the 4th element
          words.RemoveRange (0, 2);                   // Remove first 2 elements
          
          // Remove all strings starting in 'n':
          words.RemoveAll (s=> s.StartsWith ("n"));
          
          Console.WriteLine (words [0]);                          // first word
          Console.WriteLine (words [words.Count - 1]);            // last word
          foreach (string s in words) Console.WriteLine (s);      // all words
          List<string> subset=words.GetRange (1, 2);            // 2nd->3rd words
          
          string[] wordsArray=words.ToArray();    // Creates a new typed array
          
          // Copy first two elements to the end of an existing array:
          string[] existing=new string [1000];
          words.CopyTo (0, existing, 998, 2);
          
          List<string> upperCaseWords=words.ConvertAll (s=> s.ToUpper());
          List<int> lengths=words.ConvertAll (s=> s.Length);

          非泛型 ArrayList 類需要笨拙的強(qiáng)制轉(zhuǎn)換,如以下示例所示:

          ArrayList al=new ArrayList();
          al.Add ("hello");
          string first=(string) al [0];
          string[] strArr=(string[]) al.ToArray (typeof (string));

          編譯器無(wú)法驗(yàn)證此類強(qiáng)制轉(zhuǎn)換;以下內(nèi)容編譯成功,但在運(yùn)行時(shí)失敗:

          int first=(int) al [0];    // Runtime exception

          注意

          ArrayList 在功能上類似于 List<object> 。當(dāng)您需要共享不共享公共基類型(對(duì)象除外)的混合類型元素列表時(shí),兩者都很有用。在這種情況下,選擇ArrayList的一個(gè)可能的優(yōu)點(diǎn)是,如果您需要使用反射來(lái)處理列表()。使用非泛型 ArrayList 進(jìn)行反射比使用 List<object> 更容易。

          如果導(dǎo)入 System.Linq 命名空間,則可以通過(guò)調(diào)用 Cast 然后調(diào)用 ToList 將 ArrayList 轉(zhuǎn)換為泛型列表:

          ArrayList al=new ArrayList();
          al.AddRange (new[] { 1, 5, 9 } );
          List<int> list=al.Cast<int>().ToList();

          Cast 和 ToList 是 System.Linq.Enumerable 類中的擴(kuò)展方法。

          鏈接列表<T>

          LinkedList<T> 是一個(gè)通用的雙向鏈表(見(jiàn))。雙向鏈表是節(jié)點(diǎn)鏈,其中每個(gè)節(jié)點(diǎn)引用前面的節(jié)點(diǎn)、之后的節(jié)點(diǎn)和實(shí)際的元素。它的主要優(yōu)點(diǎn)是元素始終可以有效地插入列表中的任何位置,因?yàn)樗簧婕皠?chuàng)建一個(gè)新節(jié)點(diǎn)并更新一些引用。但是,首先查找將節(jié)點(diǎn)插入的位置可能會(huì)很慢,因?yàn)闆](méi)有直接索引到鏈表中的內(nèi)在機(jī)制;必須遍歷每個(gè)節(jié)點(diǎn),并且無(wú)法進(jìn)行二進(jìn)制切碎搜索。



          LinkedList<T> 實(shí)現(xiàn)了 IEnumerable<T> 和 ICollection<T>(及其非通用版本),但不是 IList<T>因?yàn)椴恢С职此饕L問(wèn)。列表節(jié)點(diǎn)通過(guò)以下類實(shí)現(xiàn):

          public sealed class LinkedListNode<T>
          {
            public LinkedList<T> List { get; }
            public LinkedListNode<T> Next { get; }
            public LinkedListNode<T> Previous { get; }
            public T Value { get; set; }
          }

          添加節(jié)點(diǎn)時(shí),可以指定其相對(duì)于另一個(gè)節(jié)點(diǎn)的位置,也可以指定其在列表的開(kāi)頭/結(jié)尾的位置。LinkedList<T>為此提供了以下方法:

          public void AddFirst(LinkedListNode<T> node);
          public LinkedListNode<T> AddFirst (T value);
          
          public void AddLast (LinkedListNode<T> node);
          public LinkedListNode<T> AddLast (T value);
          
          public void AddAfter (LinkedListNode<T> node, LinkedListNode<T> newNode);
          public LinkedListNode<T> AddAfter (LinkedListNode<T> node, T value);
          
          public void AddBefore (LinkedListNode<T> node, LinkedListNode<T> newNode);
          public LinkedListNode<T> AddBefore (LinkedListNode<T> node, T value);

          提供了類似的方法來(lái)刪除元素:

          public void Clear();
          
          public void RemoveFirst();
          public void RemoveLast();
          
          public bool Remove (T value);
          public void Remove (LinkedListNode<T> node);

          LinkedList<T>具有內(nèi)部字段來(lái)跟蹤列表中的元素?cái)?shù)量以及列表的頭部和尾部。這些屬性在以下公共屬性中公開(kāi):

          public int Count { get; }                      // Fast
          public LinkedListNode<T> First { get; }        // Fast
          public LinkedListNode<T> Last { get; }         // Fast

          LinkedList<T> 還支持以下搜索方法(每種方法都需要在內(nèi)部枚舉列表):

          public bool Contains (T value);
          public LinkedListNode<T> Find (T value);
          public LinkedListNode<T> FindLast (T value);

          最后,LinkedList<T> 支持復(fù)制到數(shù)組進(jìn)行索引處理,并獲取一個(gè)枚舉器來(lái)支持 foreach 語(yǔ)句:

          public void CopyTo (T[] array, int index);
          public Enumerator<T> GetEnumerator();

          以下是使用 LinkedList<string> 的演示:

          var tune=new LinkedList<string>();
          tune.AddFirst ("do");                           // do
          tune.AddLast ("so");                            // do - so
          
          tune.AddAfter (tune.First, "re");               // do - re- so
          tune.AddAfter (tune.First.Next, "mi");          // do - re - mi- so
          tune.AddBefore (tune.Last, "fa");               // do - re - mi - fa- so
          
          tune.RemoveFirst();                             // re - mi - fa - so
          tune.RemoveLast();                              // re - mi - fa
          
          LinkedListNode<string> miNode=tune.Find ("mi");
          tune.Remove (miNode);                           // re - fa
          tune.AddFirst (miNode);                         // mi- re - fa
          
          foreach (string s in tune) Console.WriteLine (s);

          隊(duì)列<T>和隊(duì)列

          隊(duì)列<T> 和隊(duì)列是先進(jìn)先出 (FIFO) 數(shù)據(jù)結(jié)構(gòu),提供排隊(duì)(將項(xiàng)目添加到隊(duì)列尾部)和取消排隊(duì)(檢索并刪除隊(duì)列頭部的項(xiàng)目)的方法。還提供了 Peek 方法,用于返回隊(duì)列頭部的元素而不刪除它,以及一個(gè) Count 屬性(在出列之前檢查元素是否存在很有用)。

          雖然隊(duì)列是可枚舉的,但它們不實(shí)現(xiàn) IList<T> / IList ,因?yàn)槌蓡T不能通過(guò)索引直接訪問(wèn)。但是,提供了一個(gè) ToArray 方法,用于將元素復(fù)制到可以從中隨機(jī)訪問(wèn)它們的數(shù)組:

          public class Queue<T> : IEnumerable<T>, ICollection, IEnumerable
          {
            public Queue();
            public Queue (IEnumerable<T> collection);   // Copies existing elements
            public Queue (int capacity);                // To lessen auto-resizing
            public void Clear();
            public bool Contains (T item);
            public void CopyTo (T[] array, int arrayIndex);
            public int Count { get; }
            public T Dequeue();
            public void Enqueue (T item);
            public Enumerator<T> GetEnumerator();       // To support foreach
            public T Peek();
            public T[] ToArray();
            public void TrimExcess();
          }

          以下是使用 Queue<int> 的示例:

          var q=new Queue<int>();
          q.Enqueue (10);
          q.Enqueue (20);
          int[] data=q.ToArray();         // Exports to an array
          Console.WriteLine (q.Count);      // "2"
          Console.WriteLine (q.Peek());     // "10"
          Console.WriteLine (q.Dequeue());  // "10"
          Console.WriteLine (q.Dequeue());  // "20"
          Console.WriteLine (q.Dequeue());  // throws an exception (queue empty)

          隊(duì)列是使用根據(jù)需要調(diào)整大小的數(shù)組在內(nèi)部實(shí)現(xiàn)的,與泛型 List 類非常相似。隊(duì)列維護(hù)直接指向頭和尾元素的索引;因此,排隊(duì)和取消排隊(duì)是非常快速的操作(除非需要內(nèi)部調(diào)整大小)。

          堆疊<T>和堆疊

          堆棧<T> 和堆棧是后進(jìn)先出 (LIFO) 數(shù)據(jù)結(jié)構(gòu),提供推送(將項(xiàng)目添加到堆棧頂部)和 Pop(從堆棧頂部檢索和刪除元素)的方法。還提供了非破壞性 Peek 方法,以及用于導(dǎo)出數(shù)據(jù)以進(jìn)行隨機(jī)訪問(wèn)的 Count 屬性和 ToArray 方法:

          public class Stack<T> : IEnumerable<T>, ICollection, IEnumerable
          {
            public Stack();
            public Stack (IEnumerable<T> collection);   // Copies existing elements
            public Stack (int capacity);                // Lessens auto-resizing
            public void Clear();
            public bool Contains (T item);
            public void CopyTo (T[] array, int arrayIndex);
            public int Count { get; }
            public Enumerator<T> GetEnumerator();       // To support foreach
            public T Peek();
            public T Pop();
            public void Push (T item);
            public T[] ToArray();
            public void TrimExcess();
          }

          以下示例演示了 Stack<int> :

          var s=new Stack<int>();
          s.Push (1);                      //            Stack=1
          s.Push (2);                      //            Stack=1,2
          s.Push (3);                      //            Stack=1,2,3
          Console.WriteLine (s.Count);     // Prints 3
          Console.WriteLine (s.Peek());    // Prints 3,  Stack=1,2,3
          Console.WriteLine (s.Pop());     // Prints 3,  Stack=1,2
          Console.WriteLine (s.Pop());     // Prints 2,  Stack=1
          Console.WriteLine (s.Pop());     // Prints 1,  Stack=<empty>
          Console.WriteLine (s.Pop());     // throws exception

          堆棧在內(nèi)部使用根據(jù)需要調(diào)整大小的數(shù)組實(shí)現(xiàn),如 Queue<T> 和 List<T> 。

          位數(shù)組

          位數(shù)組是壓縮布爾值的動(dòng)態(tài)大小集合。它比簡(jiǎn)單的布爾數(shù)組和通用布爾列表的內(nèi)存效率更高,因?yàn)樗鼘?duì)每個(gè)值只使用一個(gè)位,而布爾類型則為每個(gè)值占用一個(gè)字節(jié)。

          BitArray 的索引器讀取和寫(xiě)入單個(gè)位:

          var bits=new BitArray(2);
          bits[1]=true;

          有四種按位運(yùn)算符方法(和、或、異或、和不是)。除了最后一個(gè)之外,所有都接受另一個(gè)位數(shù)組:

          bits.Xor (bits);               // Bitwise exclusive-OR bits with itself
          Console.WriteLine (bits[1]);   // False

          HashSet<T> 和 SortedSet<T>

          HashSet<T> 和 SortedSet<T>具有以下顯著特征:

          • 它們的 Contains 方法使用基于哈希的查找快速執(zhí)行。
          • 它們不存儲(chǔ)重復(fù)的元素,并以靜默方式忽略添加的請(qǐng)求。
          • 不能按位置訪問(wèn)元素。

          SortedSet<T> 使元素保持有序,而 HashSet<T> 則不然。

          HashSet<T> 和 SortedSet<T> 類型的通用性由接口 ISet<T> 捕獲。從 .NET 5 開(kāi)始,這些類還實(shí)現(xiàn)了一個(gè)名為 IReadOnlySet<T> 的接口,該接口也由不可變集類型實(shí)現(xiàn)(請(qǐng)參閱)。

          HashSet<T> 是用一個(gè)只存儲(chǔ)鍵的哈希表實(shí)現(xiàn)的;SortedSet<T> 是用紅/黑樹(shù)實(shí)現(xiàn)的。

          這兩個(gè)集合都實(shí)現(xiàn)了 ICollection<T> 并提供您期望的方法,例如 包含 、 添加 和 刪除 。此外,還有一種基于謂詞的刪除方法,稱為 刪除位置 .

          下面從現(xiàn)有集合構(gòu)造 HashSet<char>,測(cè)試成員資格,然后枚舉集合(請(qǐng)注意沒(méi)有重復(fù)項(xiàng)):

          var letters=new HashSet<char> ("the quick brown fox");
          
          Console.WriteLine (letters.Contains ('t'));      // true
          Console.WriteLine (letters.Contains ('j'));      // false
          
          foreach (char c in letters) Console.Write (c);   // the quickbrownfx

          (我們可以將字符串傳遞到 HashSet<char> 的構(gòu)造函數(shù)中的原因是該字符串實(shí)現(xiàn)了 IEnumerable<char>。

          真正有趣的方法是集合操作。以下集合操作具有,因?yàn)樗鼈儠?huì)修改集合

          public void UnionWith           (IEnumerable<T> other);   // Adds
          public void IntersectWith       (IEnumerable<T> other);   // Removes
          public void ExceptWith          (IEnumerable<T> other);   // Removes
          public void SymmetricExceptWith (IEnumerable<T> other);   // Removes

          而以下方法只是查詢集合,因此是非破壞性的:

          public bool IsSubsetOf         (IEnumerable<T> other);
          public bool IsProperSubsetOf   (IEnumerable<T> other);
          public bool IsSupersetOf       (IEnumerable<T> other);
          public bool IsProperSupersetOf (IEnumerable<T> other);
          public bool Overlaps           (IEnumerable<T> other);
          public bool SetEquals          (IEnumerable<T> other);

          UnionWith 將第二個(gè)集合中的所有元素添加到原始集合中(不包括重復(fù)項(xiàng))。相交刪除不在兩個(gè)集中的元素。我們可以從我們的字符集中提取所有元音,如下所示:

          var letters=new HashSet<char> ("the quick brown fox");
          letters.IntersectWith ("aeiou");
          foreach (char c in letters) Console.Write (c);     // euio

          ExceptWith 從源代碼集中刪除指定的元素。在這里,我們從集合中剝離所有元音:

          var letters=new HashSet<char> ("the quick brown fox");
          letters.ExceptWith ("aeiou");
          foreach (char c in letters) Console.Write (c);     // th qckbrwnfx

          SymmetricExceptWith 刪除除一個(gè)集合或另一個(gè)集合所特有的元素之外的所有元素:

          var letters=new HashSet<char> ("the quick brown fox");
          letters.SymmetricExceptWith ("the lazy brown fox");
          foreach (char c in letters) Console.Write (c);     // quicklazy

          請(qǐng)注意,由于 HashSet<T> 和 SortedSet<T> 實(shí)現(xiàn)了 IEnumerable<T>,因此您可以使用其他類型的集合(或集合)作為任何集合操作方法的參數(shù)。

          SortedSet<T> 提供 HashSet<T> 的所有成員,以及以下內(nèi)容:

          public virtual SortedSet<T> GetViewBetween (T lowerValue, T upperValue)
          public IEnumerable<T> Reverse()
          public T Min { get; }
          public T Max { get; }

          SortedSet<T> 還在其構(gòu)造函數(shù)中接受可選的 IComparer<T>(而不是)。

          下面是將相同字母加載到 SortedSet<char 中的示例>:

          var letters=new SortedSet<char> ("the quick brown fox");
          foreach (char c in letters) Console.Write (c);   //  bcefhiknoqrtuwx

          在此之后,我們可以獲得 和 之間的集合中的:

          foreach (char c in letters.GetViewBetween ('f', 'i'))
            Console.Write (c);                                    //  fhi

          字典

          是一個(gè)集合,其中每個(gè)元素都是一個(gè)鍵/值對(duì)。字典最常用于查找和排序列表。

          .NET 通過(guò)接口 IDictionary 和 IDictionary<TKey、TValue 以及一組通用字典類為字典定義了一個(gè)標(biāo)準(zhǔn)協(xié)議>。每個(gè)類在以下方面有所不同:

          • 項(xiàng)目是否按排序順序存儲(chǔ)
          • 是否可以按位置(索引)和鍵訪問(wèn)項(xiàng)目
          • 無(wú)論是通用還是非通用
          • 從大型字典中按鍵檢索項(xiàng)目是快還是慢

          總結(jié)了每個(gè)字典類以及它們?cè)谶@些方面的區(qū)別。性能時(shí)間以毫秒為單位,基于在 50.000 GHz PC 上使用整數(shù)鍵和值的字典執(zhí)行 1,5 次操作。(使用相同基礎(chǔ)集合結(jié)構(gòu)的泛型和非泛型對(duì)應(yīng)項(xiàng)之間的性能差異是由于裝箱造成的,并且僅顯示值類型元素。

          字典類

          類型

          內(nèi)部結(jié)構(gòu)

          按索引檢索?

          內(nèi)存開(kāi)銷(每個(gè)項(xiàng)目的平均字節(jié)數(shù))

          速度:隨機(jī)插入

          速度:順序插入

          速度:按鍵檢索

          排序







          字典 <K,V>

          哈希表

          22

          30

          30

          20

          哈希表

          哈希表

          38

          50

          50

          30

          列表字典

          鏈表

          36

          50,000

          50,000

          50,000

          有序詞典

          哈希表 + 數(shù)組

          是的

          59

          70

          70

          40

          排序







          排序詞典 <K,V>

          紅/黑樹(shù)

          20

          130

          100

          120

          排序列表 <K,V>

          2x陣列

          是的

          2

          3,300

          30

          40

          排序列表

          2x陣列

          是的

          27

          4,500

          100

          180

          在 Big-O 表示法中,按鍵檢索時(shí)間如下:

          • O(1) 表示哈希表、字典和有序字典
          • O(log ) 表示 SortedDictionary 和 SortedList
          • O() 表示 ListDictionary(以及非字典類型,如 List<T>)

          是集合中的元素?cái)?shù)。

          IDictionary<TKey,TValue>

          IDictionary<TKey,TValue> 為所有基于鍵/值的集合定義了標(biāo)準(zhǔn)協(xié)議。它通過(guò)添加方法和屬性來(lái)訪問(wèn)基于任意類型的鍵的元素,從而擴(kuò)展了ICollection<T>:

          public interface IDictionary <TKey, TValue> :
            ICollection <KeyValuePair <TKey, TValue>>, IEnumerable
          {
             bool ContainsKey (TKey key);
             bool TryGetValue (TKey key, out TValue value);
             void Add         (TKey key, TValue value);
             bool Remove      (TKey key);
          
             TValue this [TKey key]      { get; set; }  // Main indexer - by key
             ICollection <TKey> Keys     { get; }       // Returns just keys
             ICollection <TValue> Values { get; }       // Returns just values
          }

          注意

          還有一個(gè)名為IReadOnlyDictionary<TKey,TValue>的接口,它定義了字典成員的只讀子集。

          若要將項(xiàng)添加到字典,請(qǐng)調(diào)用 Add 或使用索引的 set 訪問(wèn)器 - 如果鍵尚不存在,后者會(huì)將項(xiàng)添加到字典中(如果存在,則更新該項(xiàng))。所有字典實(shí)現(xiàn)中都禁止重復(fù)鍵,因此使用相同的鍵調(diào)用 Add 兩次會(huì)引發(fā)異常。

          若要從字典中檢索項(xiàng),請(qǐng)使用索引器或 TryGetValue 方法。如果鍵不存在,索引器將引發(fā)異常,而 TryGetValue 返回 false 。您可以通過(guò)調(diào)用 包含密鑰 來(lái)顯式測(cè)試成員資格;但是,如果您隨后檢索項(xiàng)目,則會(huì)產(chǎn)生兩次查找的費(fèi)用。

          直接在 IDictionary<TKey,TValue> 上枚舉返回一系列 KeyValuePair 結(jié)構(gòu):

          public struct KeyValuePair <TKey, TValue>
          {
            public TKey Key     { get; }
            public TValue Value { get; }
          }

          您可以通過(guò)字典的鍵/值屬性僅枚舉鍵或值。

          我們將在下一節(jié)中演示如何將此接口與泛型 Dictionary 類一起使用。

          目錄

          非通用IDictionary接口在原則上與IDictionary<TKey,TValue>相同,除了兩個(gè)重要的功能差異。了解這些差異非常重要,因?yàn)?IDictionary 出現(xiàn)在舊代碼中(包括 .NET BCL 本身的某些地方):

          • 通過(guò)索引器檢索不存在的鍵將返回 null(而不是引發(fā)異常)。
          • 包含成員資格測(cè)試,而不是包含密鑰。

          枚舉非泛型 IDictionary 將返回一系列 DictionaryEntry 結(jié)構(gòu):

          public struct DictionaryEntry
          {
            public object Key   { get; set; }
            public object Value { get; set; }
          }

          字典< TKey,TValue>和哈希表

          泛型字典類是最常用的集合之一(與 List<T> 集合一起)。它使用哈希表數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)鍵和值,并且快速高效。

          注意

          Dictionary<TKey,TValue>的非通用版本稱為Hashtable ;沒(méi)有稱為字典的非泛型類。當(dāng)我們簡(jiǎn)單地提到字典時(shí),我們指的是通用的字典<TKey,TValue>類。

          字典實(shí)現(xiàn)了通用和非通用 IDictionary 接口,通用 IDictionary 公開(kāi)。詞典其實(shí)是通用詞典的“教科書(shū)”實(shí)現(xiàn)。

          以下是使用它的方法:

          var d=new Dictionary<string, int>();
          
          d.Add("One", 1);
          d["Two"]=2;     // adds to dictionary because "two" not already present
          d["Two"]=22;    // updates dictionary because "two" is now present
          d["Three"]=3;
          
          Console.WriteLine (d["Two"]);                // Prints "22"
          Console.WriteLine (d.ContainsKey ("One"));   // true (fast operation)
          Console.WriteLine (d.ContainsValue (3));     // true (slow operation)
          int val=0;
          if (!d.TryGetValue ("onE", out val))
            Console.WriteLine ("No val");              // "No val" (case sensitive)
          
          // Three different ways to enumerate the dictionary:
          
          foreach (KeyValuePair<string, int> kv in d)          //  One; 1
            Console.WriteLine (kv.Key + "; " + kv.Value);      //  Two; 22
                                                               //  Three; 3
          
          foreach (string s in d.Keys) Console.Write (s);      // OneTwoThree
          Console.WriteLine();
          foreach (int i in d.Values) Console.Write (i);       // 1223

          其基礎(chǔ)哈希表的工作原理是將每個(gè)元素的鍵轉(zhuǎn)換為整數(shù)哈希代碼(偽唯一值),然后應(yīng)用算法將哈希代碼轉(zhuǎn)換為哈希鍵。此哈希鍵在內(nèi)部用于確定條目屬于哪個(gè)“存儲(chǔ)桶”。如果存儲(chǔ)桶包含多個(gè)值,則會(huì)對(duì)存儲(chǔ)桶執(zhí)行線性搜索。一個(gè)好的哈希函數(shù)不會(huì)努力返回嚴(yán)格唯一的哈希碼(這通常是不可能的);它努力返回均勻分布在 32 位整數(shù)空間中的哈希碼。這可以防止最終得到幾個(gè)非常大(且效率低下)的存儲(chǔ)桶的情況。

          字典可以處理任何類型的鍵,前提是它能夠確定鍵之間的相等性并獲取哈希碼。默認(rèn)情況下,相等性是通過(guò)鍵的對(duì)象確定的。等于方法,偽唯一哈希代碼是通過(guò)密鑰的 GetHashCode 方法獲取的。可以通過(guò)重寫(xiě)這些方法或在構(gòu)造字典時(shí)提供 IEqualityComparer 對(duì)象來(lái)更改此行為。這樣做的一個(gè)常見(jiàn)應(yīng)用是在使用字符串鍵時(shí)指定不區(qū)分大小寫(xiě)的相等比較器:

          var d=new Dictionary<string, int> (StringComparer.OrdinalIgnoreCase);

          我們將在中進(jìn)一步討論這個(gè)問(wèn)題。

          與許多其他類型的集合一樣,可以通過(guò)在構(gòu)造函數(shù)中指定集合的預(yù)期大小來(lái)稍微提高字典的性能,從而避免或減少對(duì)內(nèi)部調(diào)整大小操作的需求。

          非泛型版本名為 Hashtable,除了由于它公開(kāi)前面討論的非泛型 IDictionary 接口而產(chǎn)生的差異之外,它在功能上是相似的。

          字典和哈希表的缺點(diǎn)是項(xiàng)目未排序。此外,不保留添加項(xiàng)目的原始順序。與所有字典一樣,不允許使用重復(fù)的鍵。

          注意

          當(dāng)泛型集合在2005年被引入時(shí),CLR團(tuán)隊(duì)選擇根據(jù)它們所代表的內(nèi)容(字典,列表)而不是它們內(nèi)部實(shí)現(xiàn)的方式(Hashtable,ArrayList)來(lái)命名它們。雖然這很好,因?yàn)樗o了他們以后更改實(shí)現(xiàn)的自由,但這也意味著(通常是選擇一種集合而不是另一種集合的最重要標(biāo)準(zhǔn))不再包含在名稱中。

          有序詞典

          OrderedDictionary 是一種非泛型字典,它以與添加元素相同的順序維護(hù)元素。使用 OrderedDictionary ,您可以按索引和鍵訪問(wèn)元素。

          注意

          排序字典不是字典。

          OrderedDictionary是Hashtable和ArrayList的組合。這意味著它具有哈希表的所有功能,以及諸如 和整數(shù)索引器之類的函數(shù)。它還公開(kāi)按原始順序返回元素的鍵和值屬性。

          此類是在 .NET 2.0 中引入的,但奇怪的是,沒(méi)有泛型版本。

          列表詞典和混合詞典

          ListDictionary使用單向鏈表來(lái)存儲(chǔ)基礎(chǔ)數(shù)據(jù)。它不提供排序,盡管它保留了項(xiàng)目的原始輸入順序。ListDictionary 對(duì)于大型列表來(lái)說(shuō)非常慢。它唯一真正的“名聲”是它對(duì)非常小的列表(少于 10 個(gè)項(xiàng)目)的效率。

          HybridDictionary是一個(gè)ListDictionary,在達(dá)到一定大小時(shí)會(huì)自動(dòng)轉(zhuǎn)換為哈希表,以解決ListDictionary的性能問(wèn)題。這個(gè)想法是在字典較小時(shí)獲得低內(nèi)存占用,在字典較大時(shí)獲得良好的性能。但是,考慮到從一個(gè)詞典轉(zhuǎn)換到另一個(gè)詞典的開(kāi)銷,以及詞典在這兩種情況下都不會(huì)過(guò)重或過(guò)慢的事實(shí),一開(kāi)始使用都不會(huì)受到不合理的影響。

          這兩個(gè)類都僅以非泛型形式出現(xiàn)。

          排序詞典

          .NET BCL 提供兩個(gè)內(nèi)部結(jié)構(gòu)化的字典類,以便其內(nèi)容始終按鍵排序:

          • 排序詞典<TKey,TValue>
          • 排序列表<噶吱吱>吱??1

          (在本節(jié)中,我們將<TKey,TValue>縮寫(xiě)為<,>。

          SortedDictionary<,> 使用紅/黑樹(shù):一種數(shù)據(jù)結(jié)構(gòu),旨在在任何插入或檢索場(chǎng)景中始終如一地執(zhí)行良好性能。

          SortedList<,> 在內(nèi)部使用有序數(shù)組對(duì)實(shí)現(xiàn),提供快速檢索(通過(guò)二進(jìn)制切碎搜索),但插入性能較差(因?yàn)樾枰苿?dòng)現(xiàn)有值以便為新條目騰出空間)。

          SortedDictionary<,> 在隨機(jī)序列中插入元素(尤其是大型列表)方面比 SortedList<,> 快得多。但是,SortedList<,> 具有額外的功能:按索引和鍵訪問(wèn)項(xiàng)目。使用排序列表,可以直接轉(zhuǎn)到排序序列中的個(gè)元素(通過(guò)鍵/值屬性上的索引器)。若要對(duì) SortedDictionary<,> 執(zhí)行相同的操作,必須手動(dòng)枚舉 個(gè)項(xiàng)目。(或者,您可以編寫(xiě)一個(gè)將排序字典與列表類組合在一起的類。

          這三個(gè)集合中沒(méi)有一個(gè)允許重復(fù)鍵(就像所有一樣)。

          下面的示例使用反射將 System.Object 中定義的所有方法加載到按名稱鍵的排序列表中,然后枚舉它們的鍵和值:

          // MethodInfo is in the System.Reflection namespace
          
          var sorted=new SortedList <string, MethodInfo>();
          
          foreach (MethodInfo m in typeof (object).GetMethods())
            sorted [m.Name]=m;
          
          foreach (string name in sorted.Keys)
            Console.WriteLine (name);
          
          foreach (MethodInfo m in sorted.Values)
            Console.WriteLine (m.Name + " returns a " + m.ReturnType);

          下面是第一個(gè)枚舉的結(jié)果:

          Equals
          GetHashCode
          GetType
          ReferenceEquals
          ToString

          下面是第二個(gè)枚舉的結(jié)果:

          Equals returns a System.Boolean
          GetHashCode returns a System.Int32
          GetType returns a System.Type
          ReferenceEquals returns a System.Boolean
          ToString returns a System.String

          請(qǐng)注意,我們通過(guò)其索引器填充了字典。如果我們改用 Add 方法,它將引發(fā)異常,因?yàn)槲覀兎从车膶?duì)象類重載 Equals 方法,并且您不能將相同的鍵添加到字典中兩次。通過(guò)使用索引器,后面的條目將覆蓋前面的條目,從而防止此錯(cuò)誤。

          注意

          您可以通過(guò)將每個(gè)值元素設(shè)置為列表來(lái)存儲(chǔ)同一鍵的多個(gè)成員:

          SortedList <string, List<MethodInfo>>

          擴(kuò)展我們的示例,下面檢索鍵為“GetHashCode”的 MethodInfo ,就像普通字典一樣:

          Console.WriteLine (sorted ["GetHashCode"]);      // Int32 GetHashCode()

          到目前為止,我們所做的一切都可以使用SortedDictionary<,>。但是,以下兩行檢索最后一個(gè)鍵和值,僅適用于排序列表:

          Console.WriteLine (sorted.Keys  [sorted.Count - 1]);            // ToString
          Console.WriteLine (sorted.Values[sorted.Count - 1].IsVirtual);  // True

          可定制的集合和代理

          前面各節(jié)中討論的集合類很方便,因?yàn)槟梢灾苯訉?shí)例化它們,但它們不允許您控制在集合中添加或刪除項(xiàng)時(shí)發(fā)生的情況。對(duì)于應(yīng)用程序中的強(qiáng)類型集合,有時(shí)需要此控件。例如:

          • 在添加或刪除項(xiàng)目時(shí)觸發(fā)事件
          • 由于添加或刪除的項(xiàng)而更新屬性
          • 檢測(cè)“非法”添加/刪除操作并引發(fā)異常(例如,如果操作違反業(yè)務(wù)規(guī)則)

          .NET BCL 在 System.Collections.ObjectModel 命名空間中為此提供了集合類。這些本質(zhì)上是代理或包裝器,通過(guò)將方法轉(zhuǎn)發(fā)到基礎(chǔ)集合來(lái)實(shí)現(xiàn) IList<T> 或 IDictionary<,>。每個(gè)“添加”、“刪除”或“清除”操作都通過(guò)一個(gè)虛擬方法進(jìn)行路由,該方法在被覆蓋時(shí)充當(dāng)“網(wǎng)關(guān)”。

          可自定義的集合類通常用于公開(kāi)的集合;例如,在 System.Windows.Form 類上公開(kāi)的控件集合。

          Collection<T> 和 CollectionBase

          Collection<T> 類是 List<T> 的可自定義包裝器。

          除了實(shí)現(xiàn) IList<T> 和 IList 之外,它還定義了四個(gè)額外的虛擬方法和一個(gè)受保護(hù)的屬性,如下所示:

          public class Collection<T> :
            IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable
          {
             // ...
          
             protected virtual void ClearItems();
             protected virtual void InsertItem (int index, T item);
             protected virtual void RemoveItem (int index);
             protected virtual void SetItem (int index, T item);
          
             protected IList<T> Items { get; }
          }

          虛擬方法提供了一個(gè)網(wǎng)關(guān),您可以通過(guò)該網(wǎng)關(guān)“掛鉤”來(lái)更改或增強(qiáng)列表的正常行為。受保護(hù)的 Items 屬性允許實(shí)現(xiàn)者直接訪問(wèn)“內(nèi)部列表”,該列表用于在內(nèi)部進(jìn)行更改,而無(wú)需觸發(fā)虛擬方法。

          虛擬方法不需要被覆蓋;在需要更改列表的默認(rèn)行為之前,可以將它們單獨(dú)保留。以下示例演示了 Collection<T 的典型“框架”用法>:

          Zoo zoo=new Zoo();
          zoo.Animals.Add (new Animal ("Kangaroo", 10));
          zoo.Animals.Add (new Animal ("Mr Sea Lion", 20));
          foreach (Animal a in zoo.Animals) Console.WriteLine (a.Name);
          
          public class Animal
          {
            public string Name;
            public int Popularity;
          
            public Animal (string name, int popularity)
            {
              Name=name; Popularity=popularity;
            }
          }
          
          public class AnimalCollection : Collection <Animal>
          {
            // AnimalCollection is already a fully functioning list of animals.
            // No extra code is required.
          }
          
          public class Zoo   // The class that will expose AnimalCollection.
          {                  // This would typically have additional members.
          
            public readonly AnimalCollection Animals=new AnimalCollection();
          }

          就目前而言,動(dòng)物收藏并不比一個(gè)簡(jiǎn)單的列表更實(shí)用<動(dòng)物> ;它的作用是為將來(lái)的擴(kuò)展提供基礎(chǔ)。為了說(shuō)明這一點(diǎn),現(xiàn)在讓我們將一個(gè) Zoo 屬性添加到 Animal,以便它可以引用它所在的 Zoo 并重寫(xiě) Collection<Animal 中的每個(gè)虛擬方法>以維護(hù)該屬性:

          public class Animal
          {
            public string Name;
            public int Popularity;
            public Zoo Zoo { get; internal set; }
            public Animal(string name, int popularity)
            {
              Name=name; Popularity=popularity;
            }
          }
          
          public class AnimalCollection : Collection <Animal>
          {
            Zoo zoo;
            public AnimalCollection (Zoo zoo) { this.zoo=zoo; }
          
            protected override void InsertItem (int index, Animal item)
            {
              base.InsertItem (index, item);
              item.Zoo=zoo;
            }
            protected override void SetItem (int index, Animal item)
            {
              base.SetItem (index, item);
              item.Zoo=zoo;
            }
            protected override void RemoveItem (int index)
            {
              this [index].Zoo=null;
              base.RemoveItem (index);
            }
            protected override void ClearItems()
            {
              foreach (Animal a in this) a.Zoo=null;
              base.ClearItems();
            }
          }
          
          public class Zoo
          {
            public readonly AnimalCollection Animals;
            public Zoo() { Animals=new AnimalCollection (this); }
          }

          Collection<T> 也有一個(gè)接受現(xiàn)有 IList<T> 的構(gòu)造函數(shù)。與其他集合類不同,提供的列表是而不是的,這意味著后續(xù)更改將反映在包裝 Collection<T> 中(盡管觸發(fā) Collection<T> 的虛擬方法)。相反,通過(guò)集合<T>所做的更改將更改基礎(chǔ)列表。

          收藏基地

          CollectionBase 是 Collection<T> 的非通用版本。這提供了與 Collection<T 相同的大多數(shù)功能>但使用起來(lái)更笨拙。與模板方法InsertItem 、RemoveItem 、SetItem 和 ClearItem 不同,CollectionBase 具有“鉤子”方法,使所需方法的數(shù)量增加了一倍:OnInsert 、OnInsertComplete 、OnSet 、OnSetComplete 、OnRemove 、OnRemove Complete 、OnClear 和 OnClearComplete。由于 CollectionBase 是非泛型的,因此在對(duì)其進(jìn)行子類化時(shí)還必須實(shí)現(xiàn)類型化方法,至少要實(shí)現(xiàn)類型化索引器和 Add 方法。

          KeyedCollection<TKey,TItem> and DictionaryBase

          KeyedCollection<TKey,TItem> subclasses Collection<TItem> .它既增加又減去功能。它增加了按鍵訪問(wèn)項(xiàng)目的能力,就像字典一樣。它減去的是代理你自己的內(nèi)部列表的能力。

          鍵控集合與 OrderedDictionary 有一些相似之處,因?yàn)樗鼘⒕€性列表與哈希表組合在一起。然而,與OrderedDictionary不同的是,它不實(shí)現(xiàn)IDictionary,也不支持鍵/值的概念。鍵是從項(xiàng)目本身獲取的:通過(guò)抽象的 GetKeyForItem 方法。這意味著枚舉鍵控集合就像枚舉普通列表一樣。

          你可以最好地將KeyedCollection<TKey,TItem>視為Collection<TItem>加上按鍵快速查找。

          因?yàn)樗宇?Collection<> ,鍵控集合繼承了 Collection<> 的所有功能,除了在構(gòu)造中指定現(xiàn)有列表的能力。它定義的其他成員如下所示:

          public abstract class KeyedCollection <TKey, TItem> : Collection <TItem>
          
            // ...
          
            protected abstract TKey GetKeyForItem(TItem item);
            protected void ChangeItemKey(TItem item, TKey newKey);
          
            // Fast lookup by key - this is in addition to lookup by index.
            public TItem this[TKey key] { get; }
          
            protected IDictionary<TKey, TItem> Dictionary { get; }
          }

          GetKeyForItem 是實(shí)現(xiàn)者重寫(xiě)的內(nèi)容,以便從基礎(chǔ)對(duì)象獲取項(xiàng)的鍵。如果項(xiàng)的鍵屬性發(fā)生更改,則必須調(diào)用 ChangeItemKey 方法,以便更新內(nèi)部字典。Dictionary 屬性返回用于實(shí)現(xiàn)查找的內(nèi)部字典,該字典是在添加第一項(xiàng)時(shí)創(chuàng)建的。可以通過(guò)在構(gòu)造函數(shù)中指定創(chuàng)建閾值來(lái)更改此行為,延遲創(chuàng)建內(nèi)部字典直到達(dá)到閾值(在此期間,如果按鍵請(qǐng)求項(xiàng),則執(zhí)行線性搜索)。不指定創(chuàng)建閾值的一個(gè)很好的理由是,擁有有效的字典對(duì)于通過(guò)字典的 Keys 屬性獲取密鑰的 ICollection<> 很有用。然后,可以將此集合傳遞給公共屬性。

          KeyedCollection<,> 最常見(jiàn)的用途是提供可按索引和名稱訪問(wèn)的項(xiàng)集合。為了演示這一點(diǎn),讓我們重新訪問(wèn)動(dòng)物園,這次將 AnimalCollection 實(shí)現(xiàn)為 keyedCollection<string,Animal> :

          public class Animal
          {
            string name;
            public string Name
            {
              get { return name; }
              set {
                if (Zoo !=null) Zoo.Animals.NotifyNameChange (this, value);
                name=value;
              }
            }
            public int Popularity;
            public Zoo Zoo { get; internal set; }
          
            public Animal (string name, int popularity)
            {
              Name=name; Popularity=popularity;
            }
          }
          
          public class AnimalCollection : KeyedCollection <string, Animal>
          {
            Zoo zoo;
            public AnimalCollection (Zoo zoo) { this.zoo=zoo; }
          
            internal void NotifyNameChange (Animal a, string newName)=>
              this.ChangeItemKey (a, newName);
          
            protected override string GetKeyForItem (Animal item)=> item.Name;
          
            // The following methods would be implemented as in the previous example
            protected override void InsertItem (int index, Animal item)...
            protected override void SetItem (int index, Animal item)...
            protected override void RemoveItem (int index)...
            protected override void ClearItems()...
          }
          
          public class Zoo
          {
            public readonly AnimalCollection Animals;
            public Zoo() { Animals=new AnimalCollection (this); }
          }

          以下代碼演示了它的用法:

          Zoo zoo=new Zoo();
          zoo.Animals.Add (new Animal ("Kangaroo", 10));
          zoo.Animals.Add (new Animal ("Mr Sea Lion", 20));
          Console.WriteLine (zoo.Animals [0].Popularity);               // 10
          Console.WriteLine (zoo.Animals ["Mr Sea Lion"].Popularity);   // 20
          zoo.Animals ["Kangaroo"].Name="Mr Roo";
          Console.WriteLine (zoo.Animals ["Mr Roo"].Popularity);        // 10

          詞典庫(kù)

          KeyedCollection的非通用版本稱為DictionaryBase。這個(gè)遺留類采用了一種非常不同的方法,因?yàn)樗鼘?shí)現(xiàn)了 IDictionary 并使用笨拙的鉤子方法,如 CollectionBase : OnInsert , OnInsertComplete , OnSet , OnSetComplete , OnRemove , OnRemoveComplete , OnClear 和 OnClearComplete(以及另外的 OnGet)。與采用 KeyedCollection 方法相比,實(shí)現(xiàn) IDictionary 的主要優(yōu)點(diǎn)是,您無(wú)需對(duì)其進(jìn)行子類化即可獲取密鑰。但是由于DictionaryBase的目的是被子類化,所以它根本沒(méi)有優(yōu)勢(shì)。KeyedCollection 中改進(jìn)的模型幾乎可以肯定是因?yàn)樗窃趲啄旰缶帉?xiě)的,事后諸葛亮。DictionaryBase 最好被認(rèn)為是對(duì)向后兼容性有用的。

          只讀集合<T>

          ReadOnlyCollection<T> 是一個(gè)包裝器或,它提供集合的只讀視圖。這對(duì)于允許類公開(kāi)對(duì)集合的只讀訪問(wèn)權(quán)限非常有用,該集合仍然可以在內(nèi)部更新。

          只讀集合在其構(gòu)造函數(shù)中接受輸入集合,并維護(hù)對(duì)該集合的永久引用。它不采用輸入集合的靜態(tài)副本,因此對(duì)輸入集合的后續(xù)更改可通過(guò)只讀包裝器查看。

          為了說(shuō)明這一點(diǎn),假設(shè)您的類希望提供對(duì)名為 Names 的字符串列表的只讀公共訪問(wèn)。我們可以按如下方式執(zhí)行此操作:

          public class Test
          {
            List<string> names=new List<string>();
            public IReadOnlyList<string> Names=> names;
          }

          盡管 Names 返回只讀接口,但使用者仍然可以在運(yùn)行時(shí)向下轉(zhuǎn)換為 List<string> 或 IList<string>然后在列表中調(diào)用 Add 、Remove 或 Clear。ReadOnlyCollection<T> 提供了更強(qiáng)大的解決方案:

          public class Test
          {
            List<string> names=new List<string>();
            public ReadOnlyCollection<string> Names { get; private set; }
          
            public Test()=> Names=new ReadOnlyCollection<string> (names);
          
            public void AddInternally()=> names.Add ("test");
          }

          現(xiàn)在,只有 Test 類中的成員才能更改名稱列表:

          Test t=new Test();
          
          Console.WriteLine (t.Names.Count);       // 0
          t.AddInternally();
          Console.WriteLine (t.Names.Count);       // 1
          
          t.Names.Add ("test");                    // Compiler error
          ((IList<string>) t.Names).Add ("test");  // NotSupportedException

          不可變集合

          我們剛剛描述了 ReadOnlyCollection<T> 如何創(chuàng)建集合的只讀視圖。限制寫(xiě)入()集合或任何其他對(duì)象的能力可以簡(jiǎn)化軟件并減少錯(cuò)誤。

          集合通過(guò)提供初始化后根本無(wú)法修改的集合來(lái)擴(kuò)展此原則。如果需要將項(xiàng)添加到不可變集合,則必須實(shí)例化新集合,而不動(dòng)舊集合。

          不變性是的標(biāo)志,具有以下:

          • 它消除了與更改狀態(tài)相關(guān)的大量錯(cuò)誤。
          • 它極大地簡(jiǎn)化了并行性和多線程性,避免了我們?cè)凇?章中描述的大多數(shù)線程安全問(wèn)題。
          • 它使代碼更容易推理。

          不可變性的缺點(diǎn)是,當(dāng)您需要進(jìn)行更改時(shí),必須創(chuàng)建一個(gè)全新的對(duì)象。這會(huì)導(dǎo)致性能下降,盡管我們?cè)诒竟?jié)中討論了緩解策略,包括重用原始結(jié)構(gòu)部分的能力。

          不可變集合是 .NET 的一部分(在 .NET Framework 中,它們可通過(guò) NuGet 包獲得)。所有集合都在 System.Collections.Immutable 命名空間中定義:

          類型

          內(nèi)部結(jié)構(gòu)



          不可變數(shù)組<T>

          數(shù)組



          不可變列表<T>

          AVL 樹(shù)



          不可變字典<K,V>

          AVL 樹(shù)



          ImmutableHashSet<T>

          AVL 樹(shù)



          ImmutableSortedDictionary<K,V>

          AVL 樹(shù)



          ImmutableSortedSet<T>

          AVL 樹(shù)



          不可變堆棧<T>

          鏈表



          不可變隊(duì)列<T>

          鏈表



          ImmutableArray<T> 和 ImmutableList<T> 類型都是 List<T> 的不可變版本。兩者都執(zhí)行相同的工作,但具有不同的性能特征,我們?cè)谥杏懻撨^(guò)。

          不可變集合公開(kāi)一個(gè)類似于其可變對(duì)應(yīng)項(xiàng)的公共接口。主要區(qū)別在于,似乎用于更改集合的方法(例如 添加或刪除 )不會(huì)更改原始集合;相反,它們返回一個(gè)新集合,其中包含添加或刪除請(qǐng)求的項(xiàng)。

          注意

          不可變集合可防止添加和刪除項(xiàng);它們不會(huì)阻止物品發(fā)生變異。若要獲得不可變性的全部好處,需要確保只有不可變項(xiàng)才能在不可變集合中結(jié)束。

          創(chuàng)建不可變集合

          每個(gè)不可變集合類型都提供一個(gè) Create<T>() 方法,該方法接受可選的初始值并返回初始化的不可變集合:

          ImmutableArray<int> array=ImmutableArray.Create<int> (1, 2, 3);

          每個(gè)集合還提供一個(gè) CreateRange<T> 方法,該方法與 Create<T 執(zhí)行相同的工作> ;不同之處在于它的參數(shù)類型是 IEnumerable<T>而不是參數(shù) T[]。

          您還可以使用適當(dāng)?shù)臄U(kuò)展方法(ToImmutableArray、ToImmutableList、ToImmutableDictionary等)從現(xiàn)有的IEnumerable<T>創(chuàng)建不可變集合:

          var list=new[] { 1, 2, 3 }.ToImmutableList();

          操作不可變集合

          Add 方法返回一個(gè)包含現(xiàn)有元素和新元素的新集合:

          var oldList=ImmutableList.Create<int> (1, 2, 3);
          
          ImmutableList<int> newList=oldList.Add (4);
          
          Console.WriteLine (oldList.Count);     // 3  (unaltered)
          Console.WriteLine (newList.Count);     // 4

          Remove 方法以相同的方式運(yùn)行,返回已刪除項(xiàng)的新集合。

          以這種方式重復(fù)添加或刪除元素效率低下,因?yàn)闀?huì)為每個(gè)添加或刪除操作創(chuàng)建一個(gè)新的不可變集合。更好的解決方案是調(diào)用 AddRange(或 RemoveRange),它接受 IEnumerable<T> 項(xiàng),這些項(xiàng)都是一次性添加或刪除的:

          var anotherList=oldList.AddRange (new[] { 4, 5, 6 });

          不可變列表和數(shù)組還定義了用于在特定索引處插入元素的 Insert 和 InsertRange 方法、用于在索引處刪除的 RemoveAt 方法以及基于謂詞刪除的 RemoveAll。

          建設(shè)者

          對(duì)于更復(fù)雜的初始化需求,每個(gè)不可變集合類定義一個(gè)對(duì)應(yīng)項(xiàng)。生成器是在功能上等效于可變集合的類,具有類似的性能特征。數(shù)據(jù)初始化后,調(diào)用 .構(gòu)建器上的 ToImmutable() 返回一個(gè)不可變的集合。

          ImmutableArray<int>.Builder builder=ImmutableArray.CreateBuilder<int>();
          builder.Add (1);
          builder.Add (2);
          builder.Add (3);
          builder.RemoveAt (0);
          ImmutableArray<int> myImmutable=builder.ToImmutable();

          還可以使用生成器對(duì)現(xiàn)有不可變更新:

          var builder2=myImmutable.ToBuilder();
          builder2.Add (4);      // Efficient
          builder2.Remove (2);   // Efficient
          ...                    // More changes to builder...
          // Return a new immutable collection with all the changes applied:
          ImmutableArray<int> myImmutable2=builder2.ToImmutable();

          不可變集合和性能

          大多數(shù)不可變集合在內(nèi)部使用 ,這允許添加/刪除操作重用原始內(nèi)部結(jié)構(gòu)的部分內(nèi)容,而不必從頭開(kāi)始重新創(chuàng)建整個(gè)內(nèi)容。這將添加/刪除操作的開(kāi)銷從潛在的(具有大型集合)減少到,但代價(jià)是讀取操作速度變慢。最終結(jié)果是,大多數(shù)不可變集合在讀取和寫(xiě)入方面都比可變集合慢。

          受影響最嚴(yán)重的是 ImmutableList<T> ,對(duì)于讀取和添加操作,它比 List<T 慢 10 到 200 倍>(取決于列表的大小)。這就是ImmutableArray<T>存在的原因:通過(guò)在內(nèi)部使用數(shù)組,它避免了讀取操作的開(kāi)銷(其性能可與普通可變數(shù)組相媲美)。另一方面,對(duì)于添加操作,它比(甚至)ImmutableList<T>慢,因?yàn)樵冀Y(jié)構(gòu)都不能重用。

          因此,當(dāng)您想要不受阻礙的性能并且不希望后續(xù)調(diào)用添加或刪除(不使用構(gòu)建器)時(shí),ImmutableArray<> 是可取的。

          類型

          讀取性能

          提高性能

          不可變列表<T>

          不可變數(shù)組<T>

          非常快

          非常慢

          注意

          在 ImmutableArray 上調(diào)用 Remove 比在 List<T 上調(diào)用 Remove 更昂貴>即使在刪除第一個(gè)元素的最壞情況下也是如此,因?yàn)榉峙湫录蠒?huì)給垃圾回收器帶來(lái)額外的負(fù)載。

          盡管不可變集合作為一個(gè)整體會(huì)產(chǎn)生潛在的顯著性能成本,但保持總體量級(jí)非常重要。在典型的筆記本電腦上,對(duì)具有一百萬(wàn)個(gè)元素的不可變列表執(zhí)行 Add 操作仍可能在不到一微秒的時(shí)間內(nèi)發(fā)生,而讀取操作則在不到 100 納秒的時(shí)間內(nèi)發(fā)生。而且,如果您需要在循環(huán)中執(zhí)行寫(xiě)入操作,則可以使用構(gòu)建器避免累積成本。

          以下因素也有助于降低成本:

          • 不變性允許輕松并發(fā)和并行化(),因此您可以使用所有可用的內(nèi)核。與可變狀態(tài)并行化很容易導(dǎo)致錯(cuò)誤,并且需要使用鎖或并發(fā)集合,這兩者都會(huì)損害性能。
          • 通過(guò)不可變性,您無(wú)需“防御性復(fù)制”集合或數(shù)據(jù)結(jié)構(gòu)來(lái)防止意外更改。這是支持在編寫(xiě)Visual Studio的最新部分時(shí)使用不可變集合的一個(gè)因素。
          • 在大多數(shù)典型程序中,很少有集合有足夠的項(xiàng)目來(lái)區(qū)分差異。

          除了Visual Studio之外,性能良好的Microsoft Roslyn工具鏈也是用不可變的集合構(gòu)建的,展示了好處如何超過(guò)成本。

          插入平等和秩序

          在第 的“相等比較”和部分中,我們描述了使類型、可哈希和可比較的標(biāo)準(zhǔn) .NET 協(xié)議。實(shí)現(xiàn)這些協(xié)議的類型可以在字典或“開(kāi)箱即用”的排序列表中正常運(yùn)行。更具體地說(shuō):

          • Equals 和 GetHashCode 返回有意義的結(jié)果的類型可以用作字典或哈希表中的鍵。
          • 實(shí)現(xiàn) IComparable / IComparable<T> 的類型可以用作任何字典或列表中的鍵。

          類型的默認(rèn)等價(jià)或比較實(shí)現(xiàn)通常反映該類型最“自然”的內(nèi)容。但是,有時(shí)默認(rèn)行為不是您想要的。您可能需要一個(gè)字典,其字符串類型鍵的處理不考慮大小寫(xiě)。或者,您可能需要按每個(gè)客戶的郵政編碼排序的客戶排序列表。因此,.NET 還定義了一組匹配的“插件”協(xié)議。插件協(xié)議實(shí)現(xiàn)了兩件事:

          • 它們?cè)试S您切換替代等同或比較行為。
          • 它們?cè)试S您使用具有本質(zhì)上不相等或可比的鍵類型的字典或排序集合。

          插件協(xié)議由以下接口組成:

          IEqualityComparer 和 IEqualityComparer<T>

          • 執(zhí)行插件
          • 由哈希表和字典識(shí)別

          IComparer 和 IComparer<T>

          • 執(zhí)行插件
          • 被排序的詞典和集合識(shí)別;另外,數(shù)組排序

          每個(gè)接口都有通用和非通用形式。IEqualityComparer 接口在一個(gè)名為 EqualityComparer 的類中也有默認(rèn)實(shí)現(xiàn)。

          此外,還有稱為 IStructuralEquatable 和 IStructuralComparable 的接口,它們?cè)试S對(duì)類和數(shù)組進(jìn)行結(jié)構(gòu)比較的選項(xiàng)。

          IEqualityComparer 和 EqualComparer

          相等比較器切換非默認(rèn)相等和哈希行為,主要用于字典和哈希表類。

          回想一下基于哈希表的字典的要求。對(duì)于任何給定的鍵,它需要回答兩個(gè)問(wèn)題:

          • 和另一個(gè)一樣嗎?
          • 它的整數(shù)哈希碼是什么?

          相等比較器通過(guò)實(shí)現(xiàn) IEqualityComparer 接口來(lái)回答這些問(wèn)題:

          public interface IEqualityComparer<T>
          {
             bool Equals (T x, T y);
             int GetHashCode (T obj);
          }
          
          public interface IEqualityComparer     // Nongeneric version
          {
             bool Equals (object x, object y);
             int GetHashCode (object obj);
          }

          若要編寫(xiě)自定義比較器,請(qǐng)實(shí)現(xiàn)其中一個(gè)或兩個(gè)接口(實(shí)現(xiàn)這兩個(gè)接口可提供最大的互操作性)。因?yàn)檫@有點(diǎn)乏味,所以另一種方法是對(duì)抽象的 EqualityComparer 類進(jìn)行子類類,定義如下:

          public abstract class EqualityComparer<T> : IEqualityComparer,
                                                      IEqualityComparer<T>
          {
            public abstract bool Equals (T x, T y);
            public abstract int GetHashCode (T obj);
          
            bool IEqualityComparer.Equals (object x, object y);
            int IEqualityComparer.GetHashCode (object obj);
          
            public static EqualityComparer<T> Default { get; }
          }

          EqualityComparer 實(shí)現(xiàn)了這兩個(gè)接口;你的工作只是重寫(xiě)這兩個(gè)抽象方法。

          Equals 和 GetHashCode 的語(yǔ)義遵循相同的對(duì)象規(guī)則。等于和反對(duì)。GetHashCode ,在第 中描述。在下面的示例中,我們定義一個(gè)包含兩個(gè)字段的 Customer 類,然后編寫(xiě)一個(gè)與名字和姓氏匹配的相等比較器:

          public class Customer
          {
            public string LastName;
            public string FirstName;
          
            public Customer (string last, string first)
            {
              LastName=last;
              FirstName=first;
            }
          }
          public class LastFirstEqComparer : EqualityComparer <Customer>
          {
            public override bool Equals (Customer x, Customer y)=> x.LastName==y.LastName && x.FirstName==y.FirstName;
          
            public override int GetHashCode (Customer obj)=> (obj.LastName + ";" + obj.FirstName).GetHashCode();
          }

          為了說(shuō)明其工作原理,讓我們創(chuàng)建兩個(gè)客戶:

          Customer c1=new Customer ("Bloggs", "Joe");
          Customer c2=new Customer ("Bloggs", "Joe");

          因?yàn)槲覀儧](méi)有覆蓋對(duì)象。等于,正常引用類型相等語(yǔ)義適用:

          Console.WriteLine (c1==c2);               // False
          Console.WriteLine (c1.Equals (c2));         // False

          在中使用這些客戶而不指定相等比較器時(shí),相同的默認(rèn)相等語(yǔ)義適用:

          var d=new Dictionary<Customer, string>();
          d [c1]="Joe";
          Console.WriteLine (d.ContainsKey (c2));         // False

          現(xiàn)在,使用自定義相等比較器:

          var eqComparer=new LastFirstEqComparer();
          var d=new Dictionary<Customer, string> (eqComparer);
          d [c1]="Joe";
          Console.WriteLine (d.ContainsKey (c2));         // True

          在此示例中,我們必須小心不要在字典中使用客戶的名字或姓氏時(shí)更改它;否則,它的哈希代碼會(huì)改變,字典會(huì)中斷。

          平等比較<T>。違約

          調(diào)用 EqualityComparer<T>。默認(rèn)值返回一個(gè)通用相等比較器,您可以將其用作靜態(tài)對(duì)象的替代對(duì)象。等于方法。優(yōu)點(diǎn)是它首先檢查 T 是否實(shí)現(xiàn)了 IEquatable<T>,如果是,它會(huì)調(diào)用該實(shí)現(xiàn),避免裝箱開(kāi)銷。這在泛型方法中特別有用:

          static bool Foo<T> (T x, T y)
          {
            bool same=EqualityComparer<T>.Default.Equals (x, y);
            ...

          ReferenceEqualityComparer.Instance (.NET 5+)

          從 .NET 5 開(kāi)始,ReferenceEqualityComparer.Instance 返回始終應(yīng)用引用相等的相等比較器。對(duì)于值類型,其 Equals 方法始終返回 false。

          IComparer 和 Comparer

          比較器用于切換已排序詞典和集合的自定義排序邏輯。

          請(qǐng)注意,比較器對(duì)于未排序的字典(如字典和哈希表)毫無(wú)用處 - 這些字典需要IEqualityComparer來(lái)獲取哈希碼。同樣,相等比較器對(duì)于排序詞典和集合也毫無(wú)用處。

          以下是 IComparer 接口定義:

          public interface IComparer
          {
            int Compare(object x, object y);
          }
          public interface IComparer <in T>
          {
            int Compare(T x, T y);
          }

          與相等比較器一樣,有一個(gè)抽象類可以子類型化而不是實(shí)現(xiàn)接口:

          public abstract class Comparer<T> : IComparer, IComparer<T>
          {
             public static Comparer<T> Default { get; }
          
             public abstract int Compare (T x, T y);       // Implemented by you
             int IComparer.Compare (object x, object y);   // Implemented for you
          }

          下面的示例演示了一個(gè)描述愿望的類以及一個(gè)按優(yōu)先級(jí)對(duì)愿望進(jìn)行排序的比較器:

          class Wish
          {
            public string Name;
            public int Priority;
          
            public Wish (string name, int priority)
            {
              Name=name;
              Priority=priority;
            }
          }
          
          class PriorityComparer : Comparer<Wish>
          {
            public override int Compare (Wish x, Wish y)
            {
              if (object.Equals (x, y)) return 0;    // Optimization
              if (x==null) return -1;
              if (y==null) return 1;
              return x.Priority.CompareTo (y.Priority);
            }
          }

          對(duì)象。等于檢查確保我們永遠(yuǎn)不會(huì)與等于方法相矛盾。調(diào)用靜態(tài)對(duì)象。在這種情況下,等于方法比調(diào)用 x.Equals 更好,因?yàn)槿绻?x 為空,它仍然有效!

          以下是我們的 PriorityComparer 如何用于對(duì)列表進(jìn)行排序:

          var wishList=new List<Wish>();
          wishList.Add (new Wish ("Peace", 2));
          wishList.Add (new Wish ("Wealth", 3));
          wishList.Add (new Wish ("Love", 2));
          wishList.Add (new Wish ("3 more wishes", 1));
          
          wishList.Sort (new PriorityComparer());
          foreach (Wish w in wishList) Console.Write (w.Name + " | ");
          
          // OUTPUT: 3 more wishes | Love | Peace | Wealth |

          在下一個(gè)示例中,SurnameComparer 允許您按適合電話簿列表的順序?qū)π帐献址M(jìn)行排序:

          class SurnameComparer : Comparer <string>
          {
            string Normalize (string s)
            {
              s=s.Trim().ToUpper();
              if (s.StartsWith ("MC")) s="MAC" + s.Substring (2);
              return s;
            }
          
            public override int Compare (string x, string y)=> Normalize (x).CompareTo (Normalize (y));
          }

          以下是排序詞典中使用的姓氏比較器:

          var dic=new SortedDictionary<string,string> (new SurnameComparer());
          dic.Add ("MacPhail", "second!");
          dic.Add ("MacWilliam", "third!");
          dic.Add ("McDonald", "first!");
          
          foreach (string s in dic.Values)
            Console.Write (s + " ");              // first! second! third!

          StringComparer

          StringComparer 是一個(gè)預(yù)定義的插件類,用于等同和比較字符串,允許您指定語(yǔ)言和區(qū)分大小寫(xiě)。StringComparer同時(shí)實(shí)現(xiàn)了IEqualityComparer和IComparer(及其通用版本),因此您可以將其與任何類型的字典或排序集合一起使用。

          因?yàn)?StringComparer 是抽象的,所以您可以通過(guò)其靜態(tài)屬性獲取實(shí)例。StringComparer.Ordinal 鏡像字符串相等比較的默認(rèn)行為,StringComparer.CurrentCulture 鏡像順序比較的默認(rèn)行為。以下是其所有靜態(tài)成員:

          public static StringComparer CurrentCulture { get; }
          public static StringComparer CurrentCultureIgnoreCase { get; }
          public static StringComparer InvariantCulture { get; }
          public static StringComparer InvariantCultureIgnoreCase { get; }
          public static StringComparer Ordinal { get; }
          public static StringComparer OrdinalIgnoreCase { get; }
          public static StringComparer Create (CultureInfo culture,
                                                 bool ignoreCase);

          在下面的示例中,創(chuàng)建了一個(gè)不區(qū)分大小寫(xiě)的序號(hào)字典,使得 dict[“Joe”] 和 dict[“JOE”] 的含義相同:

          var dict=new Dictionary<string, int> (StringComparer.OrdinalIgnoreCase);

          在下一個(gè)示例中,使用澳大利亞英語(yǔ)對(duì)名稱數(shù)組進(jìn)行排序:

          string[] names={ "Tom", "HARRY", "sheila" };
          CultureInfo ci=new CultureInfo ("en-AU");
          Array.Sort<string> (names, StringComparer.Create (ci, false));

          最后一個(gè)例子是我們?cè)谏弦还?jié)中編寫(xiě)的 SurnameComparer 的文化感知版本(用于比較適合電話簿列表的名稱):

          class SurnameComparer : Comparer<string>
          {
            StringComparer strCmp;
          
            public SurnameComparer (CultureInfo ci)
            {
              // Create a case-sensitive, culture-sensitive string comparer
              strCmp=StringComparer.Create (ci, false);
            }
          
            string Normalize (string s)
            {
              s=s.Trim();
              if (s.ToUpper().StartsWith ("MC")) s="MAC" + s.Substring (2);
              return s;
            }
          
            public override int Compare (string x, string y)
            {
              // Directly call Compare on our culture-aware StringComparer
              return strCmp.Compare (Normalize (x), Normalize (y));
            }
          }

          等同和等同

          正如我們中所討論的,結(jié)構(gòu)默認(rèn)實(shí)現(xiàn)結(jié)構(gòu)比較:如果兩個(gè)的所有字段都相等,則它們相等。但是,有時(shí)結(jié)構(gòu)相等和順序比較作為其他類型(如數(shù)組)的插件選項(xiàng)也很有用。以下接口對(duì)此有所幫助:

          public interface IStructuralEquatable
          {
            bool Equals (object other, IEqualityComparer comparer);
            int GetHashCode (IEqualityComparer comparer);
          }
          
          public interface IStructuralComparable
          {
            int CompareTo (object other, IComparer comparer);
          }

          您傳入的 IEqualityComparer / IComparer 將應(yīng)用于復(fù)合對(duì)象中的每個(gè)單獨(dú)元素。我們可以通過(guò)使用數(shù)組來(lái)演示這一點(diǎn)。在下面的示例中,我們比較兩個(gè)數(shù)組的相等性,首先使用默認(rèn)的 Equals 方法,然后使用 IStructuralEquatable 的版本:

          int[] a1={ 1, 2, 3 };
          int[] a2={ 1, 2, 3 };
          IStructuralEquatable se1=a1;
          Console.Write (a1.Equals (a2));                                  // False
          Console.Write (se1.Equals (a2, EqualityComparer<int>.Default));  // True

          這是另一個(gè)示例:

          225 【跨新年】【萬(wàn)泉河】WINCC中獲取窗口變量前綴以及跨窗口操控的方法

          近段時(shí)間,不約而同的,網(wǎng)站論壇和煙臺(tái)方法學(xué)員中都有提出這樣的問(wèn)題。

          比如:

          • 用C腳本如何獲得窗口中對(duì)象的變量前綴?
          • 用VBS如何獲得?
          • 用C腳本如何實(shí)現(xiàn)在一個(gè)窗口中操控父窗口下的另一個(gè)窗口內(nèi)的控件?
          • 用VBS如何實(shí)現(xiàn)?

          這些問(wèn)題,都有個(gè)特點(diǎn), 提問(wèn)的時(shí)候先把編程語(yǔ)言給限定了。

          而咱就不太有辦法拒絕。畢竟,人家有可能是在完成一個(gè)更復(fù)雜的工作,已經(jīng)在選定的語(yǔ)言下實(shí)現(xiàn)了大部分的功能,現(xiàn)在就在這一點(diǎn)點(diǎn)功能搞不定被卡住了,過(guò)不去了,才來(lái)求助的。

          而如果不指定語(yǔ)言的話,其實(shí)我都早就有答案,特別是VBS的解決方案,都寫(xiě)在《西門(mén)子WINCC入門(mén)到精通》的書(shū)里了,所以只需要從書(shū)柜里把我自己的書(shū)拿出來(lái), 找到頁(yè)碼,把頁(yè)碼號(hào)告訴對(duì)方就可以了。

          而且會(huì)發(fā)現(xiàn),大部分提問(wèn)者其實(shí)是有我的那本書(shū)的,只是通讀不夠細(xì)致,沒(méi)發(fā)現(xiàn),或者沒(méi)記住有這方面的介紹。驗(yàn)證了一個(gè)道理,對(duì)一本書(shū),最了解的還是作者自己。

          當(dāng)然,我也發(fā)現(xiàn)了我寫(xiě)書(shū)時(shí)候遺漏的該寫(xiě)而未寫(xiě)的技巧知識(shí)點(diǎn)。可能潛意識(shí)里面,我自己覺(jué)得反正另一條路上有解決方案了,這邊這一條就沒(méi)必要去重復(fù)啰嗦實(shí)現(xiàn)了。特別是C腳本,對(duì)西門(mén)子來(lái)說(shuō)自從20年前的WINCC版本支持VBS之后,官方逐漸在弱化C腳本的地位, 我自己也逐漸淡化對(duì)其的研究和使用。想一碗水端平是永遠(yuǎn)不可能的。

          在此先提醒大家, 提問(wèn)問(wèn)題的時(shí)候,盡可能不要限定編程語(yǔ)言。那樣的答案多的是,可以唾手可得。而非要限定語(yǔ)言了,尋找起來(lái)就會(huì)有一些難度。

          所以,我這里做了個(gè)例子,進(jìn)行了測(cè)試,可以把這些坑填上了。 也算是對(duì)我自己著作中遺漏部分的補(bǔ)充。

          主畫(huà)面中是2個(gè)按鈕,分別演示了VBS腳本和C腳本彈出窗口的方法。

          而彈出的窗口外觀相同,然而內(nèi)部的程序語(yǔ)言不同, 分別有按鈕按下后可以彈出對(duì)話框提示讀取得到了前綴。

          而后, 將上述2個(gè)按鈕分別放到另一個(gè)叫做“窗口中操控”的窗口(PDL文件),腳本經(jīng)過(guò)稍微修改,實(shí)現(xiàn)了上述同樣的功能。

          • 按鈕11的單擊鼠標(biāo)中的VBS程序?yàn)椋?/strong>

          Sub OnClick(ByVal Item)

          ScreenItems("畫(huà)面窗口1").TagPrefix="M001_"

          ScreenItems("畫(huà)面窗口1").PictureName=Item.Text

          ScreenItems("畫(huà)面窗口1").Visible=True

          End Sub

          而窗口中有2個(gè)獲取前綴的按鈕,同樣可以得到結(jié)果,腳本分別為:

          Sub OnClick(Byval Item)

          'MsgBOX(ITEM.Parent.Parent.TagPrefix)

          MsgBOX(Parent.TagPrefix)

          End Sub

          注釋掉的腳本也同樣可以執(zhí)行。

          Sub OnClick(Byval Item)

          Dim name

          name=HMIRuntime.Tags("aa").Name

          Dim TagPrefix

          TagPrefix=Split(name,"_")(0)

          MsgBOX(TagPrefix)

          End Sub

          這里取了一個(gè)不存在的aa后綴的變量,然而也絲毫不影響功能。 因?yàn)檎Z(yǔ)法本身獲取的是變量名字,對(duì)變量是否有值是否合法根本不在意。

          也注意兩種方式得到的前綴分別有分隔符和無(wú)分隔符的區(qū)別。

          標(biāo)準(zhǔn)的用法當(dāng)然不能每次都通過(guò)腳本來(lái)獲取,而是會(huì)在子窗口打開(kāi)時(shí)即執(zhí)行腳本,獲取到前綴后,賦值到一個(gè)靜態(tài)文本中,窗口中任何需要的地方,可以通過(guò)讀取文本內(nèi)容得到。

          Sub OnOpen()

          'MsgBOX(Parent.TagPrefix)

          ScreenItems("窗口前綴").Text=Parent.TagPrefix

          End Sub

          而如果畫(huà)面窗口中不需要顯示這個(gè)前綴,可以設(shè)置靜態(tài)文本為隱藏。這是官方例程中慣用的手法。

          • 按鈕12的單擊鼠標(biāo)中的C程序?yàn)椋?/strong>

          #include "apdefap.h"

          void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)

          {

          SetPropChar(lpszPictureName, "畫(huà)面窗口1", "PictureName","窗口12-C獲取前綴.Pdl");

          SetPropChar(lpszPictureName, "畫(huà)面窗口1", "TagPrefix", "M002_");

          SetPropBOOL(lpszPictureName, "畫(huà)面窗口1", "Visible", TRUE);

          }

          窗口內(nèi)獲取按鈕的腳本:

          #include "apdefap.h"

          void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)

          {

          char Name[20];

          HWND hwnd=NULL;

          hwnd=FindWindow(NULL,"WinCC-運(yùn)行系統(tǒng) - "); //獲得句柄

          strcpy(Name,GetPropChar(GetParentPicture(lpszPictureName),GetParentPictureWindow(lpszPictureName),"TagPrefix")); //Return-Type: char*

          MessageBox(hwnd,Name,"OK",MB_OK);

          }

          畫(huà)面打開(kāi)事件中文本內(nèi)容得到的方法:

          #include "apdefap.h"

          void OnOpenPicture(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)

          {

          char Name[20];

          strcpy(Name,GetPropChar(GetParentPicture(lpszPictureName),GetParentPictureWindow(lpszPictureName),"TagPrefix")); //Return-Type: char*

          SetPropChar(lpszPictureName,"窗口前綴","Text",Name); //Return-Type: BOOL

          }

          這里C腳本實(shí)現(xiàn)的核心是一個(gè)GetParentPicture的函數(shù),可以得到窗口的父窗口的文件名字。而這個(gè)函數(shù)是個(gè)神仙函數(shù),各種幫助資料中都沒(méi)有見(jiàn)過(guò)介紹。所以只有從已有的使用演示程序中獲取。

          • 窗口中按鈕11的單擊鼠標(biāo)中的VBS程序?yàn)椋?/strong>

          Sub OnClick(ByVal Item)

          Parent.Parent.ScreenItems("畫(huà)面窗口1").TagPrefix="M001_"

          Parent.Parent.ScreenItems("畫(huà)面窗口1").PictureName=Item.Text

          Parent.Parent.ScreenItems("畫(huà)面窗口1").Visible=True

          End Sub

          • 窗口中按鈕12的單擊鼠標(biāo)中的C程序?yàn)椋?/strong>

          #include "apdefap.h"

          void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)

          {

          char szParentPicture[512];

          strncpy (szParentPicture, GetParentPicture(lpszPictureName), sizeof(szParentPicture));

          SetPropChar(szParentPicture, "畫(huà)面窗口1", "PictureName","窗口12-C獲取前綴.Pdl");

          SetPropChar(szParentPicture, "畫(huà)面窗口1", "TagPrefix", "M002_");

          SetPropBOOL(szParentPicture, "畫(huà)面窗口1", "Visible", TRUE);

          }

          分別實(shí)現(xiàn)了上述同樣的功能。

          當(dāng)然,我們也可以隨意的組合搭配按鈕和窗口內(nèi)的程序,都可以實(shí)現(xiàn)同樣的功能。 比如即便你程序的主體語(yǔ)言是C的或者VBS,然而窗口中的文本獲得前綴部分可以用VBS簡(jiǎn)單得到。

          最后,這些具體產(chǎn)品知識(shí)點(diǎn)的技巧內(nèi)容我近幾年確實(shí)很少觸及了。 因?yàn)樵谖铱磥?lái)那都是基本功。我甚至也不去記憶具體的函數(shù)名字,比如上面的name還是tagname, TagPrefix還是Prefix, 都記不住的。我即便要使用,也都直接找現(xiàn)成的程序模塊看一眼,抄來(lái)用下即可。

          而事實(shí)上,隨著標(biāo)準(zhǔn)化模塊化的推進(jìn),這些技能已經(jīng)很少用到了,因?yàn)樵缇头庋b完善在模塊中了。

          有一些年輕人看到我近年來(lái)寫(xiě)各種科普文章,很少提及這些具體的技術(shù)技能技巧,懷疑我從來(lái)沒(méi)掌握這些技能,DISS我,甚至來(lái)跟我PK,只能是他們眼光太短淺了,你要從事技術(shù)工作,不是要把一項(xiàng)項(xiàng)的技能從年輕到年老記憶地牢牢的,以隨時(shí)使用。相反的是,大部分技能是需要封裝的,封裝以后直接使用,甚至自己要主動(dòng)把這些具體技能都遺忘掉,才可以有更多的精力去掌握更高層的知識(shí)。

          最后,給大家的建議是,要盡量少自己從頭造輪子。

          雖然我自己在成長(zhǎng)過(guò)程中,是摸著石頭過(guò)河每個(gè)輪子逐個(gè)造過(guò)來(lái)的,遇到任何問(wèn)題,也都抑制不住要自己親自造輪子的沖動(dòng)。 輪子的每一個(gè)細(xì)節(jié),如果不親自掌握,就會(huì)抓耳撓腮睡覺(jué)都不安心。

          然而仍然要提醒同行后來(lái)者,這是一種非常低效率的行為。 你可以有好奇心,精力充沛的情況下可以對(duì)別人造好的輪子仔細(xì)研讀原理,自己可以從中掌握些基本功,然而自己從頭造輪子這件事,就要盡量避免了。

          上述例程的實(shí)現(xiàn)方法,西門(mén)子官方的例子中其實(shí)原本就有,而且功能比我這里介紹的要全面而細(xì)致得多得多。

          西門(mén)子官方例程中, BST例程較多的是使用了C腳本,而LBP例程(或者叫做BPL)相同的功能則更多是用VBS實(shí)現(xiàn)的。

          這些例程我都已經(jīng)寫(xiě)文章推薦過(guò)多次了。這回就不再提供鏈接以及親自提供文件了,而只提供名字,需要者自己辛苦一點(diǎn)去找到并學(xué)習(xí)了解。 看來(lái)太容易得到的資料通常都不珍惜,只有自己辛苦一點(diǎn),千辛萬(wàn)苦得到的才會(huì)更加倍的去學(xué)習(xí)。

          另外,考慮到上面的乏味的語(yǔ)言講述不夠直觀,有可能很多人看了并不能理解。 我有計(jì)劃在元旦期間做2次視頻直播講座, 專門(mén)講解展示這個(gè)例程的實(shí)現(xiàn)方法。第一次直播會(huì)在煙臺(tái)方法學(xué)員群中,第二次直播會(huì)面向大眾。 有感興趣者請(qǐng)關(guān)注公眾號(hào)、朋友圈,及時(shí)獲取通知。

          我們很多現(xiàn)場(chǎng)中,客戶需要點(diǎn)擊某個(gè)變量/按鈕,彈出曲線窗口,我們可以使用曲線彈窗+改變連接變量名來(lái)通過(guò)一個(gè)界面顯示不同的曲線趨勢(shì)圖。

          1.新建一個(gè)工程

          計(jì)算機(jī)屬性勾選變量記錄運(yùn)行系統(tǒng)與圖形運(yùn)行系統(tǒng)

          2.新建二個(gè)變量


          3.變量記錄里面新建歸檔


          4.新建曲線彈窗,如下圖添加曲線和標(biāo)尺控件

          曲線控件名為控件1


          5.新建一個(gè)主畫(huà)面

          按鈕1腳本為:

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","CaptionText","曲線1");//設(shè)置標(biāo)題

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","Visible","0");//顯示關(guān)閉

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","Visible","1");//顯示打開(kāi)

          SetPropDouble("曲線彈窗.pdl","控件1","ValueAxisBeginValue",00);//設(shè)置曲線范圍

          SetPropDouble("曲線彈窗.pdl","控件1","ValueAxisEndValue",14.0);//設(shè)置曲線范圍

          SetPropChar("曲線彈窗.pdl","控件1","TrendTagName","quxian\曲線1");//設(shè)置曲線變量

          按鈕2腳本為:

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","CaptionText","曲線2");//設(shè)置標(biāo)題

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","Visible","0");//顯示關(guān)閉

          SetPropChar(lpszPictureName,"畫(huà)面窗口1","Visible","1");//顯示打開(kāi)

          SetPropDouble("曲線彈窗.pdl","控件1","ValueAxisBeginValue",00);//設(shè)置曲線范圍

          SetPropDouble("曲線彈窗.pdl","控件1","ValueAxisEndValue",14.0);//設(shè)置曲線范圍

          SetPropChar("曲線彈窗.pdl","控件1","TrendTagName","quxian\曲線2");//設(shè)置曲線變量

          6.運(yùn)行主界面



          注意:如果提示無(wú)法建立任何數(shù)據(jù)連接,請(qǐng)轉(zhuǎn)至第一步打開(kāi)變量記錄運(yùn)行系統(tǒng)

          測(cè)試成功。


          主站蜘蛛池模板: 国内精品一区二区三区最新| 日韩动漫av在线播放一区| 亚洲天堂一区二区三区四区| 成人毛片一区二区| 制服丝袜一区在线| 久久亚洲国产精品一区二区| 韩国女主播一区二区| 国产成人精品久久一区二区三区| 无码AⅤ精品一区二区三区| 丝袜美腿一区二区三区| 伊人久久一区二区三区无码 | 在线播放精品一区二区啪视频| 一区二区视频传媒有限公司| 亚洲一区二区视频在线观看| 视频一区二区三区人妻系列| 视频一区二区三区人妻系列| 精品视频午夜一区二区| 日韩精品电影一区| 国产精品一区二区久久沈樵| 国产在线精品一区在线观看| 加勒比精品久久一区二区三区| 日本一区中文字幕日本一二三区视频| 91在线视频一区| 国产韩国精品一区二区三区久久| 国产91久久精品一区二区| 中文字幕一区在线观看视频| 亚洲色精品VR一区区三区| 国产精品av一区二区三区不卡蜜| 亚洲AV日韩综合一区尤物| 国模无码一区二区三区| 欧美亚洲精品一区二区| 日韩一区在线视频| 亚洲av午夜福利精品一区| 亚洲成在人天堂一区二区| 亚洲乱码一区二区三区国产精品| 精品动漫一区二区无遮挡| 久久精品免费一区二区喷潮| 国产一区二区在线|播放| 国精产品一区二区三区糖心 | 国产日韩一区二区三区在线观看 | 日本一区二区免费看|