มีประสิทธิภาพมากขึ้นคืออะไร: พจนานุกรม TryGetValue หรือมีคีย์ + รายการ


251

จากรายการของ MSDN ในDictionary.TryGetValue วิธีการ :

วิธีนี้รวมการทำงานของวิธีการที่มีKeyKeyและคุณสมบัติของรายการ

หากไม่พบคีย์จากนั้นพารามิเตอร์ค่าจะได้รับค่าเริ่มต้นที่เหมาะสมสำหรับประเภทค่า TValue; ตัวอย่างเช่น 0 (ศูนย์) สำหรับประเภทจำนวนเต็ม false สำหรับประเภทบูลีนและ null สำหรับประเภทอ้างอิง

ใช้วิธี TryGetValue หากรหัสของคุณพยายามเข้าถึงคีย์ที่ไม่ได้อยู่ในพจนานุกรมบ่อยครั้ง การใช้วิธีนี้มีประสิทธิภาพมากกว่าการจับ KeyNotFoundException ที่ส่งออกมาโดยคุณสมบัติ Item

วิธีนี้ใช้วิธีการ O (1)

จากคำอธิบายมันไม่ชัดเจนว่ามันมีประสิทธิภาพมากขึ้นหรือสะดวกกว่าการโทรไปที่ containKey แล้วทำการค้นหา การใช้งานTryGetValueเพียงแค่เรียกประกอบด้วย Keyk และจากนั้นรายการหรือจริง ๆ แล้วมีประสิทธิภาพมากกว่านั้นโดยทำการค้นหาเดียว?

กล่าวอีกนัยหนึ่งคืออะไรมีประสิทธิภาพมากขึ้น (เช่นที่หนึ่งทำการค้นหาน้อยกว่า):

Dictionary<int,int> dict;
//...//
int ival;
if(dict.ContainsKey(ikey))
{
  ival = dict[ikey];
}
else
{
  ival = default(int);
}

หรือ

Dictionary<int,int> dict;
//...//
int ival;
dict.TryGetValue(ikey, out ival);

หมายเหตุ: ฉันไม่ได้มองหามาตรฐาน!

คำตอบ:


313

TryGetValue จะเร็วขึ้น

ContainsKeyใช้การตรวจสอบเช่นเดียวกับTryGetValueซึ่งภายในอ้างถึงตำแหน่งรายการจริง Itemคุณสมบัติจริงมีฟังก์ชั่นรหัสเกือบเหมือนกันTryGetValueยกเว้นว่ามันจะโยนยกเว้นแทนที่จะกลับเท็จ

ใช้ContainsKeyแล้วตามด้วยItemฟังก์ชั่นการค้นหาซ้ำซึ่งเป็นกลุ่มของการคำนวณในกรณีนี้


2
นี่เป็นสิ่งที่ลึกซึ้งยิ่งขึ้น: if(dict.ContainsKey(ikey)) dict[ikey]++; else dict.Add(ikey, 0);. แต่ฉันคิดว่าTryGetValueมันยังคงมีประสิทธิภาพมากขึ้นตั้งแต่การใช้และรับชุดคุณสมบัติตัวทำดัชนีใช่หรือไม่
Tim Schmelter

4
คุณสามารถดูแหล่งที่มาของ. net ได้ในตอนนี้เช่นกัน: Referencesource.microsoft.com/#mscorlib/system/collections/ ...... คุณจะเห็นว่า TryGetValue, containKey ทั้ง 3 และ [] นี้เรียกใช้ FindEntry วิธีเดียวกัน จำนวนงานที่เท่ากันต่างกันเพียงวิธีตอบคำถาม: trygetvalue คืนค่าบูลและค่ามีคีย์ส่งคืนจริง / เท็จเท่านั้นและ [] นี้ส่งคืนค่าหรือส่งข้อยกเว้น
John Gardner

1
@JohnGardner ใช่ซึ่งเป็นสิ่งที่ฉันพูด - แต่ถ้าคุณทำเช่นนั้นมีคีย์ได้รับรายการคุณทำงานที่ 2x แทน 1x
รี้ Copsey

3
ฉันเห็นด้วยอย่างสมบูรณ์ :) ฉันแค่ชี้ให้เห็นว่าแหล่งที่มาที่แท้จริงมีอยู่ในขณะนี้ ไม่มีคำตอบ / etc อื่นใดที่มีลิงก์ไปยังแหล่งข้อมูลจริง: D
John Gardner

1
เล็กน้อยปิดหัวข้อถ้าคุณกำลังเข้าถึงผ่านทาง IDictionary ในสภาพแวดล้อมแบบมัลติเธรดผมมักจะใช้ TryGetValue เป็นของรัฐอาจมีการเปลี่ยนแปลงจากเวลาที่คุณโทร ContainsKey (ไม่มีการรับประกันว่า TryGetValue ภายในจะล็อคอย่างถูกต้องอย่างใดอย่างหนึ่งไม่ได้ แต่ก็อาจมีความปลอดภัยมากขึ้น)
Chris Berry

91

เกณฑ์มาตรฐานที่รวดเร็วแสดงให้เห็นว่าTryGetValueมีขอบเล็กน้อย:

    static void Main() {
        var d = new Dictionary<string, string> {{"a", "b"}};
        var start = DateTime.Now;
        for (int i = 0; i != 10000000; i++) {
            string x;
            if (!d.TryGetValue("a", out x)) throw new ApplicationException("Oops");
            if (d.TryGetValue("b", out x)) throw new ApplicationException("Oops");
        }
        Console.WriteLine(DateTime.Now-start);
        start = DateTime.Now;
        for (int i = 0; i != 10000000; i++) {
            string x;
            if (d.ContainsKey("a")) {
                x = d["a"];
            } else {
                x = default(string);
            }
            if (d.ContainsKey("b")) {
                x = d["b"];
            } else {
                x = default(string);
            }
        }
   }

สิ่งนี้ผลิต

00:00:00.7600000
00:00:01.0610000

ทำให้การContainsKey + Itemเข้าถึงช้าลงประมาณ 40% โดยสมมติว่ามีการผสมผสานระหว่าง Hit และคิดถึง

ยิ่งกว่านั้นเมื่อฉันเปลี่ยนโปรแกรมให้คิดถึง (เช่นค้นหาอยู่เสมอ"b") ทั้งสองเวอร์ชันจะเร็วพอ ๆ กัน:

00:00:00.2850000
00:00:00.2720000

เมื่อฉันทำให้มัน "ฮิตทั้งหมด" อย่างไรก็ตามTryGetValueยังคงเป็นผู้ชนะที่ชัดเจน:

00:00:00.4930000
00:00:00.8110000

11
แน่นอนมันขึ้นอยู่กับรูปแบบการใช้งานจริง หากคุณแทบจะไม่ล้มเหลวในการค้นหาคุณTryGetValueควรก้าวไปข้างหน้า นอกจากนี้ ... nitpick ... DateTimeไม่ใช่วิธีที่ดีที่สุดในการวัดประสิทธิภาพ
Ed S.

4
@EdS คุณถูกต้องTryGetValueได้รับมากขึ้นในการเป็นผู้นำ ฉันแก้ไขคำตอบเพื่อรวมสถานการณ์ "ฮิตทั้งหมด" และ "คิดถึงทั้งหมด"
dasblinkenlight

2
@Luciano อธิบายวิธีที่คุณใช้Any- Any(i=>i.Key==key)เช่นนี้ ในกรณีนี้ใช่นั่นเป็นการค้นหาเชิงเส้นที่ไม่ดีของพจนานุกรม
weston

13
DateTime.Nowจะมีความแม่นยำเพียงไม่กี่มิลลิวินาที ใช้StopwatchคลาสSystem.Diagnosticsแทน (ซึ่งใช้ QueryPerformanceCounter ภายใต้ฝาครอบเพื่อมอบความแม่นยำที่สูงขึ้น) มันใช้งานง่ายกว่าเช่นกัน
Alastair Maw

5
นอกจากความคิดเห็นของ Alastair และ Ed - DateTime ตอนนี้สามารถย้อนกลับไปได้หากคุณได้รับการปรับปรุงเวลาเช่นสิ่งที่เกิดขึ้นเมื่อผู้ใช้อัพเดตเวลาคอมพิวเตอร์ของพวกเขาข้ามเขตเวลาหรือการเปลี่ยนแปลงเขตเวลา (DST สำหรับ ตัวอย่าง). ลองใช้ระบบที่มีนาฬิการะบบตรงกับเวลาที่บริการวิทยุบางอย่างเช่น GPS หรือเครือข่ายโทรศัพท์มือถือ DateTime ตอนนี้จะไปทุกที่และ DateTime.UtcNow จะแก้ไขหนึ่งในสาเหตุเหล่านั้นเท่านั้น เพียงใช้ StopWatch
antiduh

51

เนื่องจากไม่มีคำตอบที่จริงตอบคำถามนี่คือคำตอบที่ยอมรับได้ฉันพบหลังจากการวิจัยบาง:

ถ้าคุณคอมไพล์ TryGetValue คุณจะเห็นว่ามันทำสิ่งนี้:

public bool TryGetValue(TKey key, out TValue value)
{
  int index = this.FindEntry(key);
  if (index >= 0)
  {
    value = this.entries[index].value;
    return true;
  }
  value = default(TValue);
  return false;
}

ในขณะที่วิธีการ containkey คือ:

public bool ContainsKey(TKey key)
{
  return (this.FindEntry(key) >= 0);
}

ดังนั้น TryGetValue จึงเป็นเพียงแค่มีคีย์และการค้นหาอาร์เรย์หากมีรายการอยู่

แหล่ง

ดูเหมือนว่า TryGetValue จะเร็วเป็นสองเท่าของชุดการรวมกับรายการ +


20

ใครสน :-)

คุณอาจจะถามเพราะTryGetValueปวดที่จะใช้ - ดังนั้นควรห่อหุ้มอย่างนี้ด้วยวิธีการขยาย

public static class CollectionUtils
{
    // my original method
    // public static V GetValueOrDefault<K, V>(this Dictionary<K, V> dic, K key)
    // {
    //    V ret;
    //    bool found = dic.TryGetValue(key, out ret);
    //    if (found)
    //    {
    //        return ret;
    //    }
    //    return default(V);
    // }


    // EDIT: one of many possible improved versions
    public static TValue GetValueOrDefault<K, V>(this IDictionary<K, V> dictionary, K key)
    {
        // initialized to default value (such as 0 or null depending upon type of TValue)
        TValue value;  

        // attempt to get the value of the key from the dictionary
        dictionary.TryGetValue(key, out value);
        return value;
    }

จากนั้นเพียงแค่โทร:

dict.GetValueOrDefault("keyname")

หรือ

(dict.GetValueOrDefault("keyname") ?? fallbackValue) 

1
@ Hüseyinฉันสับสนอย่างมากว่าฉันโง่พอที่จะโพสต์สิ่งนี้โดยไม่ได้thisแต่มันกลับกลายเป็นว่าฉันมีวิธีการของฉันซ้ำสองครั้งในฐานรหัสของฉัน - ครั้งเดียวและครั้งหนึ่งโดยไม่มีthisดังนั้นนั่นคือเหตุผลที่ฉันไม่เคยจับมัน! ขอบคุณสำหรับการแก้ไข!
Simon_Weaver

2
TryGetValueกำหนดค่าเริ่มต้นให้กับพารามิเตอร์ค่าออกหากไม่มีคีย์ดังนั้นสิ่งนี้อาจทำให้ง่ายขึ้น
Raphael Smit

2
รุ่นที่ง่ายขึ้น: ทีวีสาธารณะแบบคงที่ GetValueOrDefault <TKey, TValue> (พจนานุกรมนี้ <TKey, TValue> dict, ปุ่ม TKey) {TValue ret; dict.TryGetValue (คีย์, ret ret); ย้อนกลับไป; }
Joshua

2
ใน C # 7 นี้เป็นจริงสนุก:if(!dic.TryGetValue(key, out value item)) item = dic[key] = new Item();
Shimmy Weitzhandler

1
แดกดันซอร์สโค้ดตัวจริงมีรูทีน GetValueOrDefault () แล้ว แต่มันถูกซ่อนไว้ ... Referencesource.microsoft.com/#mscorlib/system/collections/…
Deven T. Corzine

10

ทำไมคุณไม่ลองทดสอบดูล่ะ?

แต่ฉันค่อนข้างแน่ใจว่าTryGetValueเร็วกว่าเพราะใช้การค้นหาเพียงครั้งเดียวเท่านั้น แน่นอนว่าสิ่งนี้ไม่ได้รับประกันเช่นการใช้งานที่แตกต่างกันอาจมีลักษณะการทำงานที่แตกต่างกัน

วิธีที่ฉันใช้พจนานุกรมคือการสร้างFindฟังก์ชั่นภายในที่ค้นหาช่องสำหรับรายการแล้วสร้างส่วนที่เหลือที่ด้านบนของที่


ฉันไม่คิดว่ารายละเอียดการใช้งานอาจเปลี่ยนการรับประกันได้ว่าการดำเนินการ X ครั้งหนึ่งเร็วกว่าหรือเท่ากับการดำเนินการ X สองครั้ง กรณีที่ดีที่สุดพวกเขาเหมือนกันกรณีที่แย่กว่านั้นคือรุ่น 2X ใช้เวลานานเป็นสองเท่า
Dan Bechard

9

คำตอบทั้งหมดจนถึงตอนนี้ดี แต่พลาดจุดสำคัญไป

วิธีการในชั้นเรียนของ API (เช่น. NET Framework) เป็นส่วนหนึ่งของคำนิยามอินเทอร์เฟซ (ไม่ใช่อินเทอร์เฟซ C # หรือ VB แต่เป็นอินเทอร์เฟซในความหมายวิทยาศาสตร์คอมพิวเตอร์)

ดังนั้นจึงเป็นเรื่องที่ไม่ถูกต้องที่จะถามว่าการเรียกใช้วิธีดังกล่าวเร็วกว่านี้หรือไม่เว้นแต่ความเร็วจะเป็นส่วนหนึ่งของการนิยามส่วนติดต่ออย่างเป็นทางการ (ซึ่งไม่ใช่ในกรณีนี้)

โดยปกติแล้วทางลัดประเภทนี้ (การรวมการค้นหาและดึงข้อมูล) นั้นมีประสิทธิภาพมากกว่าโดยไม่คำนึงถึงภาษาโครงสร้างพื้นฐาน OS แพลตฟอร์มหรือสถาปัตยกรรมเครื่อง นอกจากนี้ยังสามารถอ่านได้มากขึ้นเพราะมันเป็นการแสดงออกถึงความตั้งใจของคุณอย่างชัดเจนมากกว่าที่จะบ่งบอกถึงมัน (จากโครงสร้างของรหัสของคุณ)

ดังนั้นคำตอบ (จากการแฮ็กเก่าที่มีขนสีเทา) คือ 'ใช่' (TryGetValue เป็นที่นิยมมากกว่าการรวมกันของ containKey และ Item [รับ] เพื่อดึงค่าจากพจนานุกรม)

หากคุณคิดว่าฟังดูแปลกลองคิดดังนี้: แม้ว่าการนำไปใช้งานในปัจจุบันของ TryGetValue, containKey, และ Item [Get] จะไม่ให้ผลต่างของความเร็วใด ๆ คุณสามารถสันนิษฐานได้ว่าการใช้งานในอนาคต (เช่น. NET v5) จะทำ (TryGetValue จะเร็วขึ้น) คิดเกี่ยวกับอายุการใช้งานซอฟต์แวร์ของคุณ

เป็นที่น่าสนใจที่จะทราบว่าเทคโนโลยีอินเตอร์เฟซที่ทันสมัยโดยทั่วไปยังไม่ค่อยมีวิธีใด ๆ ของการ จำกัด เวลาอย่างเป็นทางการ บางที. NET v5


2
ในขณะที่ฉัน 100% เห็นด้วยกับข้อโต้แย้งของคุณเกี่ยวกับความหมายก็ยังคงมีมูลค่าการทำแบบทดสอบประสิทธิภาพ คุณไม่มีทางรู้ว่า API ที่คุณใช้มีการใช้งานที่ไม่ดีหรือไม่ซึ่งสิ่งที่ถูกต้องทางความหมายจะเกิดขึ้นช้าลงเว้นแต่คุณจะทำการทดสอบ
Dan Bechard

5

การทำโปรแกรมทดสอบอย่างรวดเร็วมีการปรับปรุงโดยใช้ TryGetValue กับ 1 ล้านรายการในพจนานุกรม

ผล:

มีคีย์ + รายการสำหรับการเข้าชม 1000000 ครั้ง: 45ms

TryGetValue เป็นเวลา 1000000 ครั้ง: 26ms

นี่คือแอปทดสอบ:

static void Main(string[] args)
{
    const int size = 1000000;

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

    for (int i = 0; i < size; i++)
    {
        dict.Add(i, i.ToString());
    }

    var sw = new Stopwatch();
    string result;

    sw.Start();

    for (int i = 0; i < size; i++)
    {
        if (dict.ContainsKey(i))
            result = dict[i];
    }

    sw.Stop();
    Console.WriteLine("ContainsKey + Item for {0} hits: {1}ms", size, sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();

    for (int i = 0; i < size; i++)
    {
        dict.TryGetValue(i, out result);
    }

    sw.Stop();
    Console.WriteLine("TryGetValue for {0} hits: {1}ms", size, sw.ElapsedMilliseconds);

}

5

ในเครื่องของฉันกับโหลดของแรมเมื่อทำงานในโหมดการปล่อย (ไม่ DEBUG) ContainsKeyเท่ากับTryGetValue/ try-catchถ้ารายการทั้งหมดในDictionary<>จะพบว่า

ContainsKeyมีประสิทธิภาพดีกว่าพวกเขาทั้งหมดเมื่อมีเพียงไม่กี่รายการพจนานุกรมที่ไม่พบ (ในตัวอย่างของฉันด้านล่างตั้งค่าMAXVALเป็นสิ่งที่ใหญ่กว่าที่ENTRIESจะพลาดบางรายการ):

ผล:

Finished evaluation .... Time distribution:
Size: 000010: TryGetValue: 53,24%, ContainsKey: 1,74%, try-catch: 45,01% - Total: 2.006,00
Size: 000020: TryGetValue: 37,66%, ContainsKey: 0,53%, try-catch: 61,81% - Total: 2.443,00
Size: 000040: TryGetValue: 22,02%, ContainsKey: 0,73%, try-catch: 77,25% - Total: 7.147,00
Size: 000080: TryGetValue: 31,46%, ContainsKey: 0,42%, try-catch: 68,12% - Total: 17.793,00
Size: 000160: TryGetValue: 33,66%, ContainsKey: 0,37%, try-catch: 65,97% - Total: 36.840,00
Size: 000320: TryGetValue: 34,53%, ContainsKey: 0,39%, try-catch: 65,09% - Total: 71.059,00
Size: 000640: TryGetValue: 32,91%, ContainsKey: 0,32%, try-catch: 66,77% - Total: 141.789,00
Size: 001280: TryGetValue: 39,02%, ContainsKey: 0,35%, try-catch: 60,64% - Total: 244.657,00
Size: 002560: TryGetValue: 35,48%, ContainsKey: 0,19%, try-catch: 64,33% - Total: 420.121,00
Size: 005120: TryGetValue: 43,41%, ContainsKey: 0,24%, try-catch: 56,34% - Total: 625.969,00
Size: 010240: TryGetValue: 29,64%, ContainsKey: 0,61%, try-catch: 69,75% - Total: 1.197.242,00
Size: 020480: TryGetValue: 35,14%, ContainsKey: 0,53%, try-catch: 64,33% - Total: 2.405.821,00
Size: 040960: TryGetValue: 37,28%, ContainsKey: 0,24%, try-catch: 62,48% - Total: 4.200.839,00
Size: 081920: TryGetValue: 29,68%, ContainsKey: 0,54%, try-catch: 69,77% - Total: 8.980.230,00

นี่คือรหัสของฉัน:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                const int ENTRIES = 10000, MAXVAL = 15000, TRIALS = 100000, MULTIPLIER = 2;
                Dictionary<int, int> values = new Dictionary<int, int>();
                Random r = new Random();
                int[] lookups = new int[TRIALS];
                int val;
                List<Tuple<long, long, long>> durations = new List<Tuple<long, long, long>>(8);

                for (int i = 0;i < ENTRIES;++i) try
                    {
                        values.Add(r.Next(MAXVAL), r.Next());
                    }
                    catch { --i; }

                for (int i = 0;i < TRIALS;++i) lookups[i] = r.Next(MAXVAL);

                Stopwatch sw = new Stopwatch();
                ConsoleColor bu = Console.ForegroundColor;

                for (int size = 10;size <= TRIALS;size *= MULTIPLIER)
                {
                    long a, b, c;

                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("Loop size: {0}", size);
                    Console.ForegroundColor = bu;

                    // ---------------------------------------------------------------------
                    sw.Start();
                    for (int i = 0;i < size;++i) values.TryGetValue(lookups[i], out val);
                    sw.Stop();
                    Console.WriteLine("TryGetValue: {0}", a = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    sw.Restart();
                    for (int i = 0;i < size;++i) val = values.ContainsKey(lookups[i]) ? values[lookups[i]] : default(int);
                    sw.Stop();
                    Console.WriteLine("ContainsKey: {0}", b = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    sw.Restart();
                    for (int i = 0;i < size;++i)
                        try { val = values[lookups[i]]; }
                        catch { }
                    sw.Stop();
                    Console.WriteLine("try-catch: {0}", c = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    Console.WriteLine();

                    durations.Add(new Tuple<long, long, long>(a, b, c));
                }

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("Finished evaluation .... Time distribution:");
                Console.ForegroundColor = bu;

                val = 10;
                foreach (Tuple<long, long, long> d in durations)
                {
                    long sum = d.Item1 + d.Item2 + d.Item3;

                    Console.WriteLine("Size: {0:D6}:", val);
                    Console.WriteLine("TryGetValue: {0:P2}, ContainsKey: {1:P2}, try-catch: {2:P2} - Total: {3:N}", (decimal)d.Item1 / sum, (decimal)d.Item2 / sum, (decimal)d.Item3 / sum, sum);
                    val *= MULTIPLIER;
                }

                Console.WriteLine();
            }
        }
    }

ฉันรู้สึกเหมือนมีอะไรบางอย่างกำลังเกิดขึ้นที่นี่ ฉันสงสัยว่าเครื่องมือเพิ่มประสิทธิภาพอาจลบหรือลดความซับซ้อนของเช็คของคุณจาก CheckKey () เนื่องจากคุณไม่เคยใช้ค่าที่ดึงมาได้
Dan Bechard

มันแค่ทำไม่ได้ ประกอบด้วยKey () อยู่ใน DLL รวบรวม เครื่องมือเพิ่มประสิทธิภาพไม่ทราบว่าเกี่ยวกับสิ่งที่มีจริง () มันอาจทำให้เกิดผลข้างเคียงดังนั้นจึงต้องมีการเรียกและไม่สามารถย่อได้
AxD

มีบางอย่างปลอมที่นี่ ความจริงก็คือการตรวจสอบรหัส. NET แสดงให้เห็นว่ามี KeepKey, TryGetValue และ [] ทั้งหมดเรียกรหัสภายในเดียวกันดังนั้น TryGetValue จึงเร็วกว่าที่มี KeepKey + นี่ [] เมื่อมีรายการอยู่
Jim Balter

3

นอกเหนือจากการออกแบบ microbenchmark ที่จะให้ผลลัพธ์ที่แม่นยำในการตั้งค่าภาคปฏิบัติคุณสามารถตรวจสอบแหล่งอ้างอิงของ. NET Framework

ทั้งหมดของพวกเขาเรียกFindEntry(TKey)วิธีการที่ไม่มากที่สุดของการทำงานและไม่ memoize ผลของตนเพื่อให้เรียกTryGetValueเป็นเกือบสองเท่าเป็นอย่างรวดเร็วContainsKeyItem +


TryGetValueสามารถปรับอินเทอร์เฟซที่ไม่สะดวกโดยใช้วิธีการขยาย :

using System.Collections.Generic;

namespace Project.Common.Extensions
{
    public static class DictionaryExtensions
    {
        public static TValue GetValueOrDefault<TKey, TValue>(
            this IDictionary<TKey, TValue> dictionary,
            TKey key,
            TValue defaultValue = default(TValue))
        {
            if (dictionary.TryGetValue(key, out TValue value))
            {
                return value;
            }
            return defaultValue;
        }
    }
}

ตั้งแต่ C # 7.1 คุณสามารถแทนที่ด้วยธรรมดาdefault(TValue) ชนิดถูกอนุมานdefault

การใช้งาน:

var dict = new Dictionary<string, string>();
string val = dict.GetValueOrDefault("theKey", "value used if theKey is not found in dict");

มันจะส่งกลับnullสำหรับประเภทการอ้างอิงที่การค้นหาล้มเหลวเว้นแต่จะมีการระบุค่าเริ่มต้นที่ชัดเจน

var dictObj = new Dictionary<string, object>();
object valObj = dictObj.GetValueOrDefault("nonexistent");
Debug.Assert(valObj == null);

val dictInt = new Dictionary<string, int>();
int valInt = dictInt.GetValueOrDefault("nonexistent");
Debug.Assert(valInt == 0);

โปรดทราบว่าผู้ใช้วิธีการขยายไม่สามารถบอกความแตกต่างระหว่างคีย์ที่ไม่มีอยู่และคีย์ที่มีอยู่ แต่ค่านั้นเป็นค่าเริ่มต้น (T)
ลูคัส

บนคอมพิวเตอร์ที่ทันสมัยถ้าคุณเรียกรูทีนย่อยสองครั้งติดต่อกันอย่างรวดเร็วมันไม่น่าจะใช้เวลาสองเท่าตราบใดที่เรียกมันครั้งเดียว นี่เป็นเพราะ CPU และสถาปัตยกรรมการแคชมีแนวโน้มที่จะแคชคำสั่งและข้อมูลจำนวนมากที่เกี่ยวข้องกับการโทรครั้งแรกดังนั้นการโทรครั้งที่สองจะถูกดำเนินการเร็วขึ้น ในทางกลับกันการโทรสองครั้งเกือบจะแน่ใจว่าใช้เวลานานกว่าการโทรเพียงครั้งเดียวดังนั้นจึงยังมีข้อได้เปรียบในการกำจัดสายที่สองหากเป็นไปได้
อภิปราย

2

หากคุณกำลังพยายามหาค่าจากพจนานุกรมตัวเลือก TryGetValue (คีย์ค่าออก) เป็นตัวเลือกที่ดีที่สุด แต่ถ้าคุณกำลังตรวจสอบว่ามีคีย์อยู่หรือไม่สำหรับการแทรกใหม่โดยไม่ต้องเขียนทับคีย์เก่า และด้วยขอบเขตนั้นเท่านั้นมีคี (คีย์) เป็นตัวเลือกที่ดีที่สุดเกณฑ์มาตรฐานสามารถยืนยันสิ่งนี้:

using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections;

namespace benchmark
{
class Program
{
    public static Random m_Rand = new Random();
    public static Dictionary<int, int> testdict = new Dictionary<int, int>();
    public static Hashtable testhash = new Hashtable();

    public static void Main(string[] args)
    {
        Console.WriteLine("Adding elements into hashtable...");
        Stopwatch watch = Stopwatch.StartNew();
        for(int i=0; i<1000000; i++)
            testhash[i]=m_Rand.Next();
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- pause....", watch.Elapsed.TotalSeconds);
        Thread.Sleep(4000);
        Console.WriteLine("Adding elements into dictionary...");
        watch = Stopwatch.StartNew();
        for(int i=0; i<1000000; i++)
            testdict[i]=m_Rand.Next();
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- pause....", watch.Elapsed.TotalSeconds);
        Thread.Sleep(4000);

        Console.WriteLine("Finding the first free number for insertion");
        Console.WriteLine("First method: ContainsKey");
        watch = Stopwatch.StartNew();
        int intero=0;
        while (testdict.ContainsKey(intero))
        {
            intero++;
        }
        testdict.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} in dictionary -- pause....", watch.Elapsed.TotalSeconds, intero);
        Thread.Sleep(4000);
        Console.WriteLine("Second method: TryGetValue");
        watch = Stopwatch.StartNew();
        intero=0;
        int result=0;
        while(testdict.TryGetValue(intero, out result))
        {
            intero++;
        }
        testdict.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} in dictionary -- pause....", watch.Elapsed.TotalSeconds, intero);
        Thread.Sleep(4000);
        Console.WriteLine("Test hashtable");
        watch = Stopwatch.StartNew();
        intero=0;
        while(testhash.Contains(intero))
        {
            intero++;
        }
        testhash.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} into hashtable -- pause....", watch.Elapsed.TotalSeconds, intero);
        Console.Write("Press any key to continue . . . ");
        Console.ReadKey(true);
    }
}
}

นี่คือตัวอย่างจริงฉันมีบริการที่สร้าง "รายการ" แต่ละรายการมันเชื่อมโยงกับหมายเลขโปรเกรสซีฟจำนวนนี้ทุกครั้งที่คุณสร้างรายการใหม่จะต้องพบฟรีถ้าคุณลบรายการหมายเลขฟรีจะกลายเป็น ฟรีแน่นอนว่านี่ไม่ได้รับการปรับให้เหมาะสมเนื่องจากฉันมีค่าคงที่ var ที่เก็บหมายเลขปัจจุบัน แต่ในกรณีที่คุณวางตัวเลขทั้งหมดคุณสามารถเริ่มใหม่ได้ตั้งแต่ 0 ถึง UInt32.MaxValue

ทดสอบการทำงาน: การ
เพิ่มองค์ประกอบลงใน hashtable ...
เสร็จใน 0,5908 - หยุดชั่วคราว ... การ
เพิ่มองค์ประกอบลงในพจนานุกรม ...
เสร็จใน 0,2679 - หยุดชั่วคราว ... การ
หาหมายเลขฟรีแรกสำหรับการแทรก
วิธีแรก : มี
คีย์เสร็จใน 0,0561 - เพิ่มมูลค่า 1000000 ในพจนานุกรม - หยุดชั่วคราว ....
วิธีที่สอง: TryGetValue
เสร็จใน 0,0643 - เพิ่มมูลค่า 1000001 ในพจนานุกรม - หยุดชั่วคราว ...
ทดสอบ hashtable
เสร็จใน 0 3015 - เพิ่มมูลค่า 1000000 ใน hashtable - หยุด ....
กดปุ่มใดก็ได้เพื่อดำเนินการต่อ .

หากคุณบางคนอาจจะถามว่ามีKeyKeyมีประโยชน์หรือไม่ฉันเคยลองใช้การกดแป้น TryGetValue ด้วยมีรหัสผลที่ได้คือเหมือนกัน

ดังนั้นสำหรับฉันด้วยการพิจารณาขั้นสุดท้ายมันทั้งหมดขึ้นอยู่กับวิธีการทำงานของโปรแกรม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.