Home
Map
KeyValuePair ExamplesLoop over a Dictionary with foreach and KeyValuePair. Understand read-only errors.
C#
This page was last reviewed on Mar 31, 2023.
KeyValuePair. This C# type joins 2 things together—for example, a string can be associated with an int or another string. We loop over these pairs when using a Dictionary.
Shows a keyvaluepairShows a keyvaluepairShows a keyvaluepairShows a keyvaluepair
This type is a struct. And it is generic—this means we must specify its key and value when creating it. The syntax can become a bit difficult.
An example. Here we use the KeyValuePair struct in a List. We store pairs of values in a single List—2 Lists could be used, but that might complicate matters.
Part 1 We initialize a List of pairs. Each pair has 2 types separated by a comma (string, int).
Part 2 KeyValuePairs are immutable, so we cannot change them after they are created, but we can print them.
Console.WriteLine
Shows a keyvaluepair
using System; using System.Collections.Generic; // Part 1: create a List of KeyValuePairs. var list = new List<KeyValuePair<string, int>>(); list.Add(new KeyValuePair<string, int>("Cat", 1)); list.Add(new KeyValuePair<string, int>("Dog", 2)); list.Add(new KeyValuePair<string, int>("Rabbit", 4)); // Part 2: loop over list and print pairs. foreach (var element in list) { Console.WriteLine(element); }
[Cat, 1] [Dog, 2] [Rabbit, 4]
Return, KeyValuePair. Often we need to return 2 values from a method. We can do this with KeyValuePair. Here is an example of the syntax—it can be tricky at first to use.
Tip This is clearer than a 2-element array. Consider out or ref parameters instead.
Shows a keyvaluepair
using System; using System.Collections.Generic; class Program { static void Main() { var result = GetFileType(); Console.WriteLine("TYPE: {0}, EXTENSION: {1}", result.Key, result.Value); } static KeyValuePair<string, string> GetFileType() { // Gets file type information. string type = "image/avif"; string extension = "avif"; return new KeyValuePair<string, string>(type, extension); } }
TYPE: image/avif, EXTENSION: avif
Foreach, Dictionary. A common use of KeyValuePair is in a loop over a Dictionary. The Dictionary has an enumerator that returns each key and value in a KeyValuePair, one at a time.
Dictionary
Detail For simpler and shorter syntax, we can use the var keyword with the foreach-loop over the Dictionary.
var
Shows a keyvaluepair
using System; using System.Collections.Generic; var animals = new Dictionary<string, int>() { { "cat", 2 }, { "dog", 1 } }; // Use KeyValuePair with foreach on Dictionary. foreach (KeyValuePair<string, int> animal in animals) { Console.WriteLine(animal); }
[cat, 2] [dog, 1]
Sort, lambda. To sort KeyValuePairs, we can use Comparison methods. A lambda is the cleanest way to do this. Here we sort in descending order by the Value of each pair.
Sort KeyValuePair List
Tip For descending, we take the second argument, and compare it to the first (instead of the other way around).
Shows a keyvaluepair
using System.Collections.Generic; var data = new List<KeyValuePair<int, int>>() { new KeyValuePair<int, int>(1, 6), new KeyValuePair<int, int>(1, 2), new KeyValuePair<int, int>(3, 4) }; // Sort pairs in list in descending order based on the value. // ... Use reverse order of A and B to mean descending sort. data.Sort((a, b) => (b.Value.CompareTo(a.Value))); foreach (var pair in data) { System.Console.WriteLine(pair); }
[1, 6] [3, 4] [1, 2]
ToString. When we want to display the values, call ToString or pass the KeyValuePair to Console.Write or Console.WriteLine. This will implicitly call ToString.
Tip Internally ToString uses a StringBuilder. This may cause memory pressure. Avoiding ToString can speed up programs.
StringBuilder
Here We invoke ToString() on the KeyValuePair, and also convert that string to an uppercase string with ToUpper.
using System; using System.Collections.Generic; var pair = new KeyValuePair<string, int>("bird", 10); // Get string from KeyValuePair. string result = pair.ToString(); Console.WriteLine("TOSTRING: {0}", result); Console.WriteLine("UPPER: {0}", result.ToUpper());
TOSTRING: [bird, 10] UPPER: [BIRD, 10]
Read-only error. When using KeyValuePair, we may get this error. The C# compiler doesn't allow us to assign the Key and Value properties. They must be assigned in the constructor.
readonly
using System.Collections.Generic; class Program { static void Main() { var pair = new KeyValuePair<string, int>("bird", 10); pair.Key = "rat"; } }
Property or indexer 'System.Collections.Generic.KeyValuePair...Key' cannot be assigned to--it is read-only.
Implementation. Here is the basic layout of the KeyValuePair struct. The KeyValuePair has 2 private fields, and 2 public properties that retrieve the values of those fields.
Property
[Serializable, StructLayout(LayoutKind.Sequential)] public struct KeyValuePair<TKey, TValue> { private TKey key; private TValue value; public KeyValuePair(TKey key, TValue value); public TKey Key { get; } public TValue Value { get; } public override string ToString(); }
Benchmark, memory use. KeyValuePair wastes no memory—it is compact. Consider this example program: it has 2 versions of code, each of which store keys and values.
Version 1 The keys and values are stored in 2 separate arrays. The GC.GetTotalMemory method measures the memory usage.
Array
GC.Collect
Version 2 This version uses a single array of KeyValuePair structs. Again, the GC.GetTotalMemory method is used.
Result The version that uses 1 array of KeyValuePairs uses a few bytes less of memory. So KeyValuePair wastes no space.
Tip Performance may be affected by the KeyValuePair version, as we must access properties rather than array elements.
using System; using System.Collections.Generic; class Program { static void Main() { const int size = 10000; Console.WriteLine("::2 ARRAYS::"); TestArrays(size); Console.WriteLine("::1 KEYVALUEPAIR ARRAY::"); TestKeyValuePairs(size); } static void TestArrays(int size) { // Version 1: allocate 2 arrays. long mem1 = GC.GetTotalMemory(false); string[] keys = new string[size]; int[] values = new int[size]; keys[0] = "bird"; values[1] = 10; long mem2 = GC.GetTotalMemory(false); // Collect garbage. GC.Collect(); Console.WriteLine(mem2 - mem1); keys[0] = ""; } static void TestKeyValuePairs(int size) { // Version 2: allocate 1 array of KeyValuePairs. long mem1 = GC.GetTotalMemory(false); KeyValuePair<string, int>[] array = new KeyValuePair<string, int>[size]; array[0] = new KeyValuePair<string, int>("bird", 10); long mem2 = GC.GetTotalMemory(false); // Collect garbage. GC.Collect(); Console.WriteLine(mem2 - mem1); array[0] = new KeyValuePair<string, int>("", 0); } }
::2 ARRAYS:: 80048 ::1 KEYVALUEPAIR ARRAY:: 80024
Benchmark, structs. Is there any advantage to using custom structs instead of KeyValuePair generic types? The 2 approaches are equivalent in functionality.
Next We look at a benchmark that compares 2 structs. It seems logical the methods should have the same performance.
Version 1 This version of the code calls a method that receives a CustomPair struct instance.
struct
Version 2 This code calls a method overload that receives a KeyValuePair struct (method overloading selects the correct method).
Overload
Result It is possible to improve performance by replacing a KeyValuePair with a regular struct.
Benchmark
using System; using System.Collections.Generic; using System.Diagnostics; struct CustomPair { public int Key; public string Value; } class Program { const int _max = 300000000; static void Main() { CustomPair p1; p1.Key = 4; p1.Value = "perls"; Method(p1); KeyValuePair<int, string> p2 = new KeyValuePair<int, string>(4, "perls"); Method(p2); for (int a = 0; a < 5; a++) { var s1 = Stopwatch.StartNew(); // Version 1: use custom struct. for (int i = 0; i < _max; i++) { Method(p1); Method(p1); } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: use KeyValuePair. for (int i = 0; i < _max; i++) { Method(p2); Method(p2); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } } static int Method(CustomPair pair) { return pair.Key + pair.Value.Length; } static int Method(KeyValuePair<int, string> pair) { return pair.Key + pair.Value.Length; } }
0.32 ns CustomPair 4.35 ns KeyValuePair 0.32 ns 4.34 ns 0.32 ns 4.36 ns 0.32 ns 4.35 ns 0.32 ns 4.36 ns
Performance, analysis. To analyze the results, I looked inside the 2 Method implementations in the IL Disassembler tool. They have the same code size.
But In the KeyValuePair version, the call instruction is used instead of ldfld because KeyValuePair uses properties.
Tip After C# compilation, the program is JIT-compiled during runtime. The behavior of the inliner is sometimes hard to determine.
A summary. Keys and values are everywhere—if you think carefully. A term has a definition. An action has a result. KeyValuePair has many uses throughout C# programs.
Dot Net Perls is a collection of tested code examples. Pages are continually updated to stay current, with code correctness a top priority.
Sam Allen is passionate about computer languages. In the past, his work has been recommended by Apple and Microsoft and he has studied computers at a selective university in the United States.
This page was last updated on Mar 31, 2023 (simplify).
Home
Changes
© 2007-2024 Sam Allen.