Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 91精品国产自产91精品 ,欧美成人小视频,欧美性受xxxx喷潮

          整合營銷服務商

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

          免費咨詢熱線:

          c# 10 教程:7 集合

          c# 10 教程:7 集合

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

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

          • 定義標準收集協(xié)議的接口
          • 即用型集合類(列表、字典等)
          • 用于編寫特定于應用程序的集合的基類

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

          集合命名空間如下所示:

          Namespace

          包含

          系統(tǒng).集合

          非泛型集合類和接口

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

          強類型非泛型集合類

          System.Collections.Generic

          泛型集合類和接口

          System.Collections.ObjectModel

          自定義集合的代理和基礎

          System.Collections.Parallel

          線程安全集合(請參閱)

          列舉

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


          集合接口

          IEnumerable 和 IEnumerator

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

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

          MoveNext 將當前元素或“光標”前進到下一個位置,如果集合中沒有更多元素,則返回 false。Current 返回當前位置的元素(通常從對象強制轉換為更具體的類型)。在檢索第一個元素之前必須調用 MoveNext,這是為了允許空集合。Reset 方法(如果實現(xiàn))將移回起始位置,從而允許再次枚舉集合。重置主要用于組件對象模型 (COM) 互操作性;通常避免直接調用它,因為它不是普遍支持的(并且是不必要的,因為實例化新的枚舉器通常同樣容易)。

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

          public interface IEnumerable
          {
            IEnumerator GetEnumerator();
          }

          通過定義重新調整枚舉器的單個方法,IEnumerable 提供了靈活性,因為迭代邏輯可以復制到另一個類。此外,這意味著多個使用者可以一次枚舉集合,而不會相互干擾。您可以將 IEnumerable 視為“IEnumeratorProvider”,它是集合類實現(xiàn)的最基本的接口。

          以下示例說明了 IEnumerable 和 IEnumerator 的低級用法:

          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.

          但是,很少以這種方式直接調用枚舉器上的方法,因為 C# 提供了一個語法快捷方式:foreach 語句。這是使用 foreach 重寫的相同示例:

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

          IEnumerable<T> 和 IEnumerator<T>

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

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

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

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

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

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

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

          幸運的是,由于 foreach ,您很少需要編寫此類代碼。

          IEnumerable<T> 和 IDisposable

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

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

          進入邏輯等價物:

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

          何時使用非泛型接口

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

          在 IEnumerable 的情況下,您必須將此接口與 IEnumerable<T> 一起實現(xiàn),因為后者源自前者。但是,真正從頭開始實現(xiàn)這些接口的情況非常罕見:在幾乎所有情況下,都可以采用使用迭代器方法、Collection<T> 和 LINQ 的更高級別的方法。

          那么,作為消費者呢?在幾乎所有情況下,您都可以完全使用通用接口進行管理。不過,非泛型接口偶爾仍然很有用,因為它們能夠跨所有元素類型的集合提供類型統(tǒng)一。例如,以下方法以計算任何集合中的元素:

          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> 似乎是有效的。但是,對于值類型元素和未實現(xiàn) IEnumerable<T 的舊集合,這將失敗> - 一個例子是 Windows 窗體中的 ControlCollection。

          (稍微切線,您可能已經注意到我們示例中的潛在錯誤:引用將導致無限遞歸并使方法崩潰。我們可以通過使用 HashSet 最容易地解決這個問題(參見)。

          使用塊確保處置 - 有關IDisposable的更多信息,請參閱。

          實現(xiàn)枚舉接口

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

          • 支持 foreach 語句
          • 與任何期望標準集合的內容進行互操作
          • 滿足更復雜的集合接口的要求
          • 支持集合初始值設定項

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

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

          注意

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

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

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

          注意“黑魔法”:GetEnumerator 似乎根本不返回枚舉器!分析yield return 語句后,編譯器在后臺編寫一個隱藏的嵌套枚舉器類,然后重構 GetEnumerator 以實例化并返回該類。迭代器功能強大且簡單(廣泛用于實現(xiàn) LINQ-to-Object 的標準查詢運算符)。

          按照這種方法,我們還可以實現(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();
          }

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

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

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

          這是我們使用的方法:

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

          編寫 GetEnumerator 的最后一種方法是編寫一個直接實現(xiàn) IEnumerator 的類。這正是編譯器在后臺解析迭代器時所做的。(幸運的是,你很少需要自己走這么遠。下面的示例定義一個經過硬編碼以包含整數(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;
            }
          }

          注意

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

          請注意,對 MoveNext 的第一次調用應移動到列表中的第一個(而不是第二個)項。

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

          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() {}
            }
          }

          泛型示例更快,因為 IEnumerator<int>。當前不需要從 int 轉換為對象,因此避免了裝箱的開銷。

          ICollection 和 IList 接口

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

          顯示了這些接口的繼承層次結構??偨Y它們的最簡單方法如下:

          IEnumerable<T> (和 IEnumerable)

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

          ICollection<T> (和ICollection)

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

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

          提供最大功能(包括按索引/鍵進行“隨機”訪問)

          注意

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

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

          注意

          IList<T>不擴展IList的另一個更微妙的原因是,強制轉換為IList<T>將返回一個同時具有Add(T)和Add(object)成員的接口。這將有效地破壞靜態(tài)類型安全性,因為您可以使用任何類型的對象調用 Add。

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

          注意

          在整個 .NET 庫中應用單詞和的方式沒有的基本原理。例如,由于 IList<T> 是 ICollection<T> 的更實用版本,您可能期望類 List<T> 相應地比類 Collection<T> 功能更強大。事實并非如此。最好將術語和視為廣義同義詞,除非涉及特定類型。

          ICollection<T> 和 ICollection

          ICollection<T> 是可數(shù)對象集合的標準接口。它提供了確定集合大小(Count)、確定集合中是否存在項(包含)、將集合復制到數(shù)組(ToArray)以及確定集合是否為只讀(IsReadOnly)的功能。對于可寫集合,還可以從集合中添加、刪除和清除項目。并且因為它擴展了 IEnumerable<T> ,它也可以通過 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 在提供可計數(shù)集合方面類似,但它不提供更改列表或檢查元素的功能:

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

          非泛型接口還定義了幫助同步的屬性()— 這些屬性在泛型版本中被轉儲,因為線程安全不再被視為集合的固有屬性。

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

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

          IList<T> 和 IList

          IList<T> 是可按位置索引的集合的標準接口。除了繼承自ICollection<T>和IEnumerable<T>的功能外,它還提供了按位置(通過索引器)讀取或寫入元素以及按位置插入/刪除元素的功能:

          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 方法對列表執(zhí)行線性搜索,如果未找到指定的項,則返回 ?1。

          IList 的非泛型版本具有更多成員,因為它從 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 方法返回一個整數(shù),這是新添加項的索引。相比之下,ICollection<T> 上的 Add 方法具有 void 返回類型。

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

          警告

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

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

          這可能看起來很無懈可擊,但如果使用多維數(shù)組調用,它將引發(fā)異常。您可以使用此表達式在運行時測試多維數(shù)組(詳見):

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

          IReadOnlyCollection<T> 和 IReadOnlyList<T>

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

          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ù)僅用于輸出位置,因此將其標記為。例如,這允許將貓列表視為只讀的動物列表。相反,T 沒有被標記為與 ICollection<T> 和 IList<T> 的協(xié)變,因為 T 同時用于輸入和輸出位置。

          注意

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

          除了允許您協(xié)變地使用集合之外,只讀接口還允許類公開私有可寫集合的只讀視圖。我們在”中演示了這一點,以及更好的解決方案。

          IReadOnlyList<T>映射到Windows運行時類型IVectorView<T>。

          數(shù)組類

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

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

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

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

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

          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;



          內存中的數(shù)組

          因為 Array 是一個類,所以數(shù)組始終是(本身)引用類型,而不管數(shù)組的元素類型如何。這意味著語句 arrayB=arrayA 會產生兩個引用同一數(shù)組的變量。同樣,兩個不同的數(shù)組總是無法通過相等性測試,除非您使用來比較數(shù)組的每個元素:

          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ù)組可以通過調用 Clone 方法復制:arrayB=arrayA.Clone() 。但是,這會導致淺層克隆,這意味著僅復制數(shù)組本身表示的內存。如果數(shù)組包含值類型對象,則復制值本身;如果數(shù)組包含引用類型對象,則僅復制引用(導致兩個數(shù)組的成員引用相同的對象)。 演示了將以下代碼添加到示例中的效果:

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


          淺拷貝陣列

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

          盡管 Array 主要設計用于 32 位索引器,但它對 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 類上許多期望作為實例方法的方法實際上是靜態(tài)方法。這是一個奇怪的設計決策,這意味著在 Array 上查找方法時,您應該同時檢查靜態(tài)方法和實例方法。

          構造和索引

          創(chuàng)建和索引數(shù)組的最簡單方法是通過 C# 的語言構造:

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

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

          GetValue 和 SetValue 方法允許您訪問動態(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];

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

          GetValue 和 SetValue 也適用于編譯器創(chuàng)建的數(shù)組,它們在編寫可以處理任何類型和等級的數(shù)組的方法時很有用。對于多維數(shù)組,它們接受索引器:

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

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

           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
           }

          注意

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

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

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

          當一個數(shù)組被實例化時,無論是通過語言語法還是Array.CreateInstance,它的元素都會被自動初始化。對于具有引用類型元素的數(shù)組,這意味著寫入 null;對于具有值類型元素的數(shù)組,這意味著調用值類型的默認構造函數(shù)(有效地將成員“清零”)。Array 類還通過 Clear 方法按需提供此功能:

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

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

          列舉

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

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

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

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

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

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

          下面是用 Array.ForEach 重寫的第一個示例:

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

          長度和等級

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

          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 返回給定維度的長度(一維數(shù)組為 0),Length 和 LongLength 返回數(shù)組中元素的總數(shù) — 包括所有維度。

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

          搜索

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

          二進制搜索方法

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

          索引/上一個索引方法

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

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

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

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

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

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

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

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

          在下面的示例中,我們在字符串數(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 表達式縮短的相同代碼:

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

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

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

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

          排序

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

          // 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);

          這些方法中的每一個都還重載以接受以下內容:

          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

          下面說明了排序的最簡單用法:

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

          接受一對數(shù)組的方法通過重新排列每個數(shù)組的項目來工作,將排序決策基于第一個數(shù)組。在下一個示例中,數(shù)字及其相應的單詞都按數(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ù)組中的元素實現(xiàn)IComparable(參見中的)。這意味著可以對大多數(shù)內置 C# 類型(如前面示例中的整數(shù))進行排序。如果元素在本質上不具有可比性,或者您希望覆蓋默認排序,則必須為 Sort 提供用于報告兩個元素的相對位置的自定義比較提供程序。有一些方法可以做到這一點:

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

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

          在下面的示例中,我們對整數(shù)數(shù)組進行排序,使奇數(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 }

          注意

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

          反轉元素

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

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

          復制

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

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

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

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

          轉換和調整大小

          Array.ConvertAll創(chuàng)建并返回一個元素類型為TOutput的新數(shù)組,調用提供的轉換器委托來復制元素。轉換器定義如下:

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

          下面將浮點數(shù)數(shù)組轉換為整數(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)建一個新數(shù)組并復制元素,通過引用參數(shù)返回新數(shù)組。但是,其他對象中對原始數(shù)組的任何引用將保持不變。

          注意

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

          列表、隊列、堆棧和集

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

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

          List<T> 和 ArrayList

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

          在內部,List<T> 和 ArrayList 通過維護一個內部對象數(shù)組來工作,在達到容量時替換為更大的數(shù)組。附加元素是有效的(因為末尾通常有一個空閑插槽),但插入元素可能很慢(因為插入點之后的所有元素都必須移動才能形成空閑插槽),刪除元素(尤其是在開始附近)也是如此。與數(shù)組一樣,如果對已排序的列表使用 BinarySearch 方法,則搜索是有效的,但在其他方面效率低下,因為必須單獨檢查每個項目。

          注意

          如果 T 是值類型,則 List<T> 比 ArrayList 快幾倍,因為 List<T> 避免了裝箱和取消裝箱元素的開銷。

          List<T> 和 ArrayList 提供了接受現(xiàn)有元素集合的構造函數(shù):這些構造函數(shù)將現(xiàn)有集合中的每個元素復制到新的 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搜索和排序方法的實例版本。

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

          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 類需要笨拙的強制轉換,如以下示例所示:

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

          編譯器無法驗證此類強制轉換;以下內容編譯成功,但在運行時失?。?/span>

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

          注意

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

          如果導入 System.Linq 命名空間,則可以通過調用 Cast 然后調用 ToList 將 ArrayList 轉換為泛型列表:

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

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

          鏈接列表<T>

          LinkedList<T> 是一個通用的雙向鏈表(見)。雙向鏈表是節(jié)點鏈,其中每個節(jié)點引用前面的節(jié)點、之后的節(jié)點和實際的元素。它的主要優(yōu)點是元素始終可以有效地插入列表中的任何位置,因為它只涉及創(chuàng)建一個新節(jié)點并更新一些引用。但是,首先查找將節(jié)點插入的位置可能會很慢,因為沒有直接索引到鏈表中的內在機制;必須遍歷每個節(jié)點,并且無法進行二進制切碎搜索。



          LinkedList<T> 實現(xiàn)了 IEnumerable<T> 和 ICollection<T>(及其非通用版本),但不是 IList<T>因為不支持按索引訪問。列表節(jié)點通過以下類實現(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é)點時,可以指定其相對于另一個節(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);

          提供了類似的方法來刪除元素:

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

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

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

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

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

          最后,LinkedList<T> 支持復制到數(shù)組進行索引處理,并獲取一個枚舉器來支持 foreach 語句:

          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);

          隊列<T>和隊列

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

          雖然隊列是可枚舉的,但它們不實現(xiàn) IList<T> / IList ,因為成員不能通過索引直接訪問。但是,提供了一個 ToArray 方法,用于將元素復制到可以從中隨機訪問它們的數(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)

          隊列是使用根據(jù)需要調整大小的數(shù)組在內部實現(xiàn)的,與泛型 List 類非常相似。隊列維護直接指向頭和尾元素的索引;因此,排隊和取消排隊是非??焖俚牟僮鳎ǔ切枰獌炔空{整大?。?/span>

          堆疊<T>和堆疊

          堆棧<T> 和堆棧是后進先出 (LIFO) 數(shù)據(jù)結構,提供推送(將項目添加到堆棧頂部)和 Pop(從堆棧頂部檢索和刪除元素)的方法。還提供了非破壞性 Peek 方法,以及用于導出數(shù)據(jù)以進行隨機訪問的 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

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

          位數(shù)組

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

          BitArray 的索引器讀取和寫入單個位:

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

          有四種按位運算符方法(和、或、異或、和不是)。除了最后一個之外,所有都接受另一個位數(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í)行。
          • 它們不存儲重復的元素,并以靜默方式忽略添加的請求。
          • 不能按位置訪問元素。

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

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

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

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

          下面從現(xiàn)有集合構造 HashSet<char>,測試成員資格,然后枚舉集合(請注意沒有重復項):

          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> 的構造函數(shù)中的原因是該字符串實現(xiàn)了 IEnumerable<char>。

          真正有趣的方法是集合操作。以下集合操作具有,因為它們會修改集合

          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 將第二個集合中的所有元素添加到原始集合中(不包括重復項)。相交刪除不在兩個集中的元素。我們可以從我們的字符集中提取所有元音,如下所示:

          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 刪除除一個集合或另一個集合所特有的元素之外的所有元素:

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

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

          SortedSet<T> 提供 HashSet<T> 的所有成員,以及以下內容:

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

          SortedSet<T> 還在其構造函數(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

          字典

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

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

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

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

          字典類

          類型

          內部結構

          按索引檢索?

          內存開銷(每個項目的平均字節(jié)數(shù))

          速度:隨機插入

          速度:順序插入

          速度:按鍵檢索

          排序







          字典 <K,V>

          哈希表

          22

          30

          30

          20

          哈希表

          哈希表

          38

          50

          50

          30

          列表字典

          鏈表

          36

          50,000

          50,000

          50,000

          有序詞典

          哈希表 + 數(shù)組

          是的

          59

          70

          70

          40

          排序







          排序詞典 <K,V>

          紅/黑樹

          20

          130

          100

          120

          排序列表 <K,V>

          2x陣列

          是的

          2

          3,300

          30

          40

          排序列表

          2x陣列

          是的

          27

          4,500

          100

          180

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

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

          是集合中的元素數(shù)。

          IDictionary<TKey,TValue>

          IDictionary<TKey,TValue> 為所有基于鍵/值的集合定義了標準協(xié)議。它通過添加方法和屬性來訪問基于任意類型的鍵的元素,從而擴展了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
          }

          注意

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

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

          若要從字典中檢索項,請使用索引器或 TryGetValue 方法。如果鍵不存在,索引器將引發(fā)異常,而 TryGetValue 返回 false 。您可以通過調用 包含密鑰 來顯式測試成員資格;但是,如果您隨后檢索項目,則會產生兩次查找的費用。

          直接在 IDictionary<TKey,TValue> 上枚舉返回一系列 KeyValuePair 結構:

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

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

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

          目錄

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

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

          枚舉非泛型 IDictionary 將返回一系列 DictionaryEntry 結構:

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

          字典< TKey,TValue>和哈希表

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

          注意

          Dictionary<TKey,TValue>的非通用版本稱為Hashtable ;沒有稱為字典的非泛型類。當我們簡單地提到字典時,我們指的是通用的字典<TKey,TValue>類。

          字典實現(xiàn)了通用和非通用 IDictionary 接口,通用 IDictionary 公開。詞典其實是通用詞典的“教科書”實現(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

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

          字典可以處理任何類型的鍵,前提是它能夠確定鍵之間的相等性并獲取哈希碼。默認情況下,相等性是通過鍵的對象確定的。等于方法,偽唯一哈希代碼是通過密鑰的 GetHashCode 方法獲取的??梢酝ㄟ^重寫這些方法或在構造字典時提供 IEqualityComparer 對象來更改此行為。這樣做的一個常見應用是在使用字符串鍵時指定不區(qū)分大小寫的相等比較器:

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

          我們將在中進一步討論這個問題。

          與許多其他類型的集合一樣,可以通過在構造函數(shù)中指定集合的預期大小來稍微提高字典的性能,從而避免或減少對內部調整大小操作的需求。

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

          字典和哈希表的缺點是項目未排序。此外,不保留添加項目的原始順序。與所有字典一樣,不允許使用重復的鍵。

          注意

          當泛型集合在2005年被引入時,CLR團隊選擇根據(jù)它們所代表的內容(字典,列表)而不是它們內部實現(xiàn)的方式(Hashtable,ArrayList)來命名它們。雖然這很好,因為它給了他們以后更改實現(xiàn)的自由,但這也意味著(通常是選擇一種集合而不是另一種集合的最重要標準)不再包含在名稱中。

          有序詞典

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

          注意

          排序字典不是字典。

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

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

          列表詞典和混合詞典

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

          HybridDictionary是一個ListDictionary,在達到一定大小時會自動轉換為哈希表,以解決ListDictionary的性能問題。這個想法是在字典較小時獲得低內存占用,在字典較大時獲得良好的性能。但是,考慮到從一個詞典轉換到另一個詞典的開銷,以及詞典在這兩種情況下都不會過重或過慢的事實,一開始使用都不會受到不合理的影響。

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

          排序詞典

          .NET BCL 提供兩個內部結構化的字典類,以便其內容始終按鍵排序:

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

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

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

          SortedList<,> 在內部使用有序數(shù)組對實現(xiàn),提供快速檢索(通過二進制切碎搜索),但插入性能較差(因為需要移動現(xiàn)有值以便為新條目騰出空間)。

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

          這三個集合中沒有一個允許重復鍵(就像所有一樣)。

          下面的示例使用反射將 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);

          下面是第一個枚舉的結果:

          Equals
          GetHashCode
          GetType
          ReferenceEquals
          ToString

          下面是第二個枚舉的結果:

          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

          請注意,我們通過其索引器填充了字典。如果我們改用 Add 方法,它將引發(fā)異常,因為我們反映的對象類重載 Equals 方法,并且您不能將相同的鍵添加到字典中兩次。通過使用索引器,后面的條目將覆蓋前面的條目,從而防止此錯誤。

          注意

          您可以通過將每個值元素設置為列表來存儲同一鍵的多個成員:

          SortedList <string, List<MethodInfo>>

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

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

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

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

          可定制的集合和代理

          前面各節(jié)中討論的集合類很方便,因為您可以直接實例化它們,但它們不允許您控制在集合中添加或刪除項時發(fā)生的情況。對于應用程序中的強類型集合,有時需要此控件。例如:

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

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

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

          Collection<T> 和 CollectionBase

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

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

          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; }
          }

          虛擬方法提供了一個網關,您可以通過該網關“掛鉤”來更改或增強列表的正常行為。受保護的 Items 屬性允許實現(xiàn)者直接訪問“內部列表”,該列表用于在內部進行更改,而無需觸發(fā)虛擬方法。

          虛擬方法不需要被覆蓋;在需要更改列表的默認行為之前,可以將它們單獨保留。以下示例演示了 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();
          }

          就目前而言,動物收藏并不比一個簡單的列表更實用<動物> ;它的作用是為將來的擴展提供基礎。為了說明這一點,現(xiàn)在讓我們將一個 Zoo 屬性添加到 Animal,以便它可以引用它所在的 Zoo 并重寫 Collection<Animal 中的每個虛擬方法>以維護該屬性:

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

          收藏基地

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

          KeyedCollection<TKey,TItem> and DictionaryBase

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

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

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

          因為它子類 Collection<> ,鍵控集合繼承了 Collection<> 的所有功能,除了在構造中指定現(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 是實現(xiàn)者重寫的內容,以便從基礎對象獲取項的鍵。如果項的鍵屬性發(fā)生更改,則必須調用 ChangeItemKey 方法,以便更新內部字典。Dictionary 屬性返回用于實現(xiàn)查找的內部字典,該字典是在添加第一項時創(chuàng)建的??梢酝ㄟ^在構造函數(shù)中指定創(chuàng)建閾值來更改此行為,延遲創(chuàng)建內部字典直到達到閾值(在此期間,如果按鍵請求項,則執(zhí)行線性搜索)。不指定創(chuàng)建閾值的一個很好的理由是,擁有有效的字典對于通過字典的 Keys 屬性獲取密鑰的 ICollection<> 很有用。然后,可以將此集合傳遞給公共屬性。

          KeyedCollection<,> 最常見的用途是提供可按索引和名稱訪問的項集合。為了演示這一點,讓我們重新訪問動物園,這次將 AnimalCollection 實現(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

          詞典庫

          KeyedCollection的非通用版本稱為DictionaryBase。這個遺留類采用了一種非常不同的方法,因為它實現(xiàn)了 IDictionary 并使用笨拙的鉤子方法,如 CollectionBase : OnInsert , OnInsertComplete , OnSet , OnSetComplete , OnRemove , OnRemoveComplete , OnClear 和 OnClearComplete(以及另外的 OnGet)。與采用 KeyedCollection 方法相比,實現(xiàn) IDictionary 的主要優(yōu)點是,您無需對其進行子類化即可獲取密鑰。但是由于DictionaryBase的目的是被子類化,所以它根本沒有優(yōu)勢。KeyedCollection 中改進的模型幾乎可以肯定是因為它是在幾年后編寫的,事后諸葛亮。DictionaryBase 最好被認為是對向后兼容性有用的。

          只讀集合<T>

          ReadOnlyCollection<T> 是一個包裝器或,它提供集合的只讀視圖。這對于允許類公開對集合的只讀訪問權限非常有用,該集合仍然可以在內部更新。

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

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

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

          盡管 Names 返回只讀接口,但使用者仍然可以在運行時向下轉換為 List<string> 或 IList<string>然后在列表中調用 Add 、Remove 或 Clear。ReadOnlyCollection<T> 提供了更強大的解決方案:

          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)建集合的只讀視圖。限制寫入()集合或任何其他對象的能力可以簡化軟件并減少錯誤。

          集合通過提供初始化后根本無法修改的集合來擴展此原則。如果需要將項添加到不可變集合,則必須實例化新集合,而不動舊集合。

          不變性是的標志,具有以下:

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

          不可變性的缺點是,當您需要進行更改時,必須創(chuàng)建一個全新的對象。這會導致性能下降,盡管我們在本節(jié)中討論了緩解策略,包括重用原始結構部分的能力。

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

          類型

          內部結構



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

          數(shù)組



          不可變列表<T>

          AVL 樹



          不可變字典<K,V>

          AVL 樹



          ImmutableHashSet<T>

          AVL 樹



          ImmutableSortedDictionary<K,V>

          AVL 樹



          ImmutableSortedSet<T>

          AVL 樹



          不可變堆棧<T>

          鏈表



          不可變隊列<T>

          鏈表



          ImmutableArray<T> 和 ImmutableList<T> 類型都是 List<T> 的不可變版本。兩者都執(zhí)行相同的工作,但具有不同的性能特征,我們在中討論過。

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

          注意

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

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

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

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

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

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

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

          操作不可變集合

          Add 方法返回一個包含現(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 方法以相同的方式運行,返回已刪除項的新集合。

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

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

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

          建設者

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

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

          還可以使用生成器對現(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ù)不可變集合在內部使用 ,這允許添加/刪除操作重用原始內部結構的部分內容,而不必從頭開始重新創(chuàng)建整個內容。這將添加/刪除操作的開銷從潛在的(具有大型集合)減少到,但代價是讀取操作速度變慢。最終結果是,大多數(shù)不可變集合在讀取和寫入方面都比可變集合慢。

          受影響最嚴重的是 ImmutableList<T> ,對于讀取和添加操作,它比 List<T 慢 10 到 200 倍>(取決于列表的大?。?。這就是ImmutableArray<T>存在的原因:通過在內部使用數(shù)組,它避免了讀取操作的開銷(其性能可與普通可變數(shù)組相媲美)。另一方面,對于添加操作,它比(甚至)ImmutableList<T>慢,因為原始結構都不能重用。

          因此,當您想要不受阻礙的性能并且不希望后續(xù)調用添加或刪除(不使用構建器)時,ImmutableArray<> 是可取的。

          類型

          讀取性能

          提高性能

          不可變列表<T>

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

          非???/span>

          非常慢

          注意

          在 ImmutableArray 上調用 Remove 比在 List<T 上調用 Remove 更昂貴>即使在刪除第一個元素的最壞情況下也是如此,因為分配新集合會給垃圾回收器帶來額外的負載。

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

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

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

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

          插入平等和秩序

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

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

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

          • 它們允許您切換替代等同或比較行為。
          • 它們允許您使用具有本質上不相等或可比的鍵類型的字典或排序集合。

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

          IEqualityComparer 和 IEqualityComparer<T>

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

          IComparer 和 IComparer<T>

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

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

          此外,還有稱為 IStructuralEquatable 和 IStructuralComparable 的接口,它們允許對類和數(shù)組進行結構比較的選項。

          IEqualityComparer 和 EqualComparer

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

          回想一下基于哈希表的字典的要求。對于任何給定的鍵,它需要回答兩個問題:

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

          相等比較器通過實現(xiàn) IEqualityComparer 接口來回答這些問題:

          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àn)其中一個或兩個接口(實現(xiàn)這兩個接口可提供最大的互操作性)。因為這有點乏味,所以另一種方法是對抽象的 EqualityComparer 類進行子類類,定義如下:

          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 實現(xiàn)了這兩個接口;你的工作只是重寫這兩個抽象方法。

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

          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();
          }

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

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

          因為我們沒有覆蓋對象。等于,正常引用類型相等語義適用:

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

          在中使用這些客戶而不指定相等比較器時,相同的默認相等語義適用:

          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

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

          平等比較<T>。違約

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

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

          ReferenceEqualityComparer.Instance (.NET 5+)

          從 .NET 5 開始,ReferenceEqualityComparer.Instance 返回始終應用引用相等的相等比較器。對于值類型,其 Equals 方法始終返回 false。

          IComparer 和 Comparer

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

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

          以下是 IComparer 接口定義:

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

          與相等比較器一樣,有一個抽象類可以子類型化而不是實現(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
          }

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

          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);
            }
          }

          對象。等于檢查確保我們永遠不會與等于方法相矛盾。調用靜態(tài)對象。在這種情況下,等于方法比調用 x.Equals 更好,因為如果 x 為空,它仍然有效!

          以下是我們的 PriorityComparer 如何用于對列表進行排序:

          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 |

          在下一個示例中,SurnameComparer 允許您按適合電話簿列表的順序對姓氏字符串進行排序:

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

          因為 StringComparer 是抽象的,所以您可以通過其靜態(tài)屬性獲取實例。StringComparer.Ordinal 鏡像字符串相等比較的默認行為,StringComparer.CurrentCulture 鏡像順序比較的默認行為。以下是其所有靜態(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)建了一個不區(qū)分大小寫的序號字典,使得 dict[“Joe”] 和 dict[“JOE”] 的含義相同:

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

          在下一個示例中,使用澳大利亞英語對名稱數(shù)組進行排序:

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

          最后一個例子是我們在上一節(jié)中編寫的 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));
            }
          }

          等同和等同

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

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

          您傳入的 IEqualityComparer / IComparer 將應用于復合對象中的每個單獨元素。我們可以通過使用數(shù)組來演示這一點。在下面的示例中,我們比較兩個數(shù)組的相等性,首先使用默認的 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

          這是另一個示例:

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

          近段時間,不約而同的,網站論壇和煙臺方法學員中都有提出這樣的問題。

          比如:

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

          這些問題,都有個特點, 提問的時候先把編程語言給限定了。

          而咱就不太有辦法拒絕。畢竟,人家有可能是在完成一個更復雜的工作,已經在選定的語言下實現(xiàn)了大部分的功能,現(xiàn)在就在這一點點功能搞不定被卡住了,過不去了,才來求助的。

          而如果不指定語言的話,其實我都早就有答案,特別是VBS的解決方案,都寫在《西門子WINCC入門到精通》的書里了,所以只需要從書柜里把我自己的書拿出來, 找到頁碼,把頁碼號告訴對方就可以了。

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

          當然,我也發(fā)現(xiàn)了我寫書時候遺漏的該寫而未寫的技巧知識點??赡軡撘庾R里面,我自己覺得反正另一條路上有解決方案了,這邊這一條就沒必要去重復啰嗦實現(xiàn)了。特別是C腳本,對西門子來說自從20年前的WINCC版本支持VBS之后,官方逐漸在弱化C腳本的地位, 我自己也逐漸淡化對其的研究和使用。想一碗水端平是永遠不可能的。

          在此先提醒大家, 提問問題的時候,盡可能不要限定編程語言。那樣的答案多的是,可以唾手可得。而非要限定語言了,尋找起來就會有一些難度。

          所以,我這里做了個例子,進行了測試,可以把這些坑填上了。 也算是對我自己著作中遺漏部分的補充。

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

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

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

          • 按鈕11的單擊鼠標中的VBS程序為:

          Sub OnClick(ByVal Item)

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

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

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

          End Sub

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

          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

          這里取了一個不存在的aa后綴的變量,然而也絲毫不影響功能。 因為語法本身獲取的是變量名字,對變量是否有值是否合法根本不在意。

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

          標準的用法當然不能每次都通過腳本來獲取,而是會在子窗口打開時即執(zhí)行腳本,獲取到前綴后,賦值到一個靜態(tài)文本中,窗口中任何需要的地方,可以通過讀取文本內容得到。

          Sub OnOpen()

          'MsgBOX(Parent.TagPrefix)

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

          End Sub

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

          • 按鈕12的單擊鼠標中的C程序為:

          #include "apdefap.h"

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

          {

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

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

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

          }

          窗口內獲取按鈕的腳本:

          #include "apdefap.h"

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

          {

          char Name[20];

          HWND hwnd=NULL;

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

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

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

          }

          畫面打開事件中文本內容得到的方法:

          #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腳本實現(xiàn)的核心是一個GetParentPicture的函數(shù),可以得到窗口的父窗口的文件名字。而這個函數(shù)是個神仙函數(shù),各種幫助資料中都沒有見過介紹。所以只有從已有的使用演示程序中獲取。

          • 窗口中按鈕11的單擊鼠標中的VBS程序為:

          Sub OnClick(ByVal Item)

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

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

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

          End Sub

          • 窗口中按鈕12的單擊鼠標中的C程序為:

          #include "apdefap.h"

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

          {

          char szParentPicture[512];

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

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

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

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

          }

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

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

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

          而事實上,隨著標準化模塊化的推進,這些技能已經很少用到了,因為早就封裝完善在模塊中了。

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

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

          雖然我自己在成長過程中,是摸著石頭過河每個輪子逐個造過來的,遇到任何問題,也都抑制不住要自己親自造輪子的沖動。 輪子的每一個細節(jié),如果不親自掌握,就會抓耳撓腮睡覺都不安心。

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

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

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

          這些例程我都已經寫文章推薦過多次了。這回就不再提供鏈接以及親自提供文件了,而只提供名字,需要者自己辛苦一點去找到并學習了解。 看來太容易得到的資料通常都不珍惜,只有自己辛苦一點,千辛萬苦得到的才會更加倍的去學習。

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

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

          1.新建一個工程

          計算機屬性勾選變量記錄運行系統(tǒng)與圖形運行系統(tǒng)

          2.新建二個變量


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


          4.新建曲線彈窗,如下圖添加曲線和標尺控件

          曲線控件名為控件1


          5.新建一個主畫面

          按鈕1腳本為:

          SetPropChar(lpszPictureName,"畫面窗口1","CaptionText","曲線1");//設置標題

          SetPropChar(lpszPictureName,"畫面窗口1","Visible","0");//顯示關閉

          SetPropChar(lpszPictureName,"畫面窗口1","Visible","1");//顯示打開

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

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

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

          按鈕2腳本為:

          SetPropChar(lpszPictureName,"畫面窗口1","CaptionText","曲線2");//設置標題

          SetPropChar(lpszPictureName,"畫面窗口1","Visible","0");//顯示關閉

          SetPropChar(lpszPictureName,"畫面窗口1","Visible","1");//顯示打開

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

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

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

          6.運行主界面



          注意:如果提示無法建立任何數(shù)據(jù)連接,請轉至第一步打開變量記錄運行系統(tǒng)

          測試成功。


          主站蜘蛛池模板: 一区二区三区福利视频| 日韩制服国产精品一区| 风流老熟女一区二区三区| 成人毛片无码一区二区| 国产精品亚洲产品一区二区三区| 亚洲成av人片一区二区三区| 国产一区二区电影| 福利片福利一区二区三区| 色窝窝无码一区二区三区成人网站| 久久国产精品一区二区| 国产精品资源一区二区| 国产无线乱码一区二三区| 在线视频亚洲一区| 狠狠色综合一区二区| 久久国产精品视频一区| 亚洲一区AV无码少妇电影☆| 一区二区三区亚洲视频| 天堂不卡一区二区视频在线观看| 精品人妻码一区二区三区| 成人无码AV一区二区| 国产精品被窝福利一区| 精品亚洲一区二区三区在线观看 | 一区二区在线视频免费观看| 成人免费视频一区二区三区 | 熟妇人妻一区二区三区四区| 国产精品女同一区二区久久| 国产一区二区三区日韩精品| 99国产精品欧美一区二区三区| 精品女同一区二区三区免费站| 一区二区视频在线观看| 久久精品免费一区二区| 亚洲日韩AV一区二区三区四区| 成人毛片无码一区二区| 日韩在线一区二区三区免费视频| 婷婷国产成人精品一区二| 国产成人精品视频一区二区不卡| 少妇人妻精品一区二区三区| 亚洲AV成人精品日韩一区| 亚洲Av永久无码精品一区二区 | 精品日韩一区二区三区视频| 国产一区二区三区露脸|