มีการใช้งาน IDictionary ที่คีย์ที่หายไปจะส่งคืนค่าเริ่มต้นแทนการขว้างปาหรือไม่?


129

ตัวสร้างดัชนีในพจนานุกรมจะแสดงข้อยกเว้นหากคีย์หายไป มีการใช้ IDictionary ที่จะคืนค่าเริ่มต้น (T) แทนหรือไม่?

ฉันรู้เกี่ยวกับเมธอด "TryGetValue" แต่ใช้กับ linq ไม่ได้

สิ่งนี้จะทำสิ่งที่ฉันต้องการได้อย่างมีประสิทธิภาพหรือไม่:

myDict.FirstOrDefault(a => a.Key == someKeyKalue);

ฉันไม่คิดว่ามันจะเป็นอย่างที่ฉันคิดว่ามันจะย้ำคีย์แทนการใช้การค้นหาแฮช


10
ดูคำถามในภายหลังนี้ซึ่งทำเครื่องหมายว่าซ้ำกับคำถามนี้ แต่มีคำตอบที่แตกต่างกัน: พจนานุกรมจะส่งคืนค่าเริ่มต้นหากไม่มีคีย์
Rory O'Kane

2
@dylan นั่นจะทำให้เกิดข้อยกเว้นสำหรับคีย์ที่หายไปไม่ใช่โมฆะ นอกจากนี้ยังต้องมีการตัดสินใจว่าค่าเริ่มต้นควรเป็นเท่าใดในทุกที่ที่ใช้พจนานุกรม โปรดสังเกตอายุของคำถามนี้ด้วย เราไม่มี ?? ผู้ดำเนินการเมื่อ 8 ปีก่อน แต่อย่างใด
TheSoftwareJedi

คำตอบ:


147

แน่นอนว่าจะไม่มีประสิทธิภาพเลย

คุณสามารถเขียนวิธีการขยายได้ตลอดเวลา:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key)
{
    TValue ret;
    // Ignore return value
    dictionary.TryGetValue(key, out ret);
    return ret;
}

หรือด้วย C # 7.1:

public static TValue GetValueOrDefault<TKey,TValue>
    (this IDictionary<TKey, TValue> dictionary, TKey key) =>
    dictionary.TryGetValue(key, out var ret) ? ret : default;

ที่ใช้:

  • วิธีการแสดงออกทางร่างกาย (C # 6)
  • ตัวแปรออก (C # 7.0)
  • ลิเทอรัลเริ่มต้น (C # 7.1)

2
หรือกระชับกว่านี้:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck

33
@PeterGluck: กะทัดรัดกว่า แต่มีประสิทธิภาพน้อยกว่า ... ทำไมต้องดูสองครั้งในกรณีที่มีคีย์อยู่
Jon Skeet

5
@JonSkeet: ขอบคุณสำหรับการแก้ไขปีเตอร์; ฉันใช้วิธีที่ "มีประสิทธิภาพน้อยกว่า" โดยไม่รู้ตัวมาระยะหนึ่งแล้ว
ราคา J Bryan

5
ดีมาก! ฉันจะคัดลอกผลงานอย่างไร้ยางอาย - เอ่อแยกมัน ;)
Jon Coombs

3
เห็นได้ชัดว่า MS ตัดสินใจว่าสิ่งนี้ดีพอที่จะเพิ่มเข้าไปSystem.Collections.Generic.CollectionExtensionsในขณะที่ฉันเพิ่งลองและมันอยู่ที่นั่น
theMayer

19

การพกพาวิธีการต่อเหล่านี้ช่วยได้ ..

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key)
{
    return dict.GetValueOrDefault(key, default(V));
}

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, V defVal)
{
    return dict.GetValueOrDefault(key, () => defVal);
}

public static V GetValueOrDefault<K, V>(this IDictionary<K, V> dict, K key, Func<V> defValSelector)
{
    V value;
    return dict.TryGetValue(key, out value) ? value : defValSelector();
}

3
โอเวอร์ครั้งสุดท้ายน่าสนใจ ตั้งแต่มีอะไรที่จะเลือกจากนี้เป็นเพียงรูปแบบของการประเมินผลขี้เกียจหรือไม่?
MEMark

1
@MEMark ใช่ตัวเลือกค่าจะถูกเรียกใช้เมื่อจำเป็นเท่านั้นดังนั้นในทางที่กล่าวได้ว่าเป็นการประเมินแบบขี้เกียจ แต่ไม่ได้เลื่อนการดำเนินการเหมือนในบริบท Linq แต่การประเมินแบบขี้เกียจที่นี่ไม่ได้ขึ้นอยู่กับสิ่งที่จะเลือกจากปัจจัยคุณสามารถเลือกFunc<K, V>ได้แน่นอน
nawfal

1
วิธีที่สามนั้นเป็นสาธารณะเพราะมีประโยชน์ในตัวเองใช่หรือไม่? นั่นคือคุณคิดว่าอาจมีคนผ่านแลมด้าที่ใช้เวลาปัจจุบันหรือการคำนวณตาม ... อืม ฉันคาดว่าจะมีวิธีที่สองทำค่า V; ส่งคืน dict.TryGetValue (คีย์ออกค่า)? ค่า: defVal;
Jon Coombs

1
@JCoombs ใช่แล้วการโอเวอร์โหลดครั้งที่สามนั้นมีจุดประสงค์เดียวกัน และแน่นอนคุณสามารถทำได้ในการโอเวอร์โหลดครั้งที่สอง แต่ฉันทำให้มันแห้งไปหน่อย(เพื่อที่ฉันจะไม่ใช้ตรรกะซ้ำ)
nawfal

4
@nawfal จุดดี. การทำให้แห้งมีความสำคัญมากกว่าการหลีกเลี่ยงการเรียกใช้วิธีการที่น่ากลัว
Jon Coombs

19

หากมีคนใช้. net core 2 ขึ้นไป (C # 7.X) จะมีการแนะนำคลาสCollectionExtensionsและสามารถใช้เมธอดGetValueOrDefaultเพื่อรับค่าเริ่มต้นหากไม่มีคีย์ในพจนานุกรม

Dictionary<string, string> colorData = new  Dictionary<string, string>();
string color = colorData.GetValueOrDefault("colorId", string.Empty);

4

Collections.Specialized.StringDictionaryให้ผลลัพธ์ที่ไม่ใช่ข้อยกเว้นเมื่อค้นหาค่าของคีย์ที่หายไป นอกจากนี้ยังไม่คำนึงถึงขนาดตัวพิมพ์โดยค่าเริ่มต้น

คำเตือน

ใช้ได้เฉพาะสำหรับการใช้งานเฉพาะทางเท่านั้นและ - ได้รับการออกแบบมาก่อนชื่อสามัญ - ไม่มีตัวนับที่ดีมากหากคุณต้องการตรวจสอบคอลเล็กชันทั้งหมด


4

หากคุณใช้. Net Core คุณสามารถใช้เมธอดCollectionExtensions.GetValueOrDefault นี่เหมือนกับการใช้งานที่ให้ไว้ในคำตอบที่ยอมรับ

public static TValue GetValueOrDefault<TKey,TValue> (
   this System.Collections.Generic.IReadOnlyDictionary<TKey,TValue> dictionary,
   TKey key);

3
public class DefaultIndexerDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private IDictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();

    public TValue this[TKey key]
    {
        get
        {
            TValue val;
            if (!TryGetValue(key, out val))
                return default(TValue);
            return val;
        }

        set { _dict[key] = value; }
    }

    public ICollection<TKey> Keys => _dict.Keys;

    public ICollection<TValue> Values => _dict.Values;

    public int Count => _dict.Count;

    public bool IsReadOnly => _dict.IsReadOnly;

    public void Add(TKey key, TValue value)
    {
        _dict.Add(key, value);
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        _dict.Add(item);
    }

    public void Clear()
    {
        _dict.Clear();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dict.Contains(item);
    }

    public bool ContainsKey(TKey key)
    {
        return _dict.ContainsKey(key);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dict.CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dict.GetEnumerator();
    }

    public bool Remove(TKey key)
    {
        return _dict.Remove(key);
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        return _dict.Remove(item);
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dict.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _dict.GetEnumerator();
    }
}

1

เราสามารถกำหนดอินเทอร์เฟซสำหรับฟังก์ชันค้นหาคีย์ของพจนานุกรม ฉันอาจกำหนดเป็นสิ่งที่ต้องการ:

Interface IKeyLookup(Of Out TValue)
  Function Contains(Key As Object)
  Function GetValueIfExists(Key As Object) As TValue
  Function GetValueIfExists(Key As Object, ByRef Succeeded As Boolean) As TValue
End Interface

Interface IKeyLookup(Of In TKey, Out TValue)
  Inherits IKeyLookup(Of Out TValue)
  Function Contains(Key As TKey)
  Function GetValue(Key As TKey) As TValue
  Function GetValueIfExists(Key As TKey) As TValue
  Function GetValueIfExists(Key As TKey, ByRef Succeeded As Boolean) As TValue
End Interface

เวอร์ชันที่มีคีย์ที่ไม่ใช่คีย์ทั่วไปจะอนุญาตให้โค้ดที่ใช้โค้ดโดยใช้ประเภทคีย์ที่ไม่ใช่โครงสร้างเพื่ออนุญาตให้มีความแปรปรวนของคีย์โดยพลการซึ่งจะเป็นไปไม่ได้กับพารามิเตอร์ประเภททั่วไป หนึ่งไม่ควรได้รับอนุญาตให้ใช้แน่นอนDictionary(Of Cat, String)เป็นไม่แน่นอนตั้งแต่หลังจะช่วยให้Dictionary(Of Animal, String) SomeDictionaryOfCat.Add(FionaTheFish, "Fiona")แต่ไม่มีอะไรผิดในการใช้ mutable Dictionary(Of Cat, String)เป็นไม่เปลี่ยนรูปDictionary(Of Animal, String)เนื่องจากSomeDictionaryOfCat.Contains(FionaTheFish)ควรได้รับการพิจารณาว่าเป็นนิพจน์ที่มีรูปแบบที่สมบูรณ์แบบ (ควรส่งคืนfalseโดยไม่ต้องค้นหาพจนานุกรมสำหรับสิ่งที่ไม่ใช่ประเภทCat)

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


1

หากคุณใช้ ASP.NET MVC คุณสามารถใช้ประโยชน์จากคลาส RouteValueDictionary ที่ทำงานได้

public object this[string key]
{
  get
  {
    object obj;
    this.TryGetValue(key, out obj);
    return obj;
  }
  set
  {
    this._dictionary[key] = value;
  }
}

1

คำถามนี้ช่วยยืนยันว่าTryGetValueบทนี้มีFirstOrDefaultบทบาทอยู่ที่นี่

คุณลักษณะ C # 7 ที่น่าสนใจอย่างหนึ่งที่ฉันอยากจะพูดถึงคือคุณลักษณะout ตัวแปรและหากคุณเพิ่มตัวดำเนินการเงื่อนไขว่างจาก C # 6 ลงในสมการโค้ดของคุณอาจง่ายกว่ามากโดยไม่ต้องใช้วิธีการขยายเพิ่มเติม

var dic = new Dictionary<string, MyClass>();
dic.TryGetValue("Test", out var item);
item?.DoSomething();

ข้อเสียคือคุณไม่สามารถทำทุกอย่างแบบอินไลน์ได้

dic.TryGetValue("Test", out var item)?.DoSomething();

หากเราต้องการ / ต้องการทำสิ่งนี้เราควรโค้ดวิธีการขยายแบบหนึ่งเช่น Jon's


1

นี่คือเวอร์ชันของ @ JonSkeet สำหรับโลกของ C # 7.1 ที่อนุญาตให้ส่งผ่านค่าเริ่มต้นที่เป็นทางเลือกได้:

public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue = default) => dict.TryGetValue(key, out TV value) ? value : defaultValue;

อาจมีประสิทธิภาพมากกว่าที่จะมีฟังก์ชันสองอย่างเพื่อเพิ่มประสิทธิภาพกรณีที่คุณต้องการส่งคืนdefault(TV):

public static TV GetValueOrDefault<TK, TV>(this IDictionary<TK, TV> dict, TK key, TV defaultValue) => dict.TryGetValue(key, out TV value) ? value : defaultValue;
public static TV GetValueOrDefault2<TK, TV>(this IDictionary<TK, TV> dict, TK key) {
    dict.TryGetValue(key, out TV value);
    return value;
}

น่าเสียดายที่ C # ไม่มี (หรือยัง?) มีตัวดำเนินการลูกน้ำ (หรือตัวดำเนินการอัฒภาคที่เสนอ C # 6) ดังนั้นคุณต้องมีเนื้อความจริง (อ้าปากค้าง!) สำหรับหนึ่งในโอเวอร์โหลด


1

ฉันใช้การห่อหุ้มเพื่อสร้าง IDictionary ที่มีลักษณะการทำงานคล้ายกับแผนที่ STLสำหรับผู้ที่คุ้นเคยกับ c ++ สำหรับผู้ที่ไม่:

  • indexer get {} ใน SafeDictionary ด้านล่างจะส่งกลับค่าเริ่มต้นหากไม่มีคีย์และเพิ่มคีย์นั้นลงในพจนานุกรมด้วยค่าเริ่มต้น นี่มักเป็นพฤติกรรมที่ต้องการเนื่องจากคุณกำลังค้นหารายการที่จะปรากฏในที่สุดหรือมีโอกาสที่จะปรากฏ
  • method Add (คีย์ TK, TV val) ทำหน้าที่เป็นเมธอด AddOrUpdate โดยแทนที่ค่าปัจจุบันหากมีอยู่แทนที่จะโยน ฉันไม่เห็นว่าทำไม m $ ไม่มีเมธอด AddOrUpdate และคิดว่าการโยนข้อผิดพลาดในสถานการณ์ที่พบบ่อยเป็นความคิดที่ดี

TL / DR - SafeDictionary ถูกเขียนขึ้นเพื่อไม่ให้มีข้อยกเว้นไม่ว่าในสถานการณ์ใด ๆ นอกเหนือจากสถานการณ์ในทางที่ผิดเช่นคอมพิวเตอร์หน่วยความจำไม่เพียงพอ (หรือไฟไหม้) ทำได้โดยแทนที่เพิ่มด้วยพฤติกรรม AddOrUpdate และส่งคืนค่าเริ่มต้นแทนการโยน NotFoundException จากตัวสร้างดัชนี

นี่คือรหัส:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public class SafeDictionary<TK, TD>: IDictionary<TK, TD> {
    Dictionary<TK, TD> _underlying = new Dictionary<TK, TD>();
    public ICollection<TK> Keys => _underlying.Keys;
    public ICollection<TD> Values => _underlying.Values;
    public int Count => _underlying.Count;
    public bool IsReadOnly => false;

    public TD this[TK index] {
        get {
            TD data;
            if (_underlying.TryGetValue(index, out data)) {
                return data;
            }
            _underlying[index] = default(TD);
            return default(TD);
        }
        set {
            _underlying[index] = value;
        }
    }

    public void CopyTo(KeyValuePair<TK, TD>[] array, int arrayIndex) {
        Array.Copy(_underlying.ToArray(), 0, array, arrayIndex,
            Math.Min(array.Length - arrayIndex, _underlying.Count));
    }


    public void Add(TK key, TD value) {
        _underlying[key] = value;
    }

    public void Add(KeyValuePair<TK, TD> item) {
        _underlying[item.Key] = item.Value;
    }

    public void Clear() {
        _underlying.Clear();
    }

    public bool Contains(KeyValuePair<TK, TD> item) {
        return _underlying.Contains(item);
    }

    public bool ContainsKey(TK key) {
        return _underlying.ContainsKey(key);
    }

    public IEnumerator<KeyValuePair<TK, TD>> GetEnumerator() {
        return _underlying.GetEnumerator();
    }

    public bool Remove(TK key) {
        return _underlying.Remove(key);
    }

    public bool Remove(KeyValuePair<TK, TD> item) {
        return _underlying.Remove(item.Key);
    }

    public bool TryGetValue(TK key, out TD value) {
        return _underlying.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator() {
        return _underlying.GetEnumerator();
    }
}


0

สิ่งที่เกี่ยวกับ one-liner นี้ที่ตรวจสอบว่ามีการใช้คีย์หรือไม่ContainsKeyจากนั้นส่งคืนค่าที่ดึงกลับตามปกติหรือค่าเริ่มต้นโดยใช้ตัวดำเนินการตามเงื่อนไข

var myValue = myDictionary.ContainsKey(myKey) ? myDictionary[myKey] : myDefaultValue;

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


5
สิ่งนี้มีการค้นหาสองรายการแทนที่จะเป็นแบบเดียวและแนะนำเงื่อนไขการแข่งขันหากคุณใช้ ConcurrentDictionary
piedar

0

มันอาจจะเป็นหนึ่งซับเพื่อตรวจสอบและส่งกลับค่าเริ่มต้นถ้ามันเป็นTryGetValuefalse

 Dictionary<string, int> myDic = new Dictionary<string, int>() { { "One", 1 }, { "Four", 4} };
 string myKey = "One"
 int value = myDic.TryGetValue(myKey, out value) ? value : 100;

myKey = "One" => value = 1

myKey = "two" => value = 100

myKey = "Four" => value = 4

ลองออนไลน์


-1

ไม่เพราะไม่เช่นนั้นคุณจะทราบความแตกต่างได้อย่างไรเมื่อคีย์มีอยู่ แต่เก็บค่าว่างไว้ นั่นอาจมีความสำคัญ


11
อาจเป็นได้ - แต่ในบางสถานการณ์คุณอาจรู้ดีว่ามันไม่ใช่ บางครั้งฉันเห็นว่ามันมีประโยชน์
Jon Skeet

8
ถ้าคุณรู้ว่าไม่มีโมฆะอยู่ - นี่เป็นสิ่งที่ดีมาก ทำให้การเข้าร่วมสิ่งเหล่านี้ใน Linq (ซึ่งก็คือทั้งหมดที่ฉันทำเมื่อไม่นานมานี้) ง่ายขึ้นมาก
TheSoftwareJedi

1
โดยใช้เมธอดประกอบด้วยคีย์?
MattDavey

4
ใช่มันมีประโยชน์มากจริงๆ Python มีสิ่งนี้และฉันใช้มันมากกว่าวิธีธรรมดาเมื่อฉันอยู่ใน Python บันทึกการเขียนคำสั่ง if พิเศษจำนวนมากที่เพิ่งตรวจสอบผลลัพธ์ null (แน่นอนคุณพูดถูกว่าโมฆะนั้นมีประโยชน์ในบางครั้ง แต่โดยปกติฉันต้องการ "" หรือ 0 แทน)
Jon Coombs

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