Well, this all seemed to go to plan, and worked just fine within my app. But when I exposed the method as a web service, I discovered that neither the Key nor the Value properties were serialized - I was left to consume a lovely generic collection of objects with no members - not a whole lot of use.
A little Googling turned up this blog entry, which explains that the Key and Value properties on the KeyValuePair class are both read-only, and by design the XmlSerializer will not serialize properties that don't have a set accessor!
Damned if that little matter was going to stop me, I whipped out my copy of Lutz Roeder's reflector to reverse engineer the KeyValuePair class (to be honest, I could have guessed most of it), and added a private set accessor to both properties, thus persuading the XmlSerializer to, well, do some serializing :-)
Here's the code for this new class:
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace IanFNelson
{
/// <summary>
/// It's just like a System.Collections.Generic.KeyValuePair,
/// but the XmlSerializer will serialize the
/// Key and Value properties!
/// </summary>
[Serializable, StructLayout( LayoutKind.Sequential )]
public struct KeyValuePairThatSerializesProperly<TKey, TValue>
{
private TKey key;
private TValue value;
public KeyValuePairThatSerializesProperly(TKey key,
TValue value)
{
this.key = key;
this.value = value;
}
public override string ToString()
{
StringBuilder builder1 = new StringBuilder();
builder1.Append( '[' );
if ( this.Key != null )
{
builder1.Append( this.Key.ToString() );
}
builder1.Append( ", " );
if ( this.Value != null )
{
builder1.Append( this.Value.ToString() );
}
builder1.Append( ']' );
return builder1.ToString();
}
/// <summary>
/// Gets the Value in the Key/Value Pair
/// </summary>
public TValue Value
{
get
{
return this.value;
}
set
{
throw new NotSupportedException();
}
}
/// <summary>
/// Gets the Key in the Key/Value pair
/// </summary>
public TKey Key
{
get
{
return this.key;
}
set
{
throw new NotSupportedException();
}
}
}
}
Having done that, to add a little clarity when using this class in the way I anticipate and eliminate repeated code, I decided to create some specific versions of the Collection, ReadOnlyCollection and KeyedCollection classes:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace IanFNelson
{
[Serializable()]
public class KeyValuePairCollection<TKey, TValue> :
Collection<KeyValuePairThatSerializesProperly<TKey, TValue>>
{
public void Add(TKey key, TValue value)
{
this.Add( new KeyValuePairThatSerializesProperly<TKey,
TValue>( key, value ) );
}
}
}
using System;
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.Generic;
namespace IanFNelson
{
[Serializable()]
public class ReadOnlyKeyValuePairCollection<TKey, TValue> :
ReadOnlyCollection<KeyValuePairThatSerializesProperly<TKey, TValue>>
{
public ReadOnlyKeyValuePairCollection(
IList<KeyValuePairThatSerializesProperly
<TKey, TValue>> list) :
base( list ) { }
}
}
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace IanFNelson
{
[Serializable()]
public class KeyValuePairKeyedCollection<TKey, TValue> :
KeyedCollection<TKey, KeyValuePairThatSerializesProperly
<TKey, TValue>>
{
protected override TKey GetKeyForItem(
KeyValuePairThatSerializesProperly<TKey, TValue> item)
{
return item.Key;
}
public void Add(TKey key, TValue value)
{
this.Add( new KeyValuePairThatSerializesProperly<TKey,
TValue>( key, value ) );
}
}
}
