// Copyright (c) Umbraco. // See LICENSE for more details. using System.Collections; using System.Collections.Concurrent; using System.Collections.Specialized; using System.Net; using System.Text; using Umbraco.Cms.Core; namespace Umbraco.Extensions; /// /// Extension methods for Dictionary & ConcurrentDictionary. /// public static class DictionaryExtensions { /// /// Method to Get a value by the key. If the key doesn't exist it will create a new TVal object for the key and return /// it. /// /// /// /// /// /// public static TVal GetOrCreate(this IDictionary dict, TKey key) where TVal : class, new() { if (dict.ContainsKey(key) == false) { dict.Add(key, new TVal()); } return dict[key]; } /// /// Updates an item with the specified key with the specified value /// /// /// /// /// /// /// /// /// Taken from: /// http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression /// If there is an item in the dictionary with the key, it will keep trying to update it until it can /// public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) where TKey : notnull { while (dict.TryGetValue(key, out TValue? curValue)) { if (dict.TryUpdate(key, updateFactory(curValue), curValue)) { return true; } // if we're looping either the key was removed by another thread, or another thread // changed the value, so we start again. } return false; } /// /// Updates an item with the specified key with the specified value /// /// /// /// /// /// /// /// /// Taken from: /// http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression /// WARNING: If the value changes after we've retrieved it, then the item will not be updated /// public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) where TKey : notnull { if (!dict.TryGetValue(key, out TValue? curValue)) { return false; } dict.TryUpdate(key, updateFactory(curValue), curValue); return true; // note we return true whether we succeed or not, see explanation below. } /// /// Converts a dictionary to another type by only using direct casting /// /// /// /// /// public static IDictionary ConvertTo(this IDictionary d) where TKeyOut : notnull { var result = new Dictionary(); foreach (DictionaryEntry v in d) { result.Add((TKeyOut)v.Key, (TValOut)v.Value!); } return result; } /// /// Converts a dictionary to another type using the specified converters /// /// /// /// /// /// /// public static IDictionary ConvertTo( this IDictionary d, Func keyConverter, Func valConverter) where TKeyOut : notnull { var result = new Dictionary(); foreach (DictionaryEntry v in d) { result.Add(keyConverter(v.Key), valConverter(v.Value!)); } return result; } /// /// Converts a dictionary to a NameValueCollection /// /// /// public static NameValueCollection ToNameValueCollection(this IDictionary d) { var n = new NameValueCollection(); foreach (KeyValuePair i in d) { n.Add(i.Key, i.Value); } return n; } /// /// Merges all key/values from the sources dictionaries into the destination dictionary /// /// /// /// /// The source dictionary to merge other dictionaries into /// /// By default all values will be retained in the destination if the same keys exist in the sources but /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the /// destination. Note that /// it will just use the last found key/value if this is true. /// /// The other dictionaries to merge values from public static void MergeLeft(this T destination, IEnumerable> sources, bool overwrite = false) where T : IDictionary { foreach (KeyValuePair p in sources.SelectMany(src => src) .Where(p => overwrite || destination.ContainsKey(p.Key) == false)) { destination[p.Key] = p.Value; } } /// /// Merges all key/values from the sources dictionaries into the destination dictionary /// /// /// /// /// The source dictionary to merge other dictionaries into /// /// By default all values will be retained in the destination if the same keys exist in the sources but /// this can changed if overwrite = true, then any key/value found in any of the sources will overwritten in the /// destination. Note that /// it will just use the last found key/value if this is true. /// /// The other dictionary to merge values from public static void MergeLeft(this T destination, IDictionary source, bool overwrite = false) where T : IDictionary => destination.MergeLeft(new[] { source }, overwrite); /// /// Returns the value of the key value based on the key, if the key is not found, a null value is returned /// /// The type of the key. /// The type of the val. /// The d. /// The key. /// The default value. /// public static TVal? GetValue(this IDictionary d, TKey key, TVal? defaultValue = default) { if (d.ContainsKey(key)) { return d[key]; } return defaultValue; } /// /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty /// string is returned /// /// /// /// public static string? GetValueAsString(this IDictionary d, TKey key) => d.ContainsKey(key) ? d[key]!.ToString() : string.Empty; /// /// Returns the value of the key value based on the key as it's string value, if the key is not found or is an empty /// string, then the provided default value is returned /// /// /// /// /// public static string? GetValueAsString(this IDictionary d, TKey key, string defaultValue) { if (d.ContainsKey(key)) { var value = d[key]!.ToString(); if (value != string.Empty) { return value; } } return defaultValue; } /// contains key ignore case. /// The dictionary. /// The key. /// Value Type /// The contains key ignore case. public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) => dictionary.Keys.InvariantContains(key); /// /// Converts a dictionary object to a query string representation such as: /// firstname=shannon&lastname=deminick. /// /// /// public static string ToQueryString(this IDictionary d) { if (!d.Any()) { return string.Empty; } var builder = new StringBuilder(); foreach (KeyValuePair i in d) { builder.Append(string.Format("{0}={1}&", WebUtility.UrlEncode(i.Key), i.Value == null ? string.Empty : WebUtility.UrlEncode(i.Value.ToString()))); } return builder.ToString().TrimEnd(Constants.CharArrays.Ampersand); } /// The get entry ignore case. /// The dictionary. /// The key. /// The type /// The entry public static TValue? GetValueIgnoreCase(this IDictionary dictionary, string key) => dictionary!.GetValueIgnoreCase(key, default); /// The get entry ignore case. /// The dictionary. /// The key. /// The default value. /// The type /// The entry public static TValue GetValueIgnoreCase(this IDictionary dictionary, string? key, TValue defaultValue) { key = dictionary.Keys.FirstOrDefault(i => i.InvariantEquals(key)); return key.IsNullOrWhiteSpace() == false ? dictionary[key!] : defaultValue; } public static async Task> ToDictionaryAsync( this IEnumerable enumerable, Func syncKeySelector, Func> asyncValueSelector) where TKey : notnull { var dictionary = new Dictionary(); foreach (TInput item in enumerable) { TKey key = syncKeySelector(item); TValue value = await asyncValueSelector(item); dictionary.Add(key, value); } return dictionary; } }