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
.
As a generic struct
type in the C# language, KeyValuePair
must have both its key and value types specified during creation time. The syntax can become a bit difficult.
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.
List
of pairs. Each pair has 2 types separated by a comma (string
, int
).KeyValuePairs
are immutable, so we cannot change them after they are created, but we can print them.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]
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.
ref
parameters.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
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.
var
keyword with the foreach
-loop over the Dictionary
.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
, lambdaTo 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.
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
.
ToString
uses a StringBuilder
. This may cause memory pressure. Avoiding ToString
can speed up programs.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]
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.
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.
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.
[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(); }
KeyValuePair
wastes no memory—it is compact. Consider this example program: it has 2 versions of code, each of which store keys and values.
GC.GetTotalMemory
method measures the memory usage.KeyValuePair
structs. Again, the GC.GetTotalMemory
method is used.KeyValuePairs
uses a few bytes less of memory. So KeyValuePair
wastes no space.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
Is there any advantage to using custom structs instead of KeyValuePair
generic types? The 2 approaches are equivalent in functionality.
CustomPair
struct
instance.KeyValuePair
struct
(method overloading selects the correct method).KeyValuePair
with a regular struct
.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
If you think carefully, keys and values are everywhere—a term has a definition, and an action has a result. It makes sense that KeyValuePair
has many uses throughout C# programs.