Повторяющиеся ключи в словарях .NET?

Есть ли в библиотеке базовых классов .NET какие-либо словарные классы, которые позволяют использовать повторяющиеся ключи? Единственное решение, которое я нашел, - это создать, например, такой класс:

Dictionary<string, List<object>>

Но на самом деле это довольно неприятно. В Java я считаю, что MultiMap выполняет это, но не могу найти аналога в .NET.

Как это повторяющийся ключ, это повторяющиеся значения (список), верно?

Shamim Hafiz 18.12.2010 08:36

@ShamimHafiz, нет, значения не должны повторяться. Если вам нужно хранить дубликаты { a, 1 } и { a, 2 } в хеш-таблице, где a является ключом, можно использовать { a, [1, 2] }.

nawfal 19.06.2014 12:27

На самом деле, я считаю, что здесь действительно нужна коллекция, в которой каждый ключ может отображаться на одно или несколько значений. Я думаю, что выражение «повторяющиеся ключи» этого не передает.

DavidRR 06.08.2014 05:25

Для справки в будущем вам следует подумать о том, чтобы сохранить 1 ключ, просто добавляя к нему значения, вместо того, чтобы добавлять одни и те же ключи снова и снова.

Ya Wang 28.08.2015 18:38

Если и ключи, и значения являются строками, существует NameValueCollection (который может связывать несколько строковых значений с каждым строковым ключом).

AnorZaken 03.04.2016 19:54

Отвечая на этот вопрос через ~ 13 лет в будущем, я думаю, что у @YaWang есть хороший аргумент. Если вы столкнулись с проблемой, когда вам нужно сохранить несколько значений, которые могут иметь один и тот же ключ, вам следует подумать о реализации, в которой вы храните только этот единственный ключ, но сопоставляете его с массивом значений. Например, мне нужно было сопоставить повторяющиеся ключи с отдельными массивами строк. Вместо этого я собираюсь использовать Словарь строк (ключей), сопоставленный со списком массивов String [] в качестве значений. Когда у меня есть повторяющийся ключ, я просто добавляю это значение в список уже существующих значений.

TGP1994 22.02.2021 01:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
267
6
232 885
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

Я думаю, что что-то вроде List<KeyValuePair<object, object>> подойдет.

Как найти что-то в этом списке по ключу?

Wayne Bloss 28.09.2008 21:00

Вы должны пройти через него: но я не знал о LookUp-классе .NET 3.5: возможно, это более полезно для поиска в нем.

MADMap 28.09.2008 21:20

@wizlib: единственный способ - просмотреть список в цикле, что не так эффективно, как хеширование. -1

petr k. 28.09.2008 21:21

@petrk. Это действительно зависит от ваших данных. Я использовал эту реализацию, потому что у меня было очень мало уникальных ключей и я не хотел нести накладные расходы, связанные с коллизиями хешей. +1

Evan M 11.06.2012 20:13

Дублирующиеся ключи нарушают весь контракт Словаря. В словаре каждый ключ уникален и соответствует одному значению. Если вы хотите связать объект с произвольным количеством дополнительных объектов, лучшим вариантом может быть что-то вроде DataSet (в просторечии - таблица). Поместите свои ключи в один столбец, а свои значения - в другой. Это значительно медленнее, чем словарь, но это ваш компромисс за потерю способности хешировать ключевые объекты.

Разве не весь смысл использования словаря для повышения производительности? Использование DataSet кажется не лучше, чем List <KeyValuePair <T, U >>.

Doug 24.02.2011 19:51

Вы имеете в виду конгруэнтный, а не фактический дубликат? В противном случае хеш-таблица не сможет работать.

Конгруэнтность означает, что два отдельных ключа могут хешировать эквивалентное значение, но ключи не равны.

Например: скажем, хеш-функция вашей хэш-таблицы была просто hashval = key mod 3. И 1, и 4 соответствуют 1, но имеют разные значения. Здесь в игру вступает ваша идея списка.

Когда вам нужно найти 1, это значение хешируется до 1, список просматривается до тех пор, пока не будет найден ключ = 1.

Если вы разрешили вставку повторяющихся ключей, вы не сможете различить, какие ключи соответствуют каким значениям.

Хеш-таблица уже обрабатывает ключи, которые имеют хэш для одного и того же значения (это называется конфликтом). Я имею в виду ситуацию, когда вы хотите сопоставить несколько значений одному и тому же точному ключу.

waltersobchek.myopenid.com 28.09.2008 23:21

Если вы используете строки как в качестве ключей, так и в качестве значений, вы можете использовать System.Collections.Specialized.NameValueCollection, который вернет массив строковых значений через метод GetValues ​​(строковый ключ).

NameValueCollection не допускает использование нескольких ключей.

Krisztián Balla 03.04.2015 13:15

@Jenny O'Reilly: Конечно, вы можете добавить повторяющиеся ключи.

isHuman 05.01.2016 15:51

Строго говоря, @ JennyO'Reilly прав, поскольку в примечаниях на связанной странице MSDN четко указано: этот класс хранит несколько строковых значений под одним ключом.

Ronald 15.03.2017 18:57

Это позволит, но вернет несколько значений, я пробовал использовать индекс и ключ.

user6121177 06.10.2017 23:51

Я только что наткнулся на библиотеку PowerCollections, которая включает, среди прочего, класс MultiDictionary. Это аккуратно завершает этот тип функциональности.

NameValueCollection поддерживает несколько строковых значений под одним ключом (который также является строкой), но это единственный известный мне пример.

Я склонен создавать конструкции, подобные той, что в вашем примере, когда я сталкиваюсь с ситуациями, когда мне нужна такая функциональность.

Ответ принят как подходящий

Если вы используете .NET 3.5, используйте класс Lookup.

Обновлено: вы обычно создаете Lookup, используя Enumerable.ToLookup. Это действительно предполагает, что вам не нужно менять его впоследствии, но я обычно считаю, что это достаточно хорошо.

Если этот не работает для вас, я не думаю, что во фреймворке есть что-то, что поможет - и использование словаря так же хорошо, как и получается :(

Спасибо за внимание к Lookup. Он предлагает отличный способ разделить (сгруппировать) результаты запроса linq, которые не соответствуют стандартным критериям orderby.

Robert Paulson 30.09.2008 00:42

@Josh: Вы используете Enumerable.ToLookup для его создания.

Jon Skeet 10.05.2010 17:43
Слово предостережения: Lookup is not serializable
SliverNinja - MSFT 23.12.2011 19:27

как нам добавлять элементы в этот поиск?

moldovanu 16.04.2012 18:58

Очень важное замечание относительно использования Lookup:

Вы можете создать экземпляр Lookup(TKey, TElement), вызвав ToLookup для объекта, который реализует IEnumerable(T).

Общедоступного конструктора для создания нового экземпляра Lookup(TKey, TElement) не существует. Кроме того, объекты Lookup(TKey, TElement) неизменяемы, то есть вы не можете добавлять или удалять элементы или ключи из объекта Lookup(TKey, TElement) после его создания.

(из MSDN)

Я думаю, что это будет ограничитель шоу для большинства применений.

Я могу придумать очень мало вариантов использования, где это могло бы быть стопором шоу. Но я считаю, что неизменяемые объекты - это здорово.

Joel Mueller 23.05.2011 20:35

@JoelMueller Я могу вспомнить множество случаев, когда это ограничивает шоу. Необходимость воссоздавать словарь для добавления элемента не является особенно эффективным решением ...

reirab 02.12.2014 23:16

Класс List на самом деле очень хорошо работает для коллекций ключ / значение, содержащих дубликаты, в которых вы хотели бы перебирать коллекцию. Пример:

List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();

// add some values to the collection here

for (int i = 0;  i < list.Count;  i++)
{
    Print(list[i].Key, list[i].Value);
}

Это решение работает функционально, но реализация List не знает ключ или значение и вообще не может оптимизировать поиск ключей.

Spencer Rose 28.02.2013 07:02

В ответ на исходный вопрос. Что-то вроде Dictionary<string, List<object>> реализовано в классе MultiMap в Code Project.

Вы можете найти дополнительную информацию по ссылке ниже: http://www.codeproject.com/KB/cs/MultiKeyDictionary.aspx

Я наткнулся на этот пост в поисках того же ответа и не нашел, поэтому я создал простой пример решения, используя список словарей, переопределив оператор [], чтобы добавить новый словарь в список, когда у всех остальных есть заданный ключ (набор) и вернуть список значений (получить) .
Это уродливо и неэффективно, он получает / устанавливает ТОЛЬКО по ключу и всегда возвращает список, но он работает:

 class DKD {
        List<Dictionary<string, string>> dictionaries;
        public DKD(){
            dictionaries = new List<Dictionary<string, string>>();}
        public object this[string key]{
             get{
                string temp;
                List<string> valueList = new List<string>();
                for (int i = 0; i < dictionaries.Count; i++){
                    dictionaries[i].TryGetValue(key, out temp);
                    if (temp == key){
                        valueList.Add(temp);}}
                return valueList;}
            set{
                for (int i = 0; i < dictionaries.Count; i++){
                    if (dictionaries[i].ContainsKey(key)){
                        continue;}
                    else{
                        dictionaries[i].Add(key,(string) value);
                        return;}}
                dictionaries.Add(new Dictionary<string, string>());
                dictionaries.Last()[key] =(string)value;
            }
        }
    }

При использовании опции List<KeyValuePair<string, object>> вы можете использовать LINQ для поиска:

List<KeyValuePair<string, object>> myList = new List<KeyValuePair<string, object>>();
//fill it here
var q = from a in myList Where a.Key.Equals("somevalue") Select a.Value
if (q.Count() > 0){ //you've got your value }

Да, но это не ускоряет работу (все еще нет хеширования)

Haukman 16.08.2011 22:28

Вот один из способов сделать это с помощью List <KeyValuePair <string, string>>

public class ListWithDuplicates : List<KeyValuePair<string, string>>
{
    public void Add(string key, string value)
    {
        var element = new KeyValuePair<string, string>(key, value);
        this.Add(element);
    }
}

var list = new ListWithDuplicates();
list.Add("k1", "v1");
list.Add("k1", "v2");
list.Add("k1", "v3");

foreach(var item in list)
{
    string x = string.format("{0} = {1}, ", item.Key, item.Value);
}

Выходы k1 = v1, k1 = v2, k1 = v3

Отлично работает в моем сценарии, а также прост в использовании.

user6121177 07.10.2017 00:31

То, как я использую, просто

Dictionary<string, List<string>>

Таким образом, у вас есть единственный ключ, содержащий список строк.

Пример:

List<string> value = new List<string>();
if (dictionary.Contains(key)) {
     value = dictionary[key];
}
value.Add(newValue);

Это красиво и чисто.

Jerry Nixon 05.08.2018 18:10

Это отличное решение для моего варианта использования.

dub stylee 25.09.2018 00:38

Если вы используете> = .NET 4, вы можете использовать класс Tuple:

// declaration
var list = new List<Tuple<string, List<object>>>();

// to add an item to the list
var item = Tuple<string, List<object>>("key", new List<object>);
list.Add(item);

// to iterate
foreach(var i in list)
{
    Console.WriteLine(i.Item1.ToString());
}

Это похоже на решение List<KeyValuePair<key, value>>, подобное приведенному выше. Я ошибаюсь?

Nathan Goings 10.04.2019 19:08

Вы можете добавить одинаковые ключи в другом регистре, например:

key1
Key1
KEY1
KeY1
kEy1
keY1

Я знаю, что это фиктивный ответ, но сработал для меня.

Нет, у вас не сработало. Словари позволяют очень быстро выполнять поиск - также классифицируется как О (1) - по ключу. Вы теряете это, когда добавляете несколько ключей в разных регистрах, потому что как вы их извлекаете? Попробовать каждую комбинацию верхнего и нижнего регистра? Независимо от того, как вы это делаете, производительность не будет такой, как при обычном поиске по одному словарю. Это в дополнение к другим, более очевидным недостаткам, таким как ограничение значений для каждого ключа, в зависимости от количества символов верхнего регистра в ключе.

Evgeniy Berezovsky 03.07.2015 02:31

Также это возможно:

Dictionary<string, string[]> previousAnswers = null;

Таким образом, у нас могут быть уникальные ключи. Надеюсь, что это работает для вас.

OP попросил словарь, который позволяет дублировать ключи.

Skaparate 25.03.2016 00:30

U может определить метод построения составного строкового ключа везде, где вы хотите использовать словарь, вы должны использовать этот метод для создания своего ключа Например:

private string keyBuilder(int key1, int key2)
{
    return string.Format("{0}/{1}", key1, key2);
}

для использования:

myDict.ContainsKey(keyBuilder(key1, key2))

Достаточно просто создать «свою собственную» версию словаря, допускающую «повторяющиеся ключевые» записи. Вот примерная простая реализация. Возможно, вы захотите рассмотреть возможность добавления поддержки практически для большинства (если не для всех) IDictionary<T>.

public class MultiMap<TKey,TValue>
{
    private readonly Dictionary<TKey,IList<TValue>> storage;

    public MultiMap()
    {
        storage = new Dictionary<TKey,IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
        if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
        storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
        get { return storage.Keys; }
    }

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

    public IList<TValue> this[TKey key]
    {
        get
        {
            if (!storage.ContainsKey(key))
                throw new KeyNotFoundException(
                    string.Format(
                        "The given key {0} was not found in the collection.", key));
            return storage[key];
        }
    }
}

Быстрый пример того, как его использовать:

const string key = "supported_encodings";
var map = new MultiMap<string,Encoding>();
map.Add(key, Encoding.ASCII);
map.Add(key, Encoding.UTF8);
map.Add(key, Encoding.Unicode);

foreach (var existingKey in map.Keys)
{
    var values = map[existingKey];
    Console.WriteLine(string.Join(",", values));
}

Это буксирный способ. Параллельный словарь, я думаю, это вам поможет:

public class HashMapDictionary<T1, T2> : System.Collections.IEnumerable
{
    private System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>> _keyValue = new System.Collections.Concurrent.ConcurrentDictionary<T1, List<T2>>();
    private System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>> _valueKey = new System.Collections.Concurrent.ConcurrentDictionary<T2, List<T1>>();

    public ICollection<T1> Keys
    {
        get
        {
            return _keyValue.Keys;
        }
    }

    public ICollection<T2> Values
    {
        get
        {
            return _valueKey.Keys;
        }
    }

    public int Count
    {
        get
        {
            return _keyValue.Count;
        }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public List<T2> this[T1 index]
    {
        get { return _keyValue[index]; }
        set { _keyValue[index] = value; }
    }

    public List<T1> this[T2 index]
    {
        get { return _valueKey[index]; }
        set { _valueKey[index] = value; }
    }

    public void Add(T1 key, T2 value)
    {
        lock (this)
        {
            if (!_keyValue.TryGetValue(key, out List<T2> result))
                _keyValue.TryAdd(key, new List<T2>() { value });
            else if (!result.Contains(value))
                result.Add(value);

            if (!_valueKey.TryGetValue(value, out List<T1> result2))
                _valueKey.TryAdd(value, new List<T1>() { key });
            else if (!result2.Contains(key))
                result2.Add(key);
        }
    }

    public bool TryGetValues(T1 key, out List<T2> value)
    {
        return _keyValue.TryGetValue(key, out value);
    }

    public bool TryGetKeys(T2 value, out List<T1> key)
    {
        return _valueKey.TryGetValue(value, out key);
    }

    public bool ContainsKey(T1 key)
    {
        return _keyValue.ContainsKey(key);
    }

    public bool ContainsValue(T2 value)
    {
        return _valueKey.ContainsKey(value);
    }

    public void Remove(T1 key)
    {
        lock (this)
        {
            if (_keyValue.TryRemove(key, out List<T2> values))
            {
                foreach (var item in values)
                {
                    var remove2 = _valueKey.TryRemove(item, out List<T1> keys);
                }
            }
        }
    }

    public void Remove(T2 value)
    {
        lock (this)
        {
            if (_valueKey.TryRemove(value, out List<T1> keys))
            {
                foreach (var item in keys)
                {
                    var remove2 = _keyValue.TryRemove(item, out List<T2> values);
                }
            }
        }
    }

    public void Clear()
    {
        _keyValue.Clear();
        _valueKey.Clear();
    }

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

Примеры:

public class TestA
{
    public int MyProperty { get; set; }
}

public class TestB
{
    public int MyProperty { get; set; }
}

            HashMapDictionary<TestA, TestB> hashMapDictionary = new HashMapDictionary<TestA, TestB>();

            var a = new TestA() { MyProperty = 9999 };
            var b = new TestB() { MyProperty = 60 };
            var b2 = new TestB() { MyProperty = 5 };
            hashMapDictionary.Add(a, b);
            hashMapDictionary.Add(a, b2);
            hashMapDictionary.TryGetValues(a, out List<TestB> result);
            foreach (var item in result)
            {
                //do something
            }

Я изменил ответ @Hector Correa на расширение с универсальными типами, а также добавил к нему собственный TryGetValue.

  public static class ListWithDuplicateExtensions
  {
    public static void Add<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
    {
      var element = new KeyValuePair<TKey, TValue>(key, value);
      collection.Add(element);
    }

    public static int TryGetValue<TKey, TValue>(this List<KeyValuePair<TKey, TValue>> collection, TKey key, out IEnumerable<TValue> values)
    {
      values = collection.Where(pair => pair.Key.Equals(key)).Select(pair => pair.Value);
      return values.Count();
    }
  }

я использую этот простой класс:

public class ListMap<T,V> : List<KeyValuePair<T, V>>
{
    public void Add(T key, V value) {
        Add(new KeyValuePair<T, V>(key, value));
    }

    public List<V> Get(T key) {
        return FindAll(p => p.Key.Equals(key)).ConvertAll(p=> p.Value);
    }
}

использование:

var fruits = new ListMap<int, string>();
fruits.Add(1, "apple");
fruits.Add(1, "orange");
var c = fruits.Get(1).Count; //c = 2;

Вы можете создать свою собственную оболочку словаря, что-то вроде этой, в качестве бонуса она поддерживает нулевое значение в качестве ключа:

/// <summary>
/// Dictionary which supports duplicates and null entries
/// </summary>
/// <typeparam name = "TKey">Type of key</typeparam>
/// <typeparam name = "TValue">Type of items</typeparam>
public class OpenDictionary<TKey, TValue>
{
    private readonly Lazy<List<TValue>> _nullStorage = new Lazy<List<TValue>>(
        () => new List<TValue>());

    private readonly Dictionary<TKey, List<TValue>> _innerDictionary =
        new Dictionary<TKey, List<TValue>>();

    /// <summary>
    /// Get all entries
    /// </summary>
    public IEnumerable<TValue> Values =>
        _innerDictionary.Values
            .SelectMany(x => x)
            .Concat(_nullStorage.Value);

    /// <summary>
    /// Add an item
    /// </summary>
    public OpenDictionary<TKey, TValue> Add(TKey key, TValue item)
    {
        if (ReferenceEquals(key, null))
            _nullStorage.Value.Add(item);
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                _innerDictionary.Add(key, new List<TValue>());

            _innerDictionary[key].Add(item);
        }

        return this;
    }

    /// <summary>
    /// Remove an entry by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveEntryByKey(TKey key, TValue entry)
    {
        if (ReferenceEquals(key, null))
        {
            int targetIdx = _nullStorage.Value.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            _nullStorage.Value.RemoveAt(targetIdx);
        }
        else
        {
            if (!_innerDictionary.ContainsKey(key))
                return this;

            List<TValue> targetChain = _innerDictionary[key];
            if (targetChain.Count == 0)
                return this;

            int targetIdx = targetChain.FindIndex(x => x.Equals(entry));
            if (targetIdx < 0)
                return this;

            targetChain.RemoveAt(targetIdx);
        }

        return this;
    }

    /// <summary>
    /// Remove all entries by key
    /// </summary>
    public OpenDictionary<TKey, TValue> RemoveAllEntriesByKey(TKey key)
    {
        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
                _nullStorage.Value.Clear();
        }       
        else
        {
            if (_innerDictionary.ContainsKey(key))
                _innerDictionary[key].Clear();
        }

        return this;
    }

    /// <summary>
    /// Try get entries by key
    /// </summary>
    public bool TryGetEntries(TKey key, out IReadOnlyList<TValue> entries)
    {
        entries = null;

        if (ReferenceEquals(key, null))
        {
            if (_nullStorage.IsValueCreated)
            {
                entries = _nullStorage.Value;
                return true;
            }
            else return false;
        }
        else
        {
            if (_innerDictionary.ContainsKey(key))
            {
                entries = _innerDictionary[key];
                return true;
            }
            else return false;
        }
    }
}

Пример использования:

var dictionary = new OpenDictionary<string, int>();
dictionary.Add("1", 1); 
// The next line won't throw an exception; 
dictionary.Add("1", 2);

dictionary.TryGetEntries("1", out List<int> result); 
// result is { 1, 2 }

dictionary.Add(null, 42);
dictionary.Add(null, 24);
dictionary.TryGetEntries(null, out List<int> result); 
// result is { 42, 24 }

Можете ли вы пояснить, что делает ваш код, как он отвечает на вопрос, и несколько примеров использования?

Enigmativity 27.10.2019 02:56

@Enigmativity, он делает именно то, что было задано, вопрос заключался в том, чтобы предложить некоторый словарь, который поддерживает дубликаты, поэтому я предложил создать оболочку вокруг словаря .net, который будет поддерживать эту функцию, и в качестве примера создал такую ​​оболочку, в качестве бонуса, которую он может обрабатывать null как ключ (даже если это, конечно, плохая практика). Использование довольно простое: var dictionary = new OpenDictionary<string, int>(); dictionary.Add("1", 1); // The next line won't throw an exception; dictionary.Add("1", 2); dictionary.TryGetEntries("1", out List<int> result); // result is { 1, 2 }

Alexander Tolstikov 27.10.2019 20:12

Можете ли вы добавить подробности к своему ответу?

Enigmativity 28.10.2019 01:54

@Enigmativity, если вы имеете в виду исходный ответ, то обязательно

Alexander Tolstikov 29.10.2019 10:49

Начиная с нового C# (я полагаю, что это из 7.0), вы также можете сделать что-то вроде этого:

var duplicatedDictionaryExample = new List<(string Key, string Value)> { ("", "") ... }

и вы используете его как стандартный список, но с двумя значениями, названными так, как вы хотите

foreach(var entry in duplicatedDictionaryExample)
{ 
    // do something with the values
    entry.Key;
    entry.Value;
}

Другие вопросы по теме