欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解C#中Dictionary<TKey,TValue>的存儲結(jié)構(gòu)

 更新時間:2023年11月20日 15:10:14   作者:彭澤0902  
無論是實(shí)際的項(xiàng)目中,還是在我們學(xué)習(xí)的過程中,都會重點(diǎn)的應(yīng)用到Dictionary<TKey,?TValue>這個存儲類型,所以本文就來為大家介紹一下這一存儲結(jié)構(gòu)的相關(guān)知識,希望對大家有所幫助

無論是實(shí)際的項(xiàng)目中,還是在我們學(xué)習(xí)的過程中,都會重點(diǎn)的應(yīng)用到Dictionary<TKey, TValue>這個存儲類型。每次對Dictionary<TKey, TValue>的添加都包含一個值和與其關(guān)聯(lián)的鍵, 使用鍵檢索值的速度非???,接近 O (1) ,因?yàn)?nbsp;Dictionary<TKey, TValue> 類是作為哈希表實(shí)現(xiàn)的。首先我們來從一個簡單的例子開始,以下是對一個字典的創(chuàng)建和賦值。

Dictionary<int, string> openWith = new Dictionary<int, string>();
openWith.Add(1000, "key值為1000");
openWith.Add(1001, "key值為1001");

相信絕大部分的開發(fā)人員對以上示例不是會陌生,那么Dictionary<TKey, TValue>的實(shí)現(xiàn)原理是什么樣的呢?在字典的初始化、賦值、取值、擴(kuò)容的實(shí)現(xiàn)原理是什么樣的呢?很多時候我們需要知其然,更需要知其所以然。接下來我們將從其內(nèi)存的存儲的數(shù)據(jù)結(jié)構(gòu)、取值的邏輯、擴(kuò)容原則等幾個視角進(jìn)行仔細(xì)的了解 。那我們就沿著CoreFX中Dictionary<TKey, TValue>的實(shí)現(xiàn)源碼來做一個簡單的學(xué)習(xí)和思考,這里需要特別注意一下:

學(xué)習(xí)和分析源碼時,不要先入為主,要按照框架和源碼的邏輯進(jìn)行解讀,記錄下不懂的地方重點(diǎn)分析,最后將整個邏輯串聯(lián)起來。如果我們一開始就設(shè)定了邏輯為A-B-C,但是讀到一個階段的時候發(fā)現(xiàn)變成了C-B-A,這個時候就無法再繼續(xù)進(jìn)行下去,因?yàn)榫唧w的實(shí)現(xiàn)過程中會有很多因素造成局部調(diào)整,我們可以在解讀完畢之后,將實(shí)際的邏輯與個人前期理解的邏輯的差異進(jìn)行比較,找出原因并做分析。

一、Dictionary<TKey, TValue>初始化

Dictionary<TKey, TValue>的構(gòu)造方法較多,我們來看一下其中的基礎(chǔ)實(shí)現(xiàn)方法,首先看一下對應(yīng)的源碼(源碼中不必要的部分已經(jīng)做了部分刪減,保留了核心的實(shí)現(xiàn)邏輯)。

public Dictionary(int capacity, IEqualityComparer<TKey>? comparer)
  {
      if (capacity > 0) Initialize(capacity);
      if (!typeof(TKey).IsValueType)
      {
         _comparer = comparer ?? EqualityComparer<TKey>.Default;
         if (typeof(TKey) == typeof(string) && NonRandomizedStringEqualityComparer.GetStringComparer(_comparer!) is IEqualityComparer<string> stringComparer)
         {
          _comparer = (IEqualityComparer<TKey>)stringComparer;
         }
      }
      else if (comparer is not null && comparer != EqualityComparer<TKey>.Default)
      {
         _comparer = comparer;
     }
 }

以上的實(shí)現(xiàn)邏輯重點(diǎn)包含了兩個部分,第一部分:對Dictionary<TKey, TValue>的容量初始化;第二部分是Dictionary<TKey, TValue>的IEqualityComparer? comparer的初始化,本文重點(diǎn)是對Dictionary<TKey, TValue>的存儲結(jié)構(gòu)進(jìn)行分析,涉及到比較器的實(shí)現(xiàn)邏輯,將放在后續(xù)的章節(jié)中進(jìn)行重點(diǎn)介紹。

我們接下來看一下Initialize()的實(shí)現(xiàn)邏輯進(jìn)行一個簡單的介紹,首先一起來看一下對應(yīng)的源碼實(shí)現(xiàn)(非必要部分已做刪減,方便大家可以直觀的查看)。

private int Initialize(int capacity)
{
  int size = HashHelpers.GetPrime(capacity);
  int[] buckets = new int[size];
  Entry[] entries = new Entry[size];
  _freeList = -1;
#if TARGET_64BIT
  _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)size);
#endif
  _buckets = buckets;
  _entries = entries;
  return size;
}

從上面的源碼可以看出,根據(jù)傳入的capacity參數(shù)來設(shè)定字典對應(yīng)的相關(guān)容量大小,其中包含兩部分,第一部分: 根據(jù)設(shè)定的容量(capacity)大小,計(jì)算對應(yīng)的buckets和entries大小,關(guān)于為什么使用buckets和entries兩個數(shù)組結(jié)構(gòu),我們將在下一節(jié)重點(diǎn)介紹;第二部分:判斷當(dāng)前機(jī)器的位數(shù),計(jì)算對應(yīng)的_fastModMultiplier。我們看一下HashHelpers.GetPrime(capacity)的計(jì)算邏輯。(該類在System.Collections命名空間下,其對應(yīng)的類型定義為:internal static partial class HashHelpers)

public static int GetPrime(int min)
{
  foreach (int prime in Primes)
  {
    if (prime >= min) return prime;
    for (int i = (min | 1); i < int.MaxValue; i += 2)
    {
        if (IsPrime(i) && ((i - 1) % HashPrime != 0)) return i;
     }
     return min;
   }
}

HashHelpers用于計(jì)算和維護(hù)哈希表容量的素?cái)?shù)值,為什么哈希表需要使用素?cái)?shù)?主要是為了減少哈希沖突(hash collisions)的發(fā)生,素?cái)?shù)的選擇能夠減少共同的因子,減小哈希沖突的可能性。此外,選擇素?cái)?shù)還能夠確保在哈希表的容量變化時,不容易出現(xiàn)過多的重復(fù)。如果容量選擇為一個合數(shù)(非素?cái)?shù)),那么在容量變化時,可能會導(dǎo)致新容量與舊容量有相同的因子,增加哈希沖突的風(fēng)險。

接下來我們沿著GetPrime()的調(diào)用關(guān)系來看整個哈希表容量的計(jì)算邏輯,HashHelpers設(shè)定了一個Primes[]的只讀素?cái)?shù)數(shù)組,具體的元素如下,至于什么使用這樣的素?cái)?shù)的數(shù)組,主要是這些素?cái)?shù)在實(shí)踐中已經(jīng)被證明是有效的,適用于許多常見的使用場景,更多的是有助于在哈希表等數(shù)據(jù)結(jié)構(gòu)中提供更好的性能。

internal static ReadOnlySpan<int> Primes => new int[]
{
  3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
  1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
  17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
  187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
  1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
};

GetPrime()會首先循環(huán)Primes[],依次判斷設(shè)定的min大小與素?cái)?shù)表元素的關(guān)系,若素?cái)?shù)表中的元素大于min,則直接去對應(yīng)的素?cái)?shù),無需后續(xù)的計(jì)算,如果設(shè)置的min不在預(yù)定的素?cái)?shù)表中,則進(jìn)行素?cái)?shù)的計(jì)算。關(guān)于素?cái)?shù)的計(jì)算邏輯,借助本文開頭的Dictionary<TKey, TValue>的定義和賦值進(jìn)行說明,首先對min和1進(jìn)行按位或運(yùn)算,初始化過程中未對capacity賦值時,則(min | 1)為1,對進(jìn)行位運(yùn)算后的i值校驗(yàn)是否符合素?cái)?shù)定義,再進(jìn)行((i - 1) % HashPrime != 0)運(yùn)算,其中HashPrime = 101,用于在哈希算法中作為質(zhì)數(shù)因子(101是一個相對小的質(zhì)數(shù),可以減少哈希碰撞的可能性,并且在計(jì)算哈希時更加高效),對于初始化未設(shè)置容量的Dictionary<TKey, TValue>,計(jì)算獲取得到的容量為int size=3。(即3*4*8=72(bit))

(注意:對于已設(shè)定了capacity的Dictionary,按照以上的邏輯進(jìn)行計(jì)算對應(yīng)的size值。這里就不再做過多介紹)

計(jì)算獲取到size值后,設(shè)置空閑列表為-1(_freeList = -1)。根據(jù)編譯時的運(yùn)行機(jī)器的位數(shù)進(jìn)行分類處理,若機(jī)器為非64位,則對buckets和entries兩個數(shù)組進(jìn)行初始化。若機(jī)器為64位是,則需要進(jìn)行重新計(jì)算,獲取_fastModMultiplier,其計(jì)算邏輯如下:

public static ulong GetFastModMultiplier(uint divisor) => ulong.MaxValue / divisor + 1;

以上的計(jì)算結(jié)果返回除數(shù)的近似倒數(shù),計(jì)算用于快速取模運(yùn)算的乘法因子。

通過以上的計(jì)算過程,我們可以對Dictionary<TKey, TValue>的容量計(jì)算有一個簡單的認(rèn)識,接下來我們來具體看一下用于存儲數(shù)據(jù)和哈希索引的兩個數(shù)組。

二、Dictionary<TKey, TValue>的存儲基礎(chǔ)結(jié)構(gòu)

對于Dictionary<TKey, TValue>的兩個重要數(shù)組buckets和entries,我們來具體的分析一下。首先來看一下Entry[]?_entries的實(shí)際的數(shù)據(jù)結(jié)構(gòu):

private struct Entry
{
  public uint hashCode;
  public int next;
  public TKey key;
  public TValue value;
}

在Dictionary<TKey, TValue>中實(shí)際存儲數(shù)據(jù)的結(jié)構(gòu)是Entry[],其中數(shù)組的每個元素是一個Entry,該類型為一個結(jié)構(gòu)體,用于在哈希表內(nèi)部存儲每個鍵值對的信息,其中定義的key和value則是我們在設(shè)置字典時添加的鍵值對,那么對于另外兩個屬性需要重點(diǎn)分析一下。

hashCode為在添加key時,將key進(jìn)行計(jì)算獲取得到的哈希值,哈希值的計(jì)算過程中,需要對key進(jìn)行按類別進(jìn)行計(jì)算,C#中對數(shù)值類型、字符串、結(jié)構(gòu)體、對象的哈希值計(jì)算邏輯都不相同,其中對于"數(shù)值類型"的哈希值計(jì)算邏輯為"數(shù)字類型的哈希碼生成邏輯通常是將數(shù)字類型的值轉(zhuǎn)換為整數(shù),然后將該整數(shù)作為哈希碼。"對于字符串的哈希值計(jì)算邏輯為"默認(rèn)的字符串哈希碼計(jì)算方式采用了所謂的“Jenkins One-at-a-Time Hash”算法的變體。"對于結(jié)構(gòu)體和對象的哈希值計(jì)算邏輯就不做具體介紹。

next通常用于處理哈希沖突,即多個鍵具有相同的哈希碼的情況。next是一個索引,指向哈希表中下一個具有相同哈希碼的元素。其中next=-1時,表示鏈表結(jié)束;next=-2 表示空閑列表的末尾,next=-3 表示在空閑列表上的索引 0,next=-4 表示在空閑列表上的索引 1,后續(xù)則依次類推。

Entry通過使用結(jié)構(gòu)體而不是類,可以減少內(nèi)存開銷,因?yàn)榻Y(jié)構(gòu)體是值類型,而類是引用類型。結(jié)構(gòu)體在棧上分配,而類在堆上分配。

以上介紹了Entry的結(jié)構(gòu)和對應(yīng)的屬性字段,接下來我們再來看一下int[] buckets的結(jié)構(gòu)和計(jì)算邏輯,buckets是一個簡單的int類型的數(shù)組,這樣的數(shù)組通常用于存儲哈希桶的信息。每個桶實(shí)際上是一個索引,指向一個鏈表或鏈表的頭部,用于解決哈希沖突。

private ref int GetBucket(uint hashCode)
{
   int[] buckets = _buckets!;
 #if TARGET_64BIT
   return ref buckets[HashHelpers.FastMod(hashCode, (uint)buckets.Length, _fastModMultiplier)];
 #else
   return ref buckets[(uint)hashCode % buckets.Length];
 #endif
 }

GetBucket()用于在哈希表中獲取桶索引,其中參數(shù)hashCode為key對應(yīng)的哈希碼,在64位目標(biāo)體系結(jié)構(gòu)下,使用 HashHelpers.FastMod 方法進(jìn)行快速模運(yùn)算,而在32位目標(biāo)體系結(jié)構(gòu)下,使用普通的取模運(yùn)算。那么為什么在Dictionary<TKey, TValue>中維護(hù)一個用來存儲哈希表的桶呢?主要有以下4個目的:

(1)、解決哈希沖突:兩個或多個不同的鍵經(jīng)過哈希函數(shù)得到相同的哈希碼,導(dǎo)致它們應(yīng)該存儲在哈希表的相同位置。通過使用桶,可以在同一個位置存儲多個元素,解決了哈希沖突的問題。

(2)、提供快速查找:通過哈希函數(shù)計(jì)算鍵的哈希碼,然后將元素存儲在哈希表的桶中,可以在常數(shù)時間內(nèi)(平均情況下)定位到存儲該元素的位置,實(shí)現(xiàn)快速的查找。

(3)、支持高效的插入和刪除:當(dāng)插入元素時,通過哈希函數(shù)確定元素應(yīng)該存儲的桶,然后將其添加到桶的鏈表或其他數(shù)據(jù)結(jié)構(gòu)中。當(dāng)刪除元素時,同樣可以快速定位到存儲元素的桶,并刪除該元素。

(4)、平衡負(fù)載:哈希表的性能與負(fù)載因子相關(guān),而負(fù)載因子是元素?cái)?shù)量與桶數(shù)量的比值。使用適當(dāng)數(shù)量的桶可以幫助平衡負(fù)載,防止哈希表變得過度擁擠,從而保持其性能。在不同的哈希表實(shí)現(xiàn)可能使用不同的數(shù)據(jù)結(jié)構(gòu),如鏈表、樹等,C#的Dictionary中使用一個int[]維護(hù)這個哈希表的桶索引。

三、Dictionary<TKey, TValue>的TryAdd的實(shí)現(xiàn)方式

以上主要介紹了Dictionary<TKey, TValue>的初始化、數(shù)據(jù)對應(yīng)的存儲和哈希表桶索引的存儲結(jié)構(gòu),現(xiàn)在我們具體看一下Dictionary<TKey, TValue>的添加元素的實(shí)現(xiàn)方式,下面對C#的實(shí)現(xiàn)代碼進(jìn)行了精簡,刪除當(dāng)前并不關(guān)注的部分。

本文實(shí)例中對key賦值的為整數(shù)類型,部分對于非數(shù)值類型、調(diào)試代碼等進(jìn)行刪減。(由于對于對象或者設(shè)置了比較器邏輯相對繁瑣,將在下文中進(jìn)行介紹)

private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
{
  Entry[]? entries = _entries;
  uint hashCode = (uint) key.GetHashCode() ;
  uint collisionCount = 0;
  ref int bucket = ref GetBucket(hashCode);
  int i = bucket - 1;
  int index;
  if (_freeCount > 0)
  {
    index = _freeList;
    _freeList = StartOfFreeList - entries[_freeList].next;
    _freeCount--;
  }
  else
  {
    int count = _count;
    if (count == entries.Length)
    {
       Resize();
       bucket = ref GetBucket(hashCode);
     }
     index = count;
     _count = count + 1;
      entries = _entries;
   }
   
   ref Entry entry = ref entries![index];
   entry.hashCode = hashCode;
   entry.next = bucket - 1; 
   entry.key = key;
   entry.value = value;
   bucket = index + 1; 
   _version++;
   
 return true;
}

以上的源碼中的實(shí)現(xiàn)邏輯中核心包含3個部分,分別是計(jì)算hashCode、計(jì)算哈希表桶索引的bucket、Dictionary擴(kuò)容,上一節(jié)中已經(jīng)介紹了前兩個實(shí)現(xiàn)邏輯,本節(jié)重點(diǎn)介紹Dictionary<TKey, TValue>的擴(kuò)容邏輯,我們來看一下Resize()的實(shí)現(xiàn)邏輯。

private void Resize() => Resize(HashHelpers.ExpandPrime(_count), false);

private void Resize(int newSize, bool forceNewHashCodes)
{
   Entry[] entries = new Entry[newSize];
   int count = _count;
   Array.Copy(_entries, entries, count);
   _buckets = new int[newSize];
#if TARGET_64BIT
   _fastModMultiplier = HashHelpers.GetFastModMultiplier((uint)newSize);
#endif
   for (int i = 0; i < count; i++)
   {
      if (entries[i].next >= -1)
      {
        ref int bucket = ref GetBucket(entries[i].hashCode);
        entries[i].next = bucket - 1;
        bucket = i + 1;
       }
    }
   _entries = entries;
}

由以上的源碼(不涉及數(shù)值類型的部分做了刪減)可以看出,HashHelpers.ExpandPrime(_count)計(jì)算新的Entry[]大小,那我們來具體看一下這個新的數(shù)組大小的計(jì)算邏輯是如何實(shí)現(xiàn)的。

public static int ExpandPrime(int oldSize)
{
   int newSize = 2 * oldSize;
   if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize) return MaxPrimeArrayLength;
   return GetPrime(newSize);
}

對于新的entries數(shù)組的擴(kuò)容,首先按照原始數(shù)組大小*2,那么對于能夠擴(kuò)容的最大數(shù)值為MaxPrimeArrayLength=0x7FFFFFC3,對應(yīng)32字節(jié)的最大值。計(jì)算新的數(shù)組大小時,會基于原始數(shù)組2倍的情況下,再取對應(yīng)的最少素?cái)?shù)相乘,即:realSize=2*oldSize*y(素?cái)?shù)表中的最少素?cái)?shù))。

【備注:其實(shí)在整個C#的擴(kuò)容邏輯中,絕大數(shù)大都是按照2倍進(jìn)行擴(kuò)容(按照2倍擴(kuò)容的方式存在一定的弊端,假設(shè)第n次擴(kuò)容分配了2^n的空間(省略常數(shù)C),那么之前釋放掉的空間總和為:1 + 2 + 2^2 + ... + 2^(n-1) = 2^n - 1 正好放不下2^n的空間。這樣導(dǎo)致的結(jié)果就是需要操作系統(tǒng)不斷分配新的內(nèi)存頁,并且數(shù)組的首地址也在不斷變大,造成緩存缺失?!?/p>

Array.Copy(_entries, entries, count)擴(kuò)容后的新數(shù)組會將對舊數(shù)組進(jìn)行Copy()操作,在C#中每次對數(shù)組進(jìn)行擴(kuò)容時,都是將就數(shù)組的元素全部拷貝到新的數(shù)組中,這個過程是比較耗時和浪費(fèi)資源,如果在實(shí)際的開發(fā)過程中提前計(jì)算好數(shù)組的容量,可以極大限度的提升性能,降低GC的活動頻率。

其中對于初始化為設(shè)置Dictionary的capacity時,第一次插入元素時,C#會對兩個數(shù)組進(jìn)行初始化,其中size=3,即維護(hù)的素?cái)?shù)表中的最小值,后續(xù)超過該數(shù)組大小后,會按照以上的擴(kuò)容邏輯進(jìn)行擴(kuò)容。

四、Dictionary<TKey, TValue>的FindValue的實(shí)現(xiàn)方式

介紹完畢Dictionary<TKey, TValue>的元素插入后,我們接下來看一下Dictionary<TKey, TValue>的查詢邏輯,在Dictionary<TKey, TValue>中實(shí)現(xiàn)查詢邏輯的核心方法是FindValue(),首先我們來看一下其實(shí)現(xiàn)的源碼。

internal ref TValue FindValue(TKey key)
{
  ref Entry entry = ref Unsafe.NullRef<Entry>();
  if (_buckets != null)
  {
    uint hashCode = (uint)key.GetHashCode();
    int i = GetBucket(hashCode);
    Entry[]? entries = _entries;
    uint collisionCount = 0;
    i--;
    do
      {
        if ((uint)i >= (uint)entries.Length)
        {
           goto ReturnNotFound;
        }
        entry = ref entries[i];
        if (entry.hashCode == hashCode && EqualityComparer<TKey>.Default.Equals(entry.key, key))
        {
           goto ReturnFound;
        }
        i = entry.next;
        collisionCount++;
      } while (collisionCount <= (uint)entries.Length);
         goto ConcurrentOperation;
    }
      goto ReturnNotFound;
       ConcurrentOperation:
            ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
        ReturnFound:
            ref TValue value = ref entry.value;
        Return:
            return ref value;
        ReturnNotFound:
            value = ref Unsafe.NullRef<TValue>();
            goto Return;
}

以上的源碼中,對于計(jì)算hashCode和計(jì)算哈希索引的桶的邏輯就不再贅述,重點(diǎn)關(guān)注entry.hashCode == hashCode &&EqualityComparer.Default.Equals(entry.key, key)),在FindValue()中,對已經(jīng)緩存的Entry[]? entries進(jìn)行循環(huán)遍歷,然后依次進(jìn)行比較,其中比較的邏輯包含兩部分。在判斷取值key時,不僅需要判斷傳入key值的hashCode與對應(yīng)Entry[]? entries中的元素的hashCode值相等,還需要判斷key是否相同,通過EqualityComparer.Default.Equals(entry.key, key)進(jìn)行比較,關(guān)于比較器的邏輯將在下一章中進(jìn)行介紹。

五、學(xué)在最后的思考和感悟

上面介紹了Dictionary<TKey, TValue>的初始化、元素插入、元素插入時的擴(kuò)容、元素取值的部分邏輯,我們可以發(fā)現(xiàn)在Dictionary<TKey, TValue>中維護(hù)了nt[] buckets和Entry[]? _entries兩個數(shù)組,其中用于存儲數(shù)據(jù)的結(jié)構(gòu)為Entry[]? _entries,這個類型為一個結(jié)構(gòu)體,在C#中結(jié)構(gòu)體占用的內(nèi)存要小于一個對象的內(nèi)存占用。無論多么復(fù)雜的存儲結(jié)構(gòu),其內(nèi)部會盡量將其簡化為一個數(shù)組,然后通過數(shù)組的存儲和讀取特性進(jìn)行優(yōu)化,規(guī)避了數(shù)組在某方面的不足,發(fā)揮了其優(yōu)勢。

以上的部分思考中,我們其實(shí)可以發(fā)現(xiàn)在實(shí)際的編碼過程中,需要注意的幾個事項(xiàng):

(1)、創(chuàng)建存儲結(jié)構(gòu)時,需要思考其對應(yīng)的存儲場景和對象,盡量選擇合適的結(jié)構(gòu)進(jìn)行處理,降低內(nèi)存的占用情況。

(2)、對于存儲結(jié)構(gòu),盡量可以提前指定容量,避免頻繁的擴(kuò)容,每次擴(kuò)容都會伴隨數(shù)組的復(fù)制。

(3)、C#的擴(kuò)容機(jī)制都是按照擴(kuò)容2倍,在hash存儲結(jié)構(gòu)中,還會按照維護(hù)的素?cái)?shù)表進(jìn)行個性化的計(jì)算優(yōu)化。

(4)、解讀源碼時,可以先選擇一個簡單的場景,盡量剔除與需要驗(yàn)證場景無關(guān)的代碼,集中核心邏輯進(jìn)行分析,然后再逐步進(jìn)行擴(kuò)展思考。

到此這篇關(guān)于詳解C#中Dictionary<TKey,TValue>的存儲結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)C# Dictionary<TKey,TValue>內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Unity實(shí)現(xiàn)俄羅斯方塊(一)

    Unity實(shí)現(xiàn)俄羅斯方塊(一)

    這篇文章主要介紹了Unity實(shí)現(xiàn)俄羅斯方塊的第一部分代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • CefSharp如何進(jìn)行頁面的縮放(Ctrl+滾輪)

    CefSharp如何進(jìn)行頁面的縮放(Ctrl+滾輪)

    CefSharp簡單來說就是一款.Net編寫的瀏覽器包,本文主要介紹了CefSharp如何進(jìn)行頁面的縮放,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • C#后臺創(chuàng)建控件并獲取值的方法

    C#后臺創(chuàng)建控件并獲取值的方法

    這篇文章主要介紹了C#后臺創(chuàng)建控件并獲取值的方法,實(shí)例講述了前臺與后臺的具體實(shí)現(xiàn)方法,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-01-01
  • C#使用NPOI實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出功能

    C#使用NPOI實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出功能

    這篇文章主要為大家詳細(xì)介紹了C#使用NPOI實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C#窗口實(shí)現(xiàn)單例模式的方法

    C#窗口實(shí)現(xiàn)單例模式的方法

    本文介紹了C#窗口實(shí)現(xiàn)單例模式的方法,對于一個軟件如果第二次打開程序,就把已經(jīng)啟動的那個進(jìn)程的窗口放到最前端顯示,需要了解的朋友可以參考下
    2015-07-07
  • C#的winform控件命名規(guī)范

    C#的winform控件命名規(guī)范

    這篇文章主要介紹了C#的winform控件命名規(guī)范,對各種常用控件的命名規(guī)范做了較為詳細(xì)的講解,需要的朋友可以參考下
    2015-01-01
  • 淺析.NET中AsyncLocal的實(shí)現(xiàn)原理

    淺析.NET中AsyncLocal的實(shí)現(xiàn)原理

    這篇文章主要為大家詳細(xì)介紹了.NET中AsyncLocal的具體實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,如果有講得不清晰或不準(zhǔn)確的地方,還望指出
    2023-08-08
  • C#對XmlHelper幫助類操作Xml文檔的通用方法匯總

    C#對XmlHelper幫助類操作Xml文檔的通用方法匯總

    該篇文章主要總結(jié)的是自己平時工作中使用頻率比較高的Xml文檔操作的一些常用方法和收集網(wǎng)上寫的比較好的一些通用Xml文檔操作的方法,對C#?XmlHelper幫助類操作Xml文檔相關(guān)知識感興趣的朋友一起看看吧
    2022-03-03
  • C#使用StreamWriter寫入文件的方法

    C#使用StreamWriter寫入文件的方法

    這篇文章主要介紹了C#使用StreamWriter寫入文件的方法,涉及C#中StreamWriter類操作文件的相關(guān)技巧,需要的朋友可以參考下
    2015-05-05
  • C# List介紹及具體用法

    C# List介紹及具體用法

    這篇文章主要介紹了C# List介紹及具體用法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12

最新評論