Словарь внутри словаря как значение [дубликат]

Если вы не используете jQuery в своем коде, этот ответ для вас

Ваш код должен быть чем-то вроде этого:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // always ends up being 'undefined'

Феликс Клинг отлично справился с написанием ответа для людей, использующих jQuery для AJAX, я решил предоставить альтернативу для людей, которые этого не делают.

( Примечание. используя новый API fetch, угловые или обещания, я добавил еще один ответ ниже )


То, с чем вы столкнулись

Это краткое резюме «Объяснение проблемы» из другого ответа, если вы не уверены, прочитав это, прочитайте это.

A в AJAX означает асинхронность. Это означает, что отправка запроса (или, скорее, получение ответа) вынимается из обычного потока выполнения. В вашем примере .send немедленно возвращается, а следующий оператор return result; выполняется до того, как функция, которую вы передали, когда был вызван обратный вызов success.

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

Вот простая аналогия

function getFive(){ 
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(Fiddle)

Возвращаемое значение a - undefined так как часть a=5 еще не выполнена. AJAX действует так, вы возвращаете значение до того, как сервер получил возможность сообщить вашему браузеру, что это за значение.

Одним из возможных решений этой проблемы является код повторно активно , сообщая вашей программе, что делать, когда расчет завершен.

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){ 
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

Это называется CPS . В основном, мы передаем getFive действие, которое необходимо выполнить, когда оно завершается, мы сообщаем нашему кодексу, как реагировать, когда событие завершается (например, наш вызов AJAX или в этом случае время ожидания).

Использование будет:

getFive(onComplete);

Который должен предупредить «5» на экране. (Fiddle) .

Возможные решения

Существуют два способа решения этой проблемы:

  1. Сделать AJAX синхронный вызов (позволяет называть его SJAX).
  2. Реструктурируйте свой код для правильной работы с обратными вызовами.

1. Синхронный AJAX - Не делайте этого !!

Что касается синхронного AJAX, не делайте этого! Ответ Феликса вызывает некоторые веские аргументы в пользу того, почему это плохая идея. Подводя итог, он заморозит браузер пользователя, пока сервер не вернет ответ и не создаст очень плохой пользовательский интерфейс. Вот еще краткое резюме из MDN о том, почему:

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

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

Если вы имеете , вы можете передать флаг: Вот как это сделать:

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. Код реструктуризации

Пусть ваша функция принимает обратный вызов. В примере код foo может быть сделан для принятия обратного вызова. Мы сообщим нашему кодексу, как отреагировали , когда foo завершает работу.

Итак:

var result = foo();
// code that depends on `result` goes here

Становится:

foo(function(result) {
    // code that depends on `result`
});

Здесь мы передали анонимную функцию, но мы могли бы так же легко передать ссылку на существующую , чтобы он выглядел следующим образом:

function myHandler(result) {
    // code that depends on `result`
}
foo(myHandler);

Для получения дополнительной информации о том, как выполняется этот вид обратного вызова, проверьте ответ Felix.

Теперь давайте определим сам foo, чтобы действовать соответственно

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // when the request is loaded
       callback(httpRequest.responseText);// we're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(скрипка)

Теперь мы сделали нашу функцию foo принять действие, которое будет выполняться, когда AJAX завершится успешно, мы можем продолжить это, проверив, не является ли статус ответа не 200 и действует соответственно (создайте обработчик сбоя и т. д.). Эффективное решение нашей проблемы.

Если вам все еще трудно понять это , прочитайте руководство по началу работы AJAX в MDN.

58
задан Drew Gaynor 5 August 2014 в 15:19
поделиться

9 ответов

У словаря .NET есть только отношение 1 к 1 для ключей и значений. Но это не означает, что значение не может быть другим массивом / списком / словарем.

Я не могу придумать причины, чтобы иметь отношение 1 к многим в словаре, но, очевидно, существует один.

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

1
ответ дан Alastair Pitts 31 August 2018 в 20:08
поделиться

Взгляните на MultiValueDictionary из Microsoft.

Пример кода:

MultiValueDictionary<string, string> Parameters = new MultiValueDictionary<string, string>();

Parameters.Add("Malik", "Ali");
Parameters.Add("Malik", "Hamza");
Parameters.Add("Malik", "Danish");

//Parameters["Malik"] now contains the values Ali, Hamza, and Danish
1
ответ дан Ali Asad 31 August 2018 в 20:08
поделиться

Используйте словарь списков (или другой тип коллекции), например:

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

myDictionary["My key"] = new List<int> {1, 2, 3, 4, 5};
4
ответ дан GraemeF 31 August 2018 в 20:08
поделиться

В качестве значения вы можете иметь словарь с коллекцией (или любым другим типом / классом). Таким образом, у вас есть один ключ, и вы сохраняете значения в своей коллекции.

1
ответ дан Maurits Rijk 31 August 2018 в 20:08
поделиться

Начиная с .net3.5 + вместо использования Dictionary<IKey, List<IValue>> вы можете использовать Lookup из пространства имен Linq:

// lookup Order by payment status (1:m) 
// would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
IEnumerable<Order> payedOrders = byPayment[false];

Из msdn :

Поиск похож на словарь. Разница заключается в том, что словарь сопоставляет ключи с одиночными значениями, тогда как Lookup сопоставляет ключи с коллекциями значений.

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

Вы также можете прочитать этот ответ на вопрос, связанный с . Для получения дополнительной информации см. msdn .

Полный пример:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqLookupSpike
{
    class Program
    {
        static void Main(String[] args)
        {
            // init 
            var orderList = new List<Order>();
            orderList.Add(new Order(1, 1, 2010, true));//(orderId, customerId, year, isPayed)
            orderList.Add(new Order(2, 2, 2010, true));
            orderList.Add(new Order(3, 1, 2010, true));
            orderList.Add(new Order(4, 2, 2011, true));
            orderList.Add(new Order(5, 2, 2011, false));
            orderList.Add(new Order(6, 1, 2011, true));
            orderList.Add(new Order(7, 3, 2012, false));

            // lookup Order by its id (1:1, so usual dictionary is ok)
            Dictionary<Int32, Order> orders = orderList.ToDictionary(o => o.OrderId, o => o);

            // lookup Order by customer (1:n) 
            // would need something like Dictionary<Int32, IEnumerable<Order>> orderIdByCustomer
            ILookup<Int32, Order> byCustomerId = orderList.ToLookup(o => o.CustomerId);
            foreach (var customerOrders in byCustomerId)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // the same using old fashioned Dictionary
            Dictionary<Int32, List<Order>> orderIdByCustomer;
            orderIdByCustomer = byCustomerId.ToDictionary(g => g.Key, g => g.ToList());
            foreach (var customerOrders in orderIdByCustomer)
            {
                Console.WriteLine("Customer {0} ordered:", customerOrders.Key);
                foreach (var order in customerOrders.Value)
                {
                    Console.WriteLine("    Order {0} is payed: {1}", order.OrderId, order.IsPayed);
                }
            }

            // lookup Order by payment status (1:m) 
            // would need something like Dictionary<Boolean, IEnumerable<Order>> orderIdByIsPayed
            ILookup<Boolean, Order> byPayment = orderList.ToLookup(o => o.IsPayed);
            IEnumerable<Order> payedOrders = byPayment[false];
            foreach (var payedOrder in payedOrders)
            {
                Console.WriteLine("Order {0} from Customer {1} is not payed.", payedOrder.OrderId, payedOrder.CustomerId);
            }
        }

        class Order
        {
            // key properties
            public Int32 OrderId { get; private set; }
            public Int32 CustomerId { get; private set; }
            public Int32 Year { get; private set; }
            public Boolean IsPayed { get; private set; }

            // additional properties
            // private List<OrderItem> _items;

            public Order(Int32 orderId, Int32 customerId, Int32 year, Boolean isPayed)
            {
                OrderId = orderId;
                CustomerId = customerId;
                Year = year;
                IsPayed = isPayed;
            }
        }
    }
}

Замечание о неизменности

По умолчанию Lookups являются добрыми непреложного и доступ к internal s будет включать отражение. Если вам нужна изменчивость и вы не хотите писать свою собственную оболочку, вы можете использовать MultiValueDictionary (ранее известный как MultiDictionary ) из corefxlab (ранее часть Microsoft.Experimental.Collections , которая больше не обновляется).

64
ответ дан mbx 31 August 2018 в 20:08
поделиться

Вы можете создать очень упрощенный многозадачный словарь, который автоматизирует процесс вставки таких значений:

public class MultiDictionary<TKey, TValue> : Dictionary<TKey, List<TValue>>
{
    public void Add(TKey key, TValue value)
    {
        if (TryGetValue(key, out List<TValue> valueList)) {
            valueList.Add(value);
        } else {
            Add(key, new List<TValue> { value });
        }
    }
}

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

0
ответ дан Olivier Jacot-Descombes 31 August 2018 в 20:08
поделиться

Вот мой подход к достижению такого поведения.

Для более полного решения, включающего ILookup<TKey, TElement>, проверьте мой другой ответ .

public abstract class Lookup<TKey, TElement> : KeyedCollection<TKey, ICollection<TElement>>
{
  protected override TKey GetKeyForItem(ICollection<TElement> item) =>
    item
    .Select(b => GetKeyForItem(b))
    .Distinct()
    .SingleOrDefault();

  protected abstract TKey GetKeyForItem(TElement item);

  public void Add(TElement item)
  {
    var key = GetKeyForItem(item);
    if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
      collection.Add(item);
    else
      Add(new List<TElement> { item });
  }

  public void Remove(TElement item)
  {
    var key = GetKeyForItem(item);
    if (Dictionary != null && Dictionary.TryGetValue(key, out var collection))
    {
      collection.Remove(item);
      if (collection.Count == 0)
        Remove(key);
    }
  }
}

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

public class Item
{
  public string Key { get; }
  public string Value { get; set; }
  public Item(string key, string value = null) { Key = key; Value = value; }
}

public class Lookup : Lookup<string, Item>
{
  protected override string GetKeyForItem(Item item) => item.Key;
}

static void Main(string[] args)
{
  var toRem = new Item("1", "different");
  var single = new Item("2", "single");
  var lookup = new Lookup()
  {
    new Item("1", "hello"),
    new Item("1", "hello2"),
    new Item(""),
    new Item("", "helloo"),
    toRem,
    single
  };

  lookup.Remove(toRem);
  lookup.Remove(single);
}

Примечание: ключ должен быть неизменным (или удалять и повторно добавлять при смене ключа).

47
ответ дан Shimmy 31 August 2018 в 20:08
поделиться

Вы также можете использовать:

 List<KeyValuePair<string, string>> Mappings;
0
ответ дан Teoman shipahi 31 August 2018 в 20:08
поделиться

Тип значения вашего словаря может быть списком или другим классом, который содержит несколько объектов. Что-то вроде

Dictionary<int, List<string>> 

для словаря, который вводится с помощью int и содержит список строк.

Основное внимание при выборе типа значения - это то, что вы будете использовать Словарь для, если вам придется выполнять поиск или другие операции над значениями, тогда, возможно, подумайте об использовании структуры данных, которая поможет вам делать то, что вы хотите - как HashSet.

7
ответ дан Tim Ridgely 31 August 2018 в 20:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: