C-Sharp | Java | Python | Swift | GO | WPF | Ruby | Scala | F# | JavaScript | SQL | PHP | Angular | HTML
Part 1: Here 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.
ConsoleC# 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]
Tip: This is clearer than a 2-element array. Consider out or ref parameters instead.
ParametersC# 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]
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.
Var: For simpler and shorter syntax, we can use the var keyword with the foreach-loop over the Dictionary.
VarC# 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
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 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();
}
Tip: Internally ToString uses a StringBuilder. This may cause memory pressure. Avoiding ToString can speed up programs.
StringBuilderProgram: 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]
Version 1: The keys and values are stored in 2 separate arrays. The GC.GetTotalMemory method measures the memory usage.
ArrayGC.CollectVersion 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
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.
StructVersion 2: This code calls a method overload that receives a KeyValuePair struct (method overloading selects the correct method).
OverloadResult: It is possible to improve performance by replacing a KeyValuePair with a regular struct.
BenchmarkC# 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 = "Codex";
Method(p1);
KeyValuePair<int, string> p2 = new KeyValuePair<int, string>(4, "Codex");
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
But: In the KeyValuePair version, the call instruction is used instead of ldfld because KeyValuePair uses properties.
IL DisassemblerTip: After C# compilation, the program is JIT-compiled during runtime. The behavior of the inliner is sometimes hard to determine.
Shuffle: Randomly shuffled arrays are more complicated than you might at first expect. KeyValuePair, and List, can help here.
Shuffle ArrayTherefore: Prefer classes when the usage is not trivial. This improves object-oriented design.