Có một triển khai IDixi, trên khóa bị thiếu, trả về giá trị mặc định thay vì ném không?


129

Bộ chỉ mục vào Từ điển sẽ ném một ngoại lệ nếu thiếu khóa. Có một triển khai IDadata thay vào đó sẽ trả về mặc định (T) không?

Tôi biết về phương pháp "TryGetValue", nhưng không thể sử dụng với linq.

Điều này có hiệu quả làm những gì tôi cần?:

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

Tôi không nghĩ nó sẽ như tôi nghĩ nó sẽ lặp lại các phím thay vì sử dụng tra cứu Hash.


10
Xem thêm câu hỏi sau này, được đánh dấu là bản sao của câu hỏi này, nhưng với các câu trả lời khác nhau: Từ điển trả về giá trị mặc định nếu khóa không tồn tại
Rory O'Kane

2
@dylan Điều đó sẽ ném một ngoại lệ vào khóa bị thiếu, không phải là null. Ngoài ra, nó sẽ yêu cầu quyết định những gì mặc định sẽ có ở mọi nơi mà từ điển được sử dụng. Cũng lưu ý về độ tuổi của câu hỏi này. Chúng tôi không có ?? Nhà điều hành 8 năm trước dù sao đi nữa
TheSoftwareJedi

Câu trả lời:


147

Thật vậy, điều đó sẽ không hiệu quả chút nào.

Bạn luôn có thể viết một phương thức mở rộng:

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

Hoặc với C # 7.1:

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

Sử dụng:

  • Phương thức biểu thức (C # 6)
  • Một biến ra (C # 7.0)
  • Một nghĩa đen mặc định (C # 7.1)

2
Hoặc gọn hơn:return (dictionary.ContainsKey(key)) ? dictionary[key] : default(TValue);
Peter Gluck

33
@PeterGluck: Nhỏ gọn hơn, nhưng kém hiệu quả hơn ... tại sao thực hiện hai lần tra cứu trong trường hợp khi có chìa khóa?
Jon Skeet

5
@JonSkeet: Cảm ơn vì đã sửa Peter; Tôi đã sử dụng phương pháp "kém hiệu quả" đó mà không nhận ra nó trong một thời gian.
J Bryan Giá

5
Rất đẹp! Tôi sẽ xấu hổ ăn cắp ý tưởng - er, fork nó. ;)
Jon Coombs

3
Rõ ràng MS đã quyết định điều này là đủ tốt để thêm vào System.Collections.Generic.CollectionExtensions, như tôi vừa thử và nó ở đó.
Người chơi

19

Thực hiện các phương pháp mở rộng này có thể giúp ..

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
Quá tải cuối cùng là thú vị. Kể từ khi không có gì để chọn là từ , điều này chỉ đơn giản là một hình thức đánh giá lười biếng?
MEMark

1
@MEMark có bộ chọn giá trị chỉ được chạy nếu cần, do đó, theo cách có thể nói là đánh giá lười biếng, nhưng nó không được thực thi như trong bối cảnh Linq. Nhưng đánh giá lười biếng ở đây không phụ thuộc vào không có gì để chọn từ yếu tố, tất nhiên bạn có thể thực hiện bộ chọn Func<K, V>.
nawfal

1
Phương thức thứ ba đó có công khai không vì nó hữu ích theo đúng nghĩa của nó? Đó là, bạn đang nghĩ ai đó có thể vượt qua trong lambda sử dụng thời gian hiện tại hoặc tính toán dựa trên ... hmm. Tôi đã dự kiến ​​sẽ chỉ có phương thức thứ hai làm giá trị V; return dict.TryGetValue (key, out value)? giá trị: defVal;
Jon Coombs

1
@JCoombs đúng, quá tải thứ ba là có cùng mục đích. Và tất nhiên bạn có thể làm điều đó trong tình trạng quá tải thứ hai, nhưng tôi đã làm cho nó hơi khô (để tôi không lặp lại logic).
nawfal

4
@nawfal Điểm tốt. Giữ khô là quan trọng hơn là tránh một cuộc gọi phương pháp bệnh sởi.
Jon Coombs

19

Nếu ai đó sử dụng .net core 2 trở lên (C # 7.X), lớp CollectionExtensions được giới thiệu và có thể sử dụng phương thức GetValueOrDefault để lấy giá trị mặc định nếu không có khóa trong từ điển.

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

4

Collections.Specialized.StringDictionarycung cấp kết quả không ngoại lệ khi tra cứu giá trị của khóa bị thiếu. Nó cũng không phân biệt chữ hoa chữ thường.

Hãy cẩn thận

Nó chỉ hợp lệ cho các mục đích sử dụng chuyên biệt của nó và - được thiết kế trước khi sử dụng thuốc generic - nó không có một điều tra viên rất tốt nếu bạn cần xem lại toàn bộ bộ sưu tập.


4

Nếu bạn đang sử dụng .Net Core, bạn có thể sử dụng phương thức CollectionExtensions.GetValueOrDefault . Điều này giống như việc thực hiện được cung cấp trong câu trả lời được chấp nhận.

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

Người ta có thể định nghĩa một giao diện cho chức năng tra cứu khóa của từ điển. Tôi có thể định nghĩa nó là một cái gì đó như:

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

Phiên bản có các khóa không chung sẽ cho phép mã đang sử dụng mã sử dụng các loại khóa không cấu trúc để cho phép phương sai khóa tùy ý, điều này không thể thực hiện được với tham số loại chung. Người ta không được phép sử dụng một biến đổi Dictionary(Of Cat, String)như một đột biến Dictionary(Of Animal, String)vì cái sau sẽ cho phép SomeDictionaryOfCat.Add(FionaTheFish, "Fiona"). Nhưng không có gì sai khi sử dụng một biến đổi Dictionary(Of Cat, String)như một bất biến Dictionary(Of Animal, String), vì SomeDictionaryOfCat.Contains(FionaTheFish)nên được coi là một biểu thức hoàn toàn tốt (nó sẽ trở lại false, mà không phải tìm kiếm từ điển, cho bất cứ thứ gì không thuộc loại Cat).

Thật không may, cách duy nhất người ta có thể thực sự sử dụng một giao diện như vậy là nếu một người bao bọc một Dictionaryđối tượng trong một lớp thực hiện giao diện đó. Tuy nhiên, tùy thuộc vào những gì bạn đang làm, một giao diện như vậy và phương sai mà nó cho phép có thể khiến nó đáng để nỗ lực.


1

Nếu bạn đang sử dụng ASP.NET MVC, bạn có thể tận dụng lớp RouteValueDipedia thực hiện công việc.

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

1

Câu hỏi này đã giúp xác nhận rằng TryGetValuevai FirstOrDefaulttrò ở đây.

Một tính năng thú vị của C # 7 mà tôi muốn đề cập là tính năng biến ngoài và nếu bạn thêm toán tử có điều kiện null từ C # 6 vào phương trình, mã của bạn có thể đơn giản hơn nhiều mà không cần thêm các phương thức mở rộng.

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

Nhược điểm của điều này là bạn không thể làm mọi thứ nội tuyến như thế này;

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

Nếu chúng ta cần / muốn làm điều này, chúng ta nên mã hóa một phương thức mở rộng như của Jon.


1

Đây là phiên bản của @ JonSkeet dành cho thế giới C # 7.1, cũng cho phép một mặc định tùy chọn được truyền vào:

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

Có thể hiệu quả hơn khi có hai chức năng để tối ưu hóa trường hợp bạn muốn trả về 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;
}

Thật không may, C # không (chưa?) Có toán tử dấu phẩy (hoặc toán tử dấu chấm phẩy được đề xuất C # 6), do đó bạn phải có một cơ thể chức năng thực sự (thở hổn hển!) Cho một trong những tình trạng quá tải.


1

Tôi đã sử dụng đóng gói để tạo một IDadata có hành vi rất giống với bản đồ STL , cho những người quen thuộc với c ++. Đối với những người không:

  • bộ chỉ mục nhận {} trong SafeDipedia bên dưới trả về giá trị mặc định nếu không có khóa thêm khóa đó vào từ điển với giá trị mặc định. Đây thường là hành vi mong muốn, vì bạn đang tìm kiếm các mặt hàng sẽ xuất hiện cuối cùng hoặc có cơ hội xuất hiện tốt.
  • phương thức Thêm (khóa TK, giá trị TV) hoạt động như một phương thức AddOrUpdate, thay thế giá trị hiện tại nếu nó tồn tại thay vì ném. Tôi không thấy lý do tại sao m $ không có phương thức AddOrUpdate và nghĩ rằng việc ném lỗi trong các tình huống rất phổ biến là một ý tưởng hay.

TL / DR - SafeDipedia được viết để không bao giờ ném ngoại lệ trong bất kỳ trường hợp nào, ngoại trừ các tình huống đồi trụy , chẳng hạn như máy tính hết bộ nhớ (hoặc bốc cháy). Nó thực hiện điều này bằng cách thay thế hành vi Add bằng AddOrUpdate và trả về mặc định thay vì ném NotFoundException từ bộ chỉ mục.

Đây là mã:

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

1

Vì .NET core 2.0, bạn có thể sử dụng:

myDict.GetValueOrDefault(someKeyKalue)

0

Điều gì về một lớp lót này kiểm tra xem một khóa có hiện diện bằng cách sử dụng ContainsKeyvà sau đó trả về giá trị được truy xuất thông thường hoặc giá trị mặc định bằng toán tử điều kiện không?

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

Không cần triển khai lớp Từ điển mới hỗ trợ các giá trị mặc định, chỉ cần thay thế các câu lệnh tra cứu của bạn bằng dòng ngắn ở trên.


5
Điều này có hai lần tra cứu thay vì một lần và nó đưa ra một điều kiện cuộc đua nếu bạn đang sử dụng ConcurrencyDipedia.
piedar

0

Nó có thể là một lớp lót để kiểm tra TryGetValuevà trả về giá trị mặc định nếu có false.

 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

Dùng thử trực tuyến


-1

Không, bởi vì làm thế nào bạn biết được sự khác biệt khi khóa tồn tại nhưng được lưu trữ một giá trị null? Điều đó có thể là đáng kể.


11
Nó có thể - nhưng trong một số tình huống bạn có thể biết rằng nó không phải. Tôi có thể thấy điều này đôi khi hữu ích.
Jon Skeet

8
Nếu bạn biết null không có trong đó - điều này cực kỳ tốt để có. Nó làm cho việc tham gia những điều này vào Linq (đó là tất cả những gì tôi làm gần đây) trở nên dễ dàng hơn nhiều
TheSoftwareJedi

1
Bằng cách sử dụng phương pháp ContainsKey?
MattDavey

4
Vâng, nó thực sự rất hữu ích. Python có cái này và tôi sử dụng nó nhiều hơn so với phương thức đơn giản, khi tôi tình cờ ở trong Python. Lưu lại việc viết thêm rất nhiều nếu các câu lệnh chỉ kiểm tra kết quả null. (Tất nhiên, bạn đúng, đôi khi null là hữu ích, nhưng thường thì tôi muốn có "" hoặc 0 thay vào đó.)
Jon Coombs

Tôi đã nâng cao điều này. Nhưng nếu đó là ngày hôm nay tôi sẽ không có. Không thể hủy ngay bây giờ :). Tôi ủng hộ các ý kiến ​​để đưa ra quan điểm của mình :)
nawfal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.