HomeSearch

C# KeyValuePair Examples

Loop over a Dictionary with foreach and KeyValuePair. Understand read-only errors.

KeyValuePair. A crow pushes against a latch. The gate unlocks. Inside, the bird finds seed and nourishment. The action (a push with its beak) has a result (a value).

Joined data. For a crow, the action and the value are linked together. A KeyValuePair struct (often used inside dictionaries) joins 2 things together.

An example. To begin, 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

C# program that uses KeyValuePair using System; using System.Collections.Generic; class Program { static void Main() { // 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); } } } Output [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.

Parameters
C# program that returns two values using System; using System.Collections.Generic; class Program { static void Main() { Console.WriteLine(GetNames()); } static KeyValuePair<string, string> GetNames() { // Gets collection of first and last name. string firstName = "Sundar"; string lastName = "Pichai"; return new KeyValuePair<string, string>(firstName, lastName); } } Output [Sundar, Pichai]

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
C# program that causes read-only error using System.Collections.Generic; class Program { static void Main() { var pair = new KeyValuePair<string, int>("bird", 10); pair.Key = "rat"; } } Output Property or indexer 'System.Collections.Generic.KeyValuePair...Key' cannot be assigned to--it is read-only.

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

Var: For simpler and shorter syntax, we can use the var keyword with the foreach-loop over the Dictionary.

Var
C# program that uses foreach loop on Dictionary using System; using System.Collections.Generic; class Program { static void Main() { Dictionary<string, int> birds = new Dictionary<string, int>() { { "crow", 10 }, { "robin", 5 } }; // Use KeyValuePair to use foreach on Dictionary. foreach (KeyValuePair<string, int> bird in birds) { Console.WriteLine($"Pair here: {bird.Key}, {bird.Value}"); } } } Output Pair here: crow, 10 Pair here: robin, 5

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.

Tip: For descending, we take the second argument, and compare it to the first (instead of the other way around).

C# program that uses lambda, sorts KeyValuePairs using System.Collections.Generic; class Program { static void Main() { 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); } } } Output [1, 6] [3, 4] [1, 2]

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
Implementation of KeyValuePair: C# [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(); }

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

Program: We invoke ToString() on the KeyValuePair, and also convert that string to an uppercase string with ToUpper.

C# program that uses ToString using System; using System.Collections.Generic; class Program { static void Main() { 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()); } } Output TOSTRING: [bird, 10] UPPER: [BIRD, 10]

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.

ArrayGC.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.

C# program that tests memory usage for KeyValuePair 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); } } Output ::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 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
C# program that tests KeyValuePair performance 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")); } Console.Read(); } static int Method(CustomPair pair) { return pair.Key + pair.Value.Length; } static int Method(KeyValuePair<int, string> pair) { return pair.Key + pair.Value.Length; } } Output 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.

IL Disassembler

Tip: After C# compilation, the program is JIT-compiled during runtime. The behavior of the inliner is sometimes hard to determine.

Sort, no lambdas. How can we sort a collection of KeyValuePair instances? We can implement custom sorting Comparison method—no lambdas are required. We use the delegate method syntax.Sort KeyValuePair List

Sort, parallel lists. We can use KeyValuePair in a List to create 2 parallel Lists. These are easily sorted, keeping both values together.

Shuffle: Randomly shuffled arrays are more complicated than you might at first expect. KeyValuePair, and List, can help here.

Shuffle Array

Notes, class. A class can be used to store keys and values together. When we reuse the object in many places, a class is probably clearer. It is easier to reuse.Class

Tuple. Another option in the .NET Framework is the Tuple type. We can have a 2-element Tuple. A Tuple is a class, not a struct. It can also have many more items in it.Tuple

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.
Home
Dot Net Perls
© 2007-2020 Sam Allen. Every person is special and unique. Send bug reports to info@dotnetperls.com.