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.
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.
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. For clearer code, consider out or 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
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.
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.
Tip For descending, we take the second argument, and compare it to the first (instead of the other way around).
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.
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.
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.
[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.
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.
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.
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
Summary. 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.
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 Sep 22, 2024 (simplify).