I have a class 'products' that is serializable to XML. I'm using the standard System.Xml.Serialization.XmlSerializer to serialize and a XmlWriter 'writer' object to write the serialized results to a StreamWriter object. The serializer object now serializes the whole class in one go:
XmlSerializer serializer = new XmlSerializer(typeof(products));
serializer.Serialize(writer, products);
The class has a Dictionary<string,string> member called 'Specifications'. It is dynamically built, so I don't know the keys beforehand. Here's an example of what data the dictionary may contain (key: value):
color: blue
length: 110mm
width: 55mm
I would like to be able to serialize that property into this:
...
<specifications>
<color>blue</color>
<length>110mm</length>
<width>55mm</width>
</specifications>
...
I know this is poor XML design, but it has to conform to a 3rd party specification.
Is there perhaps a standard attribute that I can use? If not, how would I be able to serialize the dictionary like that?
If you need more code snippets, let me know.
EDIT:
Due to some changes in requirement, I let go of the Dictionary<string,string>. Instead, I created a class "Specification":
public class Specification
{
public string Name;
public string Value;
public bool IsOther;
public Specification() : this(null, null, false) { }
public Specification(string name, string value) : this(name, value, false) { }
public Specification(string name, string value, bool isOther)
{
Name = name;
Value = value;
IsOther = isOther;
}
}
To avoid repeating the element "spec" by having a List of "Specification" in the product class, I use a plural class "Specifications" that implements the IXmlSerializable interface:
public class Specifications: IXmlSerializable
{
public List<Specification> Specs = new List<Specification>();
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
//I don't need deserialization, but it would be simple enough now.
throw new System.NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
//write all "standarad", named specs
//this writes the <color>blue</color>-like elements
Specs.Where(s => !s.IsOther).ToList().ForEach(s => writer.WriteElementString(s.Name, s.Value));
//write other specs
//this writes <other_specs>{name|value[;]}*</other_specs>
string otherSpecs = string.Join(";", Specs.Where(s => s.IsOther).Select(s => string.Concat(s.Name, "|", s.Value)));
if (otherSpecs.Length > 0) writer.WriteElementString("other_specs", otherSpecs);
}
}
The class "Specifications" is applied as:
public class Product
{
public Product()
{
Specifications = new Specifications();
}
[XmlElement("specs")]
public Specifications Specifications;
//this "feature" will not include <specs/> when there are none
[XmlIgnore]
public bool SpecificationsSpecified { get { return Specifications.Specs.Any(); } }
//...
}
Thank you for providing examples of IXmlSerializable and XmlWriter. I didn't know that interface and usage of XmlWriter - it proved to be a valuable inspiration for me!
*this was my first SO question. What's the most appropriate way to close it? I didn't provide this as my own answer as it is not a real answer to my initial question (about Dictionary).
Assuming that your dictionary value are all simple types that can be converted to a string, you can create your own IXmlSerializable dictionary wrapper to store and retrieve the keys and values:
public class XmlKeyTextValueListWrapper<TValue> : CollectionWrapper<KeyValuePair<string, TValue>>, IXmlSerializable
{
public XmlKeyTextValueListWrapper() : base(new List<KeyValuePair<string, TValue>>()) { } // For deserialization.
public XmlKeyTextValueListWrapper(ICollection<KeyValuePair<string, TValue>> baseCollection) : base(baseCollection) { }
public XmlKeyTextValueListWrapper(Func<ICollection<KeyValuePair<string, TValue>>> getCollection) : base(getCollection) {}
#region IXmlSerializable Members
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.ReadXml(reader, this, converter);
}
public void WriteXml(XmlWriter writer)
{
var converter = TypeDescriptor.GetConverter(typeof(TValue));
XmlKeyValueListHelper.WriteXml(writer, this, converter);
}
#endregion
}
public static class XmlKeyValueListHelper
{
public static void WriteXml<T>(XmlWriter writer, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
foreach (var pair in collection)
{
writer.WriteStartElement(XmlConvert.EncodeName(pair.Key));
writer.WriteValue(typeConverter.ConvertToInvariantString(pair.Value));
writer.WriteEndElement();
}
}
public static void ReadXml<T>(XmlReader reader, ICollection<KeyValuePair<string, T>> collection, TypeConverter typeConverter)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var key = XmlConvert.DecodeName(reader.Name);
string value;
if (reader.IsEmptyElement)
{
value = string.Empty;
// Move past the end of item element
reader.Read();
}
else
{
// Read content and move past the end of item element
value = reader.ReadElementContentAsString();
}
collection.Add(new KeyValuePair<string,T>(key, (T)typeConverter.ConvertFromInvariantString(value)));
}
// Move past the end of the list element
reader.ReadEndElement();
}
public static void CopyTo<TValue>(this XmlKeyTextValueListWrapper<TValue> collection, ICollection<KeyValuePair<string, TValue>> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (collection == null)
dictionary.Clear();
else
{
if (collection.IsWrapperFor(dictionary)) // For efficiency
return;
var pairs = collection.ToList();
dictionary.Clear();
foreach (var item in pairs)
dictionary.Add(item);
}
}
}
public class CollectionWrapper<T> : ICollection<T>
{
readonly Func<ICollection<T>> getCollection;
public CollectionWrapper(ICollection<T> baseCollection)
{
if (baseCollection == null)
throw new ArgumentNullException();
this.getCollection = () => baseCollection;
}
public CollectionWrapper(Func<ICollection<T>> getCollection)
{
if (getCollection == null)
throw new ArgumentNullException();
this.getCollection = getCollection;
}
public bool IsWrapperFor(ICollection<T> other)
{
if (other == Collection)
return true;
var otherWrapper = other as CollectionWrapper<T>;
return otherWrapper != null && otherWrapper.IsWrapperFor(Collection);
}
ICollection<T> Collection { get { return getCollection(); } }
#region ICollection<T> Members
public void Add(T item)
{
Collection.Add(item);
}
public void Clear()
{
Collection.Clear();
}
public bool Contains(T item)
{
return Collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
Collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Collection.Count; }
}
public bool IsReadOnly
{
get { return Collection.IsReadOnly; }
}
public bool Remove(T item)
{
return Collection.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return Collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
And then use it like so:
[XmlRoot("products")]
public class Products
{
public Products()
{
Specifications = new Dictionary<string, string>();
}
[XmlIgnore]
[JsonProperty("specifications")] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public Dictionary<string, string> Specifications { get; set; }
[XmlElement("specifications")]
[JsonIgnore] // For testing purposes, I compare Json.NET serialization before and after XML serialization. You can remove this.
public XmlKeyTextValueListWrapper<string> XmlSpecifications
{
get
{
return new XmlKeyTextValueListWrapper<string>(() => this.Specifications);
}
set
{
value.CopyTo(Specifications = (Specifications ?? new Dictionary<string, string>()));
}
}
}
The fact that your dictionary values are simple types (directly convertible from and to text) makes it possible to avoid nested creations of XmlSerializer, which is more complex. See here for an example.
Make the dictionary NonSerialized
[XmlRoot("specifications")]
public class Specifications
{
[NonSerialized]
Dictionary<string, string> dict { get; set; }
[XmlElement("color")]
string color {get;set;}
[XmlElement("length")]
string length { get; set; }
[XmlElement("width")]
string width { get; set; }
public Specifications()
{
dict = new Dictionary<string, string>();
}
}
Related
I'm trying to expose a read-only dictionary that holds objects with a read-only interface. Internally, the dictionary is write-able, and so are the objects within (see below example code). My problem is that IReadOnlyDictionary doesn't support covariant conversions because of the reason outlined in the question here. This means I can't just expose my internal dictionary as a read only one.
So my question is, is there an efficient way to convert my internal dictionary to an IReadOnlyDictionary, or some other way to handle this? The options I can think of are:
Hold two internal dictionaries and keep them in sync.
Create a new dictionary when the property is accessed and cast all the objects within.
Cast the IReadOnly's back to NotReadOnly when using it internally.
1 seems like a pain, 2 seems highly inefficient. 3 sounds like the most promising at the moment, but is still ugly. Do I have any other options?
public class ExposesReadOnly
{
private Dictionary<int, NotReadOnly> InternalDict { get; set; }
public IReadOnlyDictionary<int, IReadOnly> PublicList
{
get
{
// This doesn't work...
return this.InternalDict;
}
}
// This class can be modified internally, but I don't want
// to expose this functionality.
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
}
public interface IReadOnly
{
string Name { get; }
}
You could write your own read-only wrapper for the dictionary, e.g.:
public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue
{
private IDictionary<TKey, TValue> _dictionary;
public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } }
public bool TryGetValue(TKey key, out TReadOnlyValue value)
{
TValue v;
var result = _dictionary.TryGetValue(key, out v);
value = v;
return result;
}
public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } }
public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } }
public int Count { get { return _dictionary.Count; } }
public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator()
{
return _dictionary
.Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value))
.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
I would suggest that you might want to define your own covariant interfaces, and include covariant access methods as well as a method which will create a read-only wrapper object which implements either IDictionary or IReadonlyDictionary with the desired types. Simply ignore IEnumerable<KeyValuePair<TKey,TValue>> within your interface.
Depending upon what you're doing, it may be helpful to define an IFetchByKey<out TValue> which is inherited by IFetchByKey<in TKey, out TValue>, with the former accepting queries for any type of object (given an object instance, a collection of Cat should be able to say whether it contains that instance, even if it's a type Dog or ToyotaPrius; the collection won't contain any instances of the latter types, and should be able to say so).
Maybe this solutions works for you:
public class ExposesReadOnly
{
private IDictionary<int, IReadOnly> InternalDict { get; set; }
public IReadOnlyDictionary<int, IReadOnly> PublicList
{
get
{
IReadOnlyDictionary<int, IReadOnly> dictionary = new ReadOnlyDictionary<int, IReadOnly>(InternalDict);
return dictionary;
}
}
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
public void AddSomeValue()
{
InternalDict = new Dictionary<int, NotReadOnly>();
InternalDict.Add(1, new NotReadOnly() { Name = "SomeValue" });
}
}
public interface IReadOnly
{
string Name { get; }
}
class Program
{
static void Main(string[] args)
{
ExposesReadOnly exposesReadOnly = new ExposesReadOnly();
exposesReadOnly.AddSomeValue();
Console.WriteLine(exposesReadOnly.PublicList[1].Name);
Console.ReadLine();
exposesReadOnly.PublicList[1].Name = "This is not possible!";
}
}
Hope this helps!
Greets
Depending on your use case, you might be able to get away with exposing a Func<int,IReadOnly>.
public class ExposesReadOnly
{
private Dictionary<int, NotReadOnly> InternalDict { get; set; }
public Func<int,IReadOnly> PublicDictionaryAccess
{
get
{
return (x)=>this.InternalDict[x];
}
}
// This class can be modified internally, but I don't want
// to expose this functionality.
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
}
public interface IReadOnly
{
string Name { get; }
}
Another approach for a specific lack of covariance:
A work around for a specific type of useful covariance on idictionary
public static class DictionaryExtensions
{
public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
this IDictionary<TKey, List<TValue>> toWrap)
{
var intermediate = toWrap.ToDictionary(a => a.Key, a =>a.Value!=null? a.Value.ToArray().AsEnumerable():null);
var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate);
return wrapper;
}
}
I'm trying to expose a read-only dictionary that holds objects with a read-only interface. Internally, the dictionary is write-able, and so are the objects within (see below example code). My problem is that IReadOnlyDictionary doesn't support covariant conversions because of the reason outlined in the question here. This means I can't just expose my internal dictionary as a read only one.
So my question is, is there an efficient way to convert my internal dictionary to an IReadOnlyDictionary, or some other way to handle this? The options I can think of are:
Hold two internal dictionaries and keep them in sync.
Create a new dictionary when the property is accessed and cast all the objects within.
Cast the IReadOnly's back to NotReadOnly when using it internally.
1 seems like a pain, 2 seems highly inefficient. 3 sounds like the most promising at the moment, but is still ugly. Do I have any other options?
public class ExposesReadOnly
{
private Dictionary<int, NotReadOnly> InternalDict { get; set; }
public IReadOnlyDictionary<int, IReadOnly> PublicList
{
get
{
// This doesn't work...
return this.InternalDict;
}
}
// This class can be modified internally, but I don't want
// to expose this functionality.
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
}
public interface IReadOnly
{
string Name { get; }
}
You could write your own read-only wrapper for the dictionary, e.g.:
public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue
{
private IDictionary<TKey, TValue> _dictionary;
public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } }
public bool TryGetValue(TKey key, out TReadOnlyValue value)
{
TValue v;
var result = _dictionary.TryGetValue(key, out v);
value = v;
return result;
}
public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } }
public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } }
public int Count { get { return _dictionary.Count; } }
public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator()
{
return _dictionary
.Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value))
.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
I would suggest that you might want to define your own covariant interfaces, and include covariant access methods as well as a method which will create a read-only wrapper object which implements either IDictionary or IReadonlyDictionary with the desired types. Simply ignore IEnumerable<KeyValuePair<TKey,TValue>> within your interface.
Depending upon what you're doing, it may be helpful to define an IFetchByKey<out TValue> which is inherited by IFetchByKey<in TKey, out TValue>, with the former accepting queries for any type of object (given an object instance, a collection of Cat should be able to say whether it contains that instance, even if it's a type Dog or ToyotaPrius; the collection won't contain any instances of the latter types, and should be able to say so).
Maybe this solutions works for you:
public class ExposesReadOnly
{
private IDictionary<int, IReadOnly> InternalDict { get; set; }
public IReadOnlyDictionary<int, IReadOnly> PublicList
{
get
{
IReadOnlyDictionary<int, IReadOnly> dictionary = new ReadOnlyDictionary<int, IReadOnly>(InternalDict);
return dictionary;
}
}
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
public void AddSomeValue()
{
InternalDict = new Dictionary<int, NotReadOnly>();
InternalDict.Add(1, new NotReadOnly() { Name = "SomeValue" });
}
}
public interface IReadOnly
{
string Name { get; }
}
class Program
{
static void Main(string[] args)
{
ExposesReadOnly exposesReadOnly = new ExposesReadOnly();
exposesReadOnly.AddSomeValue();
Console.WriteLine(exposesReadOnly.PublicList[1].Name);
Console.ReadLine();
exposesReadOnly.PublicList[1].Name = "This is not possible!";
}
}
Hope this helps!
Greets
Depending on your use case, you might be able to get away with exposing a Func<int,IReadOnly>.
public class ExposesReadOnly
{
private Dictionary<int, NotReadOnly> InternalDict { get; set; }
public Func<int,IReadOnly> PublicDictionaryAccess
{
get
{
return (x)=>this.InternalDict[x];
}
}
// This class can be modified internally, but I don't want
// to expose this functionality.
private class NotReadOnly : IReadOnly
{
public string Name { get; set; }
}
}
public interface IReadOnly
{
string Name { get; }
}
Another approach for a specific lack of covariance:
A work around for a specific type of useful covariance on idictionary
public static class DictionaryExtensions
{
public static IReadOnlyDictionary<TKey, IEnumerable<TValue>> ToReadOnlyDictionary<TKey, TValue>(
this IDictionary<TKey, List<TValue>> toWrap)
{
var intermediate = toWrap.ToDictionary(a => a.Key, a =>a.Value!=null? a.Value.ToArray().AsEnumerable():null);
var wrapper = new ReadOnlyDictionary<TKey, IEnumerable<TValue>>(intermediate);
return wrapper;
}
}
This question already has answers here:
How to serialize/deserialize a custom collection with additional properties using Json.Net
(6 answers)
Closed 7 years ago.
I created a custom List class that maintains a set of item ids for performance reasons:
public class MyCustomList : List<ItemWithID>
{
private HashSet<int> itemIDs = new HashSet<int>();
public MyCustomList()
{
}
[JsonConstructor]
public MyCustomList(IEnumerable<ItemWithID> collection)
: base(collection)
{
itemIDs = new HashSet<int>(this.Select(i => i.ID));
}
public new void Add(ItemWithID item)
{
base.Add(item);
itemIDs.Add(item.ID);
}
public new bool Remove(ItemWithID item)
{
var removed = base.Remove(item);
if (removed)
{
itemIDs.Remove(item.ID);
}
return removed;
}
public bool ContainsID(int id)
{
return itemIDs.Contains(id);
}
}
I want to deserialize this List from a simply JSON array e.g.:
JsonConvert.DeserializeObject<MyCustomList>("[{ID:8},{ID:9}]");
this will cause JSON.NET to call only the empty constructor, so my itemIDs list remains empty. Also the Add method is not called.
How does JSON.NET add the items to the list so I can add logic at that place.
(this is about deserialization without properties that should be persistent in the json string, so the suggested duplicate question has nothing to do with this one)
Solution:
public class MyCustomList : IList<ItemWithID>
{
private HashSet<int> itemIDs = new HashSet<int>();
private List<ItemWithID> actualList = new List<ItemWithID>();
public void Add(ItemWithID item)
{
actualList.Add(item);
itemIDs.Add(item.ID);
}
public bool Remove(ItemWithID item)
{
var removed = actualList.Remove(item);
if (removed)
{
itemIDs.Remove(item.ID);
}
return removed;
}
public bool ContainsID(int id)
{
return itemIDs.Contains(id);
}
public int IndexOf(ItemWithID item)
{
return actualList.IndexOf(item);
}
public void Insert(int index, ItemWithID item)
{
actualList.Insert(index, item);
itemIDs.Add(item.ID);
}
public void RemoveAt(int index)
{
itemIDs.Remove(actualList[index].ID);
actualList.RemoveAt(index);
}
public ItemWithID this[int index]
{
get
{
return actualList[index];
}
set
{
actualList[index] = value;
if (!itemIDs.Contains(value.ID))
{
itemIDs.Add(value.ID);
}
}
}
public void Clear()
{
actualList.Clear();
itemIDs.Clear();
}
public bool Contains(ItemWithID item)
{
return actualList.Contains(item);
}
public void CopyTo(ItemWithID[] array, int arrayIndex)
{
actualList.CopyTo(array, arrayIndex);
}
public int Count
{
get { return actualList.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<ItemWithID> GetEnumerator()
{
return actualList.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
You could deserialize to the form the constructor expects, then call that yourself.
var collection = JsonConvert.DeserializeObject<ItemID[]>("[{ID:8},{ID:9}]");
var aCustomList = new MyCustomList(collection);
Your problem isn't with JSON deserialization, your MyCustomList class needs to derive from IList if you want to be able to override the Add method. See THIS for details.
I use C# 3 on microsoft .net 3.5 (VS2008).
I have a problem with de-serialization. I use DataContract and DataMember in a hierarchy of classes that I want to be serializable.
However, I also have polymorphism in one container, so I need to pass a list of known types to the serializers. My collection is a serializable dictionary that I found on the net:
[Serializable]
[XmlRoot("dictionary")]
public class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
if (reader.IsEmptyElement)
{
return;
}
// Move past container
if (!reader.Read())
{
throw new XmlException("Error in Deserialization of Dictionary");
}
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
// for serialization/deserialization pruporses
public void SetKnownTypes(Type[] extraTypes)
{
this.extraTypes = extraTypes;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
This is the one that holds a polymorphic object tree in its TVal.
So you see I have modified the original code to add a list of known types, which works well for serialization, because I set this list in my superior classes constructors. (the classes that holds the dictionary instance).
This list of known types happens to be discovered at runtime, using this function:
static public class TypeDiscoverer
{
public enum EFilter { All, OnlyConcreteTypes }
public enum EAssemblyRange { AllAppDomain, OnlyAssemblyOfRequestedType }
public static List<Type> FindAllDerivedTypes<T>(EFilter typesFilter, EAssemblyRange assembRange)
{
HashSet< Type > founds = new HashSet<Type>();
Assembly[] searchDomain =
assembRange == EAssemblyRange.OnlyAssemblyOfRequestedType ?
new Assembly[1] { Assembly.GetAssembly(typeof(T)) }
: AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in searchDomain)
{
founds = new HashSet<Type>(founds.Concat(FindAllDerivedTypes<T>(a, typesFilter)));
}
return founds.ToList();
}
public static List<Type> FindAllDerivedTypes<T>(Assembly assembly, EFilter typesFilter)
{
var derivedType = typeof(T);
List<Type> result = assembly
.GetTypes()
.Where(t =>
t != derivedType &&
derivedType.IsAssignableFrom(t)
).ToList();
if (typesFilter == EFilter.OnlyConcreteTypes)
result = result.Where(x => !x.IsAbstract).ToList();
return result;
}
}
This dynamic system allows me to discover the known types by just knowing the base class. Which is something I always wondered why do the framework does not provide this feature... but well..
So my issue is that, my serializable dictionary, is an utility class, I can not specialize it to hardcode the list of known types, even less so because it is discovered at run time.
Deserialization works on uninitialized object, and therefore I can not provide the list of known types to the dictionary de-serializer.
Of course, for the moment, I will workaround that problem by discovering the list of known types using my FindAllDerivedTypes functions on TVal directly in the dictionary.
But as it is less scalable than an exeternally-provided type list, I'd like to know if anyone can provide me with a real fix.
thanks a lot.
You can use custom XmlReader (or pass the types in some static / thread-local-storage variable).
IdeOne example
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
namespace DynaXmlSer {
public class KnownTypesXmlReader: XmlTextReader {
public KnownTypesXmlReader(Stream ios): base(ios) {}
public Type[] ExtraTypes = null;
}
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
public void SetKnownTypes(Type[] extraTypes) {
this.extraTypes = extraTypes;
valueSerializer = null;
keySerializer = null;
}
void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) {
if (reader.IsEmptyElement)
return;
if (!reader.Read())
throw new XmlException("Error in Deserialization of Dictionary");
//HERE IS THE TRICK
if (reader is KnownTypesXmlReader)
SetKnownTypes(((KnownTypesXmlReader)reader).ExtraTypes);
//reader.ReadStartElement(DictionaryNodeName);
while (reader.NodeType != XmlNodeType.EndElement)
{
reader.ReadStartElement(ItemNodeName);
reader.ReadStartElement(KeyNodeName);
TKey key = (TKey)KeySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement(ValueNodeName);
TVal value = (TVal)ValueSerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadEndElement();
this.Add(key, value);
reader.MoveToContent();
}
//reader.ReadEndElement();
reader.ReadEndElement(); // Read End Element to close Read of containing node
}
}
public class BasicElement {
private string name;
public string Name {
get { return name; }
set { name = value; } }
}
public class ElementOne: BasicElement {
private string one;
public string One {
get { return one; }
set { one = value; }
}
}
public class ElementTwo: BasicElement {
private string two;
public string Two {
get { return two; }
set { two = value; }
}
}
public class Program {
static void Main(string[] args) {
Type[] extraTypes = new Type[] { typeof(ElementOne), typeof(ElementTwo) };
SerializableSortedDictionary<string, BasicElement> dict = new SerializableSortedDictionary<string,BasicElement>();
dict.SetKnownTypes(extraTypes);
dict["foo"] = new ElementOne() { Name = "foo", One = "FOO" };
dict["bar"] = new ElementTwo() { Name = "bar", Two = "BAR" };
XmlSerializer ser = new XmlSerializer(typeof(SerializableSortedDictionary<string, BasicElement>));
MemoryStream mem = new MemoryStream();
ser.Serialize(mem, dict);
Console.WriteLine(Encoding.UTF8.GetString(mem.ToArray()));
mem.Position = 0;
using(XmlReader rd = new KnownTypesXmlReader(mem) { ExtraTypes = extraTypes })
dict = (SerializableSortedDictionary<string, BasicElement>)ser.Deserialize(rd);
foreach(KeyValuePair<string, BasicElement> e in dict) {
Console.Write("Key = {0}, Name = {1}", e.Key, e.Value.Name);
if(e.Value is ElementOne) Console.Write(", One = {0}", ((ElementOne)e.Value).One);
else if(e.Value is ElementTwo) Console.Write(", Two = {0}", ((ElementTwo)e.Value).Two);
Console.WriteLine(", Type = {0}", e.Value.GetType().Name);
}
}
}
[Serializable]
[XmlRoot("dictionary")]
public partial class SerializableSortedDictionary<TKey, TVal>
: SortedDictionary<TKey, TVal>, IXmlSerializable
{
#region Constants
private const string DictionaryNodeName = "Dictionary";
private const string ItemNodeName = "Item";
private const string KeyNodeName = "Key";
private const string ValueNodeName = "Value";
#endregion
#region Constructors
public SerializableSortedDictionary()
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary)
: base(dictionary)
{
}
public SerializableSortedDictionary(IComparer<TKey> comparer)
: base(comparer)
{
}
public SerializableSortedDictionary(IDictionary<TKey, TVal> dictionary, IComparer<TKey> comparer)
: base(dictionary, comparer)
{
}
#endregion
#region IXmlSerializable Members
void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
{
//writer.WriteStartElement(DictionaryNodeName);
foreach (KeyValuePair<TKey, TVal> kvp in this)
{
writer.WriteStartElement(ItemNodeName);
writer.WriteStartElement(KeyNodeName);
KeySerializer.Serialize(writer, kvp.Key);
writer.WriteEndElement();
writer.WriteStartElement(ValueNodeName);
ValueSerializer.Serialize(writer, kvp.Value);
writer.WriteEndElement();
writer.WriteEndElement();
}
//writer.WriteEndElement();
}
System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
return null;
}
public Type[] extraTypes = null;
#endregion
#region Private Properties
protected XmlSerializer ValueSerializer
{
get
{
if (valueSerializer == null)
{
if (extraTypes == null)
valueSerializer = new XmlSerializer(typeof(TVal));
else
valueSerializer = new XmlSerializer(typeof(TVal), extraTypes);
}
return valueSerializer;
}
}
private XmlSerializer KeySerializer
{
get
{
if (keySerializer == null)
{
if (extraTypes == null)
keySerializer = new XmlSerializer(typeof(TKey));
else
keySerializer = new XmlSerializer(typeof(TKey), extraTypes);
}
return keySerializer;
}
}
#endregion
#region Private Members
[NonSerialized]
private XmlSerializer keySerializer = null;
[NonSerialized]
private XmlSerializer valueSerializer = null;
#endregion
}
}
Output:
Key = bar, Name = bar, Two = BAR, Type = ElementTwo
Key = foo, Name = foo, One = FOO, Type = ElementOne
You can comment out the passing of the types and deserialization fails: using(XmlReader rd = new KnownTypesXmlReader(mem) /* { ExtraTypes = extraTypes } */)
ADDED COMMENTS:
I was searching for the solution in this order:
Use what is already there ([XmlInclude]) if you can. (Your runtime constraint disallows this.)
Customize some class involved - the problem was in void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) thus XmlReader was perfect candidate. I'd suggest using iterface for final solution (e.g. interface KnownTypes { Type[] GetKnownTypes(object me, string hint, params Type[] involved); })
If above fails (if you were not in control of the deserializer) use static variable (or thread-local-storage for use with multiple threads, or static synchronized(weak)dictionary) for additional configuration. (Not perfect and it seems that you can use option 2.)
First of all XmlSerializer ignores [Serializable], [NonSerialized], [DataContract] and [DataMember] attributes - it is controlled either by IXmlSerializable interface (which completely changes behavior of object serialization) or by default it serializes all public members/properties of an object, and you can give hints to XmlSerializer via attributes like [XmlRoot], [XmlAttribute], [XmlIgnore] [XmlArray], [XmlElement], [XmlArrayItem], [XmlInclude], [XmlText], etc.
The functionality you're after is already included in those attributes. Lets assume you have SerializableSortedDictionary<string, Car> where Car is class with subclasses Volvo and Audi.
[XmlInclude(typeof(Audi))]
[XmlInclude(typeof(Volvo))]
public class Car {
private string m_Name = "Car";
public virtual string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Audi : Car {
private string m_Name = "Audi";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
public class Volvo : Car {
private string m_Name = "Volvo";
public override string Name {
get { return m_Name; }
set { m_Name = value; }
}
}
All you need is to decorate the base class with all possible sub classes via the XmlInclude
var dic = new SerializableSortedDictionary<string, Car>();
dic.Add("0", new Car());
dic.Add("1", new Audi());
dic.Add("2", new Volvo());
var serializer = new XmlSerializer(typeof(SerializableSortedDictionary<string, Car>));
var builder = new StringBuilder();
using(var writer = new StringWriter(builder)) {
serializer.Serialize(writer, dic);
}
Deserialization works as well. You may notice xsi:type attribute in resulting xml - that how xml serializer persist information about types. It essentially impossible to "guess" type from serialized object without specifying it. It won't work for generic types, that weren't specified via XmlInclude - that's a security feature (if an attacker can make you make instance of any object he like when you're parsing xml feed you may run into serious troubles).
Is there a collection in C# that will not let you add duplicate items to it? For example, with the silly class of
public class Customer {
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public override int GetHashCode() {
return (FirstName + LastName + Address).GetHashCode();
}
public override bool Equals(object obj) {
Customer C = obj as Customer;
return C != null && String.Equals(this.FirstName, C.FirstName) && String.Equals(this.LastName, C.LastName) && String.Equals(this.Address, C.Address);
}
}
The following code will (obviously) throw an exception:
Customer Adam = new Customer { Address = "A", FirstName = "Adam", LastName = "" };
Customer AdamDup = new Customer { Address = "A", FirstName = "Adam", LastName = "" };
Dictionary<Customer, bool> CustomerHash = new Dictionary<Customer, bool>();
CustomerHash.Add(Adam, true);
CustomerHash.Add(AdamDup, true);
But is there a class that will similarly guarantee uniqueness, but without KeyValuePairs? I thought HashSet<T> would do that, but having read the docs it seems that class is just a set implementation (go figure).
HashSet<T> is what you're looking for. From MSDN (emphasis added):
The HashSet<T> class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
Note that the HashSet<T>.Add(T item) method returns a bool -- true if the item was added to the collection; false if the item was already present.
How about just an extension method on HashSet?
public static void AddOrThrow<T>(this HashSet<T> hash, T item)
{
if (!hash.Add(item))
throw new ValueExistingException();
}
From the HashSet<T> page on MSDN:
The HashSet(Of T) class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.
(emphasis mine)
If all you need is to ensure uniqueness of elements, then HashSet is what you need.
What do you mean when you say "just a set implementation"? A set is (by definition) a collection of unique elements that doesn't save element order.
Just to add my 2 cents...
if you need a ValueExistingException-throwing HashSet<T> you can also create your collection easily:
public class ThrowingHashSet<T> : ICollection<T>
{
private HashSet<T> innerHash = new HashSet<T>();
public void Add(T item)
{
if (!innerHash.Add(item))
throw new ValueExistingException();
}
public void Clear()
{
innerHash.Clear();
}
public bool Contains(T item)
{
return innerHash.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
innerHash.CopyTo(array, arrayIndex);
}
public int Count
{
get { return innerHash.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
return innerHash.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
return innerHash.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
this can be useful for example if you need it in many places...
You can try HashSet<T>
You may look into something kind of Unique List as follows
public class UniqueList<T>
{
public List<T> List
{
get;
private set;
}
List<T> _internalList;
public static UniqueList<T> NewList
{
get
{
return new UniqueList<T>();
}
}
private UniqueList()
{
_internalList = new List<T>();
List = new List<T>();
}
public void Add(T value)
{
List.Clear();
_internalList.Add(value);
List.AddRange(_internalList.Distinct());
//return List;
}
public void Add(params T[] values)
{
List.Clear();
_internalList.AddRange(values);
List.AddRange(_internalList.Distinct());
// return List;
}
public bool Has(T value)
{
return List.Contains(value);
}
}
and you can use it like follows
var uniquelist = UniqueList<string>.NewList;
uniquelist.Add("abc","def","ghi","jkl","mno");
uniquelist.Add("abc","jkl");
var _myList = uniquelist.List;
will only return "abc","def","ghi","jkl","mno" always even when duplicates are added to it
As an overall check different methods here are 4 ways to check if the collection has not any duplicates:
public static bool LinqAny<T>(IEnumerable<T> enumerable)
{
HashSet<T> set = new();
return enumerable.Any(element => !set.Add(element));
}
public static bool LinqAll<T>(IEnumerable<T> enumerable)
{
HashSet<T> set = new();
return !enumerable.All(set.Add);
}
public static bool LinqDistinct<T>(IEnumerable<T> enumerable)
{
return enumerable.Distinct().Count() != enumerable.Count();
}
public static bool ToHashSet<T>(IEnumerable<T> enumerable)
{
return enumerable.ToHashSet().Count != enumerable.Count();
}