Я бачив кілька різних способів ітерації словника на C #. Чи є стандартний спосіб?
Я бачив кілька різних способів ітерації словника на C #. Чи є стандартний спосіб?
Відповіді:
foreach(KeyValuePair<string, string> entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
var entry
краще в цьому випадку, і , таким чином , я голосував цей відповідь на другий погляд замість вище.
var
коли ви не знаєте тип, як правило, погана практика.
var
працює лише в тому випадку, якщо тип відомий під час компіляції. Якщо Visual Studio знає тип, то це також можна дізнатися.
Якщо ви намагаєтесь використовувати загальний словник у C #, як і ви, використовуєте асоціативний масив іншою мовою:
foreach(var item in myDictionary)
{
foo(item.Key);
bar(item.Value);
}
Або, якщо вам потрібно лише перебрати колекцію ключів, скористайтеся
foreach(var item in myDictionary.Keys)
{
foo(item);
}
І нарешті, якщо вас цікавлять лише цінності:
foreach(var item in myDictionary.Values)
{
foo(item);
}
(Візьміть до уваги, що var
ключове слово є необов'язковою функцією C # 3.0 і вище, ви також можете використовувати точний тип ваших ключів / значень тут)
myDictionary
(якщо тільки це не є фактичною назвою). Я думаю, що використовувати var добре, коли тип очевидний, наприклад, var x = "some string"
але коли це не відразу очевидно, я думаю, що це ліниве кодування, що шкодить читачеві / рецензенту коду
var
На мою думку, слід використовувати економно. Особливо тут це не конструктивно: тип KeyValuePair
, ймовірно, має відношення до питання.
var
має унікальне призначення, і я не вірю, що це «синтаксичний» цукор. Цілеспрямовано використовувати його - це відповідний підхід.
У деяких випадках може знадобитися лічильник, який може бути наданий впровадженням циклу. Для цього LINQ надає ElementAt
такі можливості:
for (int index = 0; index < dictionary.Count; index++) {
var item = dictionary.ElementAt(index);
var itemKey = item.Key;
var itemValue = item.Value;
}
ElementAt
операція O (n)?
.ElementAt
в цьому контексті може призвести до тонких помилок. Набагато серйозніше - точка Артуро вище. Ви будете dictionary.Count + 1
повторювати словниковий час, що призводить до складності O (n ^ 2) для операції, яка повинна бути лише O (n). Якщо вам дійсно потрібен індекс (якщо це так, ви, ймовірно, в першу чергу використовуєте неправильний тип колекції), dictionary.Select( (kvp, idx) => new {Index = idx, kvp.Key, kvp.Value})
замість цього слід повторити і не використовувати .ElementAt
всередині циклу.
Залежить від того, чи будете ви після ключів або значень ...
З Dictionary(TKey, TValue)
опису класу MSDN :
// When you use foreach to enumerate dictionary elements,
// the elements are retrieved as KeyValuePair objects.
Console.WriteLine();
foreach( KeyValuePair<string, string> kvp in openWith )
{
Console.WriteLine("Key = {0}, Value = {1}",
kvp.Key, kvp.Value);
}
// To get the values alone, use the Values property.
Dictionary<string, string>.ValueCollection valueColl =
openWith.Values;
// The elements of the ValueCollection are strongly typed
// with the type that was specified for dictionary values.
Console.WriteLine();
foreach( string s in valueColl )
{
Console.WriteLine("Value = {0}", s);
}
// To get the keys alone, use the Keys property.
Dictionary<string, string>.KeyCollection keyColl =
openWith.Keys;
// The elements of the KeyCollection are strongly typed
// with the type that was specified for dictionary keys.
Console.WriteLine();
foreach( string s in keyColl )
{
Console.WriteLine("Key = {0}", s);
}
Взагалі, просити "найкращий спосіб" без конкретного контексту - це як запитати, що є найкращим кольором ?
З одного боку, є багато кольорів, і немає найкращого кольору. Це залежить від потреби, а часто і від смаку.
З іншого боку, існує багато способів ітерації словника в C #, і немає кращого способу. Це залежить від потреби, а часто і від смаку.
foreach (var kvp in items)
{
// key is kvp.Key
doStuff(kvp.Value)
}
Якщо вам потрібно лише значення (дозволяє його називати item
, читабельніше, ніж kvp.Value
).
foreach (var item in items.Values)
{
doStuff(item)
}
Як правило, початківців дивує порядок перерахування словника.
LINQ пропонує стислий синтаксис, який дозволяє задавати порядок (та багато інших речей), наприклад:
foreach (var kvp in items.OrderBy(kvp => kvp.Key))
{
// key is kvp.Key
doStuff(kvp.Value)
}
Знову вам може знадобитися лише значення. LINQ також пропонує стисле рішення для:
item
, читабельніше, ніж kvp.Value
)Ось:
foreach (var item in items.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value))
{
doStuff(item)
}
Існує ще багато випадків використання в реальному світі, які можна зробити з цих прикладів. Якщо вам не потрібно конкретного замовлення, просто дотримуйтесь "найпростішого способу" (див. Вище)!
.Values
а не обраним пунктом.
Value
поля. Точний тип я бачу тут IOrderedEnumerable<KeyValuePair<TKey, TValue>>
. Можливо, ви мали на увазі щось інше? Чи можете ви написати повний рядок із зазначенням того, що ви маєте на увазі (і перевірити це)?
items.Value
як ви запропонували. У випадку четвертого розділу, який ви прокоментували, Select()
це спосіб викликати foreach
перерахування безпосередньо значень у словнику замість пар ключ-значення. Якщо якимось чином вам не подобається Select()
в цьому випадку, ви можете віддати перевагу третьому розділу коду. Суть четвертого розділу полягає в тому, щоб показати, що можна попередньо обробити колекцію за допомогою LINQ.
.Keys.Orderby()
ви повторіть список ключів. Якщо це все, що вам потрібно, добре. Якщо вам потрібні значення, то в циклі вам доведеться запитувати словник на кожній клавіші, щоб отримати значення. У багатьох сценаріях це практично не зміниться. У високопродуктивному сценарії це буде. Як я писав на початку відповіді: "Є багато способів (...) і немає найкращого способу. Це залежить від потреби, а часто і від смаку".
Я б сказав, що foreach - це стандартний спосіб, хоча це, очевидно, залежить від того, що ви шукаєте
foreach(var kvp in my_dictionary) {
...
}
Це те, що ви шукаєте?
kvp
зазвичай використовується для іменування примірників KeyValuePair при Перебір словників і пов'язаних з ними структур даних: foreach(var kvp in myDictionary){...
.
C # 7.0 представив Deconstructors, і якщо ви використовуєте .NET Core 2.0+ додаток, структураKeyValuePair<>
вже міститьDeconstruct()
для вас. Отже, ви можете зробити:
var dic = new Dictionary<int, string>() { { 1, "One" }, { 2, "Two" }, { 3, "Three" } };
foreach (var (key, value) in dic) {
Console.WriteLine($"Item [{key}] = {value}");
}
//Or
foreach (var (_, value) in dic) {
Console.WriteLine($"Item [NO_ID] = {value}");
}
//Or
foreach ((int key, string value) in dic) {
Console.WriteLine($"Item [{key}] = {value}");
}
foreach (var (key, value) in dic.Select(x => (x.Key, x.Value)))
Я вдячний, що на це питання вже було багато відповідей, але я хотів трохи вивчити.
Ітерація через словник може бути досить повільною, порівняно з ітерацією над чимось на зразок масиву. У моїх тестах ітерація над масивом займала 0,015003 секунди, тоді як ітерація словника (з однаковою кількістю елементів) займала 0,0365073 секунди, що в 2,4 рази довше! Хоча я бачив набагато більші відмінності. Для порівняння, список опинився десь посеред 0,00215043 секунд.
Однак це як порівняння яблук та апельсинів. Моя думка, що ітерація над словниками повільна.
Словники оптимізовані для пошуку, тому маючи на увазі, я створив два методи. Один просто робить передбачення, інший повторює натискання клавіш.
public static string Normal(Dictionary<string, string> dictionary)
{
string value;
int count = 0;
foreach (var kvp in dictionary)
{
value = kvp.Value;
count++;
}
return "Normal";
}
Цей завантажує ключі та замість них повторює (я також намагався втягнути ключі в рядок [], але різниця була незначною.
public static string Keys(Dictionary<string, string> dictionary)
{
string value;
int count = 0;
foreach (var key in dictionary.Keys)
{
value = dictionary[key];
count++;
}
return "Keys";
}
З цього прикладу звичайний тест передбачення взяв 0,0310062, а версія ключів - 0,2205441. Завантаження всіх клавіш і повторення через усі пошуки явно МНОГО повільніше!
Для остаточного тесту я провів свою ітерацію десять разів, щоб побачити, чи є користь від використання тут клавіш (до цього моменту мені було просто цікаво):
Ось метод RunTest, якщо це допоможе вам уявити, що відбувається.
private static string RunTest<T>(T dictionary, Func<T, string> function)
{
DateTime start = DateTime.Now;
string name = null;
for (int i = 0; i < 10; i++)
{
name = function(dictionary);
}
DateTime end = DateTime.Now;
var duration = end.Subtract(start);
return string.Format("{0} took {1} seconds", name, duration.TotalSeconds);
}
Тут звичайний пробіг передбачення зайняв 0,2820564 секунди (приблизно в десять разів довше, ніж за одну ітерацію - як можна було очікувати). Ітерація клавіш зайняла 2.2249449 секунд.
Відредаговано, щоб додати: Читання деяких інших відповідей змусило мене запитати, що буде, якщо я використовую словник замість словника. У цьому прикладі масив займав 0,0120024 секунди, список 0,0185037 секунд і словник 0,0465093 секунди. Доцільно розраховувати, що тип даних змінить, наскільки повільніший словник.
Які мої висновки ?
Варіантів багато. Мій особистий фаворит - KeyValuePair
Dictionary<string, object> myDictionary = new Dictionary<string, object>();
// Populate your dictionary here
foreach (KeyValuePair<string,object> kvp in myDictionary)
{
// Do some interesting things
}
Ви також можете використовувати колекції ключів і значень
З .NET Framework 4.7
одним можна використовувати розкладання
var fruits = new Dictionary<string, int>();
...
foreach (var (fruit, number) in fruits)
{
Console.WriteLine(fruit + ": " + number);
}
Щоб цей код працював на нижчих версіях C #, додайте System.ValueTuple NuGet package
та запишіть десь
public static class MyExtensions
{
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple,
out T1 key, out T2 value)
{
key = tuple.Key;
value = tuple.Value;
}
}
ValueTuple
вбудований. Він доступний як набір пакетів для більш ранніх версій. Що ще важливіше, C # 7.0+ потрібен, щоб Deconstruct
метод працював як деконструктор для var (fruit, number) in fruits
.
Станом на C # 7 ви можете деконструювати об'єкти на змінні. Я вважаю, що це найкращий спосіб перебрати словник.
Приклад:
Створіть метод розширення на KeyValuePair<TKey, TVal>
тому, що деконструює його:
public static void Deconstruct<TKey, TVal>(this KeyValuePair<TKey, TVal> pair, out TKey key, out TVal value)
{
key = pair.Key;
value = pair.Value;
}
Повторіть будь Dictionary<TKey, TVal>
-який наступним чином
// Dictionary can be of any types, just using 'int' and 'string' as examples.
Dictionary<int, string> dict = new Dictionary<int, string>();
// Deconstructor gets called here.
foreach (var (key, value) in dict)
{
Console.WriteLine($"{key} : {value}");
}
Ви запропонували нижче повторити
Dictionary<string,object> myDictionary = new Dictionary<string,object>();
//Populate your dictionary here
foreach (KeyValuePair<string,object> kvp in myDictionary) {
//Do some interesting things;
}
FYI, foreach
не працює, якщо значення мають тип об'єкта.
foreach
не працюватиме , якщо який значення типу object
? Інакше це не має особливого сенсу.
Використовуючи C # 7 , додайте цей метод розширення до будь-якого проекту вашого рішення:
public static class IDictionaryExtensions
{
public static IEnumerable<(TKey, TValue)> Tuples<TKey, TValue>(
this IDictionary<TKey, TValue> dict)
{
foreach (KeyValuePair<TKey, TValue> kvp in dict)
yield return (kvp.Key, kvp.Value);
}
}
І використовуйте цей простий синтаксис
foreach (var(id, value) in dict.Tuples())
{
// your code using 'id' and 'value'
}
Або цей, якщо хочете
foreach ((string id, object value) in dict.Tuples())
{
// your code using 'id' and 'value'
}
На зміну традиційному
foreach (KeyValuePair<string, object> kvp in dict)
{
string id = kvp.Key;
object value = kvp.Value;
// your code using 'id' and 'value'
}
Метод розширення перетворює KeyValuePair
ваш IDictionary<TKey, TValue>
на сильно набранийtuple
, що дозволяє використовувати цей новий зручний синтаксис.
Він перетворює -just- необхідні записи словника tuples
, тому НЕ перетворює весь словник уtuples
, тому не виникає жодних проблем щодо продуктивності.
Є лише незначна вартість виклику методу розширення для створення tuple
в порівнянні з використанням KeyValuePair
безпосередньо, що НЕ повинно бути проблемою, якщо ви призначаєте KeyValuePair
властивості ' Key
іValue
нові змінні циклу в будь-якому випадку.
На практиці цей новий синтаксис дуже добре підходить для більшості випадків, за винятком низькорівневих сценаріїв надвисокої продуктивності, де ви все ще маєте можливість просто не використовувати його у певному місці.
Перевірте це: Блог MSDN - Нові функції в C # 7
kvp.Key
та kvp.Value
для використання відповідно ключа та значення. За допомогою кортежів ви отримуєте можливість називати ключ і значення за вашим бажанням, не використовуючи подальших оголошень змінних всередині блоку foreach. Наприклад, ви можете назвати свій ключ як factoryName
, а значення як models
, що особливо корисно, коли ви отримуєте вкладені петлі (словники словників): обслуговування коду стає набагато простішим. Просто спробуйте! ;-)
Я знаю, що це дуже старе питання, але я створив кілька методів розширення, які можуть бути корисними:
public static void ForEach<T, U>(this Dictionary<T, U> d, Action<KeyValuePair<T, U>> a)
{
foreach (KeyValuePair<T, U> p in d) { a(p); }
}
public static void ForEach<T, U>(this Dictionary<T, U>.KeyCollection k, Action<T> a)
{
foreach (T t in k) { a(t); }
}
public static void ForEach<T, U>(this Dictionary<T, U>.ValueCollection v, Action<U> a)
{
foreach (U u in v) { a(u); }
}
Таким чином я можу написати такий код:
myDictionary.ForEach(pair => Console.Write($"key: {pair.Key}, value: {pair.Value}"));
myDictionary.Keys.ForEach(key => Console.Write(key););
myDictionary.Values.ForEach(value => Console.Write(value););
Іноді, якщо вам потрібні лише перелічені значення, використовуйте колекцію значень словника:
foreach(var value in dictionary.Values)
{
// do something with entry.Value only
}
Про це повідомляє цей пост, де зазначено, що це найшвидший метод: http://alexpinsker.blogspot.hk/2010/02/c-fastest-way-to-iterate-over.html
Я знайшов цей метод у документації до класу DictionaryBase на MSDN:
foreach (DictionaryEntry de in myDictionary)
{
//Do some stuff with de.Value or de.Key
}
Це було єдине, що мені вдалося правильно функціонувати в класі, який успадкував від DictionaryBase.
Hashtable
об’єктів
ContainsKey()
у for
версії? Це додає додаткових накладних витрат, яких немає в коді, з яким ви порівнюєте. TryGetValue()
існує, щоб замінити точний шаблон "якщо ключ існує, отримати елемент з ключем". Крім того, якщо dict
містить суцільний діапазон цілих чисел від 0
до dictCount - 1
, ви знаєте, що індексатор не може вийти з ладу; в іншому випадку - dict.Keys
це те, що ви повинні повторювати. У будь-якому випадку, немає ContainsKey()
/ TryGetValue()
необхідності. Останнє, будь ласка, не публікуйте скріншоти коду.
Я написав розширення, щоб перетворитися на словник.
public static class DictionaryExtension
{
public static void ForEach<T1, T2>(this Dictionary<T1, T2> dictionary, Action<T1, T2> action) {
foreach(KeyValuePair<T1, T2> keyValue in dictionary) {
action(keyValue.Key, keyValue.Value);
}
}
}
Тоді ви можете зателефонувати
myDictionary.ForEach((x,y) => Console.WriteLine(x + " - " + y));
ForEach
метод, у якому у вас є foreach (...) { }
... Здається, непотрібним.
Якщо ви скажете, ви хочете переглядати колекцію значень за замовчуванням, я вважаю, що ви можете реалізувати IEnumerable <>, де T - тип об'єкта значень у словнику, а "this" - словник.
public new IEnumerator<T> GetEnumerator()
{
return this.Values.GetEnumerator();
}
Просто хотів додати свої 2 центри, оскільки більшість відповідей стосуються foreach-loop. Погляньте на наступний код:
Dictionary<String, Double> myProductPrices = new Dictionary<String, Double>();
//Add some entries to the dictionary
myProductPrices.ToList().ForEach(kvP =>
{
kvP.Value *= 1.15;
Console.Writeline(String.Format("Product '{0}' has a new price: {1} $", kvp.Key, kvP.Value));
});
Вважаючи, що це додає додатковий виклик ".ToList ()", можливо, відбудеться невелике покращення продуктивності (як вказувалося тут, передбачити проти someList.Foreach () {} ), espacially при роботі з великими словниками і працює паралельно немає варіант / взагалі не матиме ефекту.
Також зауважте, що ви не зможете призначити значення властивості "Value" всередині циклу foreach. З іншого боку, ви також зможете маніпулювати «Ключем», можливо, потрапляючи у вас проблеми під час виконання.
Коли ви просто хочете "прочитати" ключі та значення, ви також можете використовувати IEnumerable.Select ().
var newProductPrices = myProductPrices.Select(kvp => new { Name = kvp.Key, Price = kvp.Value * 1.15 } );
foreach
побічних ефектів : примушує видимість побічних ефектів угору, куди вона належить.
var dictionary = new Dictionary<string, int>
{
{ "Key", 12 }
};
var aggregateObjectCollection = dictionary.Select(
entry => new AggregateObject(entry.Key, entry.Value));
AggregateObject
додає KeyValuePair
? Де "ітерація", як запитується у запитанні?
foreach
, але я його багато використовував. Чи справді моя відповідь заслуговувала голосування?
Select
використовує ітерацію для отримання результату, але це не сам ітератор. Типи речей, для яких використовується ітерація ( foreach
), особливо операції з побічними ефектами, - виходять за рамки Linq, у тому числі Select
. Лямбда не буде працювати, поки aggregateObjectCollection
фактично не перераховано. Якщо ця відповідь сприймається як "перший шлях" (тобто використовується перед прямим foreach
), це заохочує погані практики. Ситуаційно можуть виникати операції Linq, які є корисними перед ітерацією словника, але це не вирішує питання як задане.
Словник <TKey, TValue> Це загальний клас колекції в c #, і він зберігає дані у форматі ключових значень. Ключ повинен бути унікальним, і він не може бути нульовим, тоді як значення може бути дублікатом і null. Як кожен елемент у словнику є трактується як KeyValuePair <TKey, TValue> структура, що представляє ключ та його значення. і, отже, нам слід прийняти тип елемента KeyValuePair <TKey, TValue> під час ітерації елемента. Нижче наведено приклад.
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1,"One");
dict.Add(2,"Two");
dict.Add(3,"Three");
foreach (KeyValuePair<int, string> item in dict)
{
Console.WriteLine("Key: {0}, Value: {1}", item.Key, item.Value);
}
Якщо ви хочете використовувати для циклу, ви можете зробити це:
var keyList=new List<string>(dictionary.Keys);
for (int i = 0; i < keyList.Count; i++)
{
var key= keyList[i];
var value = dictionary[key];
}
foreach
цикл і гірша продуктивність, тому що new List<string>(dictionary.Keys)
ви повторите dictionary.Count
час, перш ніж у вас навіть з’явиться можливість повторити його. Відкидаючи, що прохання про "кращий спосіб" є суб'єктивним, я не бачу, як це може бути кваліфіковано як "найкращий спосіб", або "стандартний спосіб", якого прагне питання. До "Якщо ви хочете використовувати для циклу ...", я б порівнювався з " Не використовувати for
цикл".
foreach (var pair in dictionary.ToArray()) { }
. Тим не менш, я думаю, що було б добре відповісти у відповіді конкретний сценарій (и), у якому хотілося б використовувати цей код, і наслідки цього.
простий з linq
Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1, "American Ipa");
dict.Add(2, "Weiss");
dict.ToList().ForEach(x => Console.WriteLine($"key: {x.Key} | value: {x.Value}") );
ToList()
тому що ForEach()
це визначено лише на List<>
уроці, але навіщо робити все це замість просто foreach (var pair in dict) { }
? Я б сказав, що це ще простіше і не має однакових наслідків для пам'яті / продуктивності. Це точне рішення було запропоновано у цій відповіді ще 3,5 роки тому.
на додаток до найвищого рейтингу, де існує дискусія між використанням
foreach(KeyValuePair<string, string> entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
або
foreach(var entry in myDictionary)
{
// do something with entry.Value or entry.Key
}
Найповнішим є наступне, оскільки ви можете бачити тип словника при ініціалізації, kvp - KeyValuePair
var myDictionary = new Dictionary<string, string>(x);//fill dictionary with x
foreach(var kvp in myDictionary)//iterate over dictionary
{
// do something with kvp.Value or kvp.Key
}