protobuf-net: Inheriting custom collection - c#

I've a custom collection, which is a simple implementation encapsulating List<>:
[DebuggerDisplay("Count = {Count}")]
[Serializable()]
[ProtoInclude(100, typeof(SimpleTreeNodeList<>))]
public class SimpleList<T> : IList<T>, ICollection<T>, IEnumerable<T>, ICollection
{
... all methods redirect to a private List<T> ...
}
The reason for this is that I need to override the Add method, which isn't possible for a standard List. I need to create a tree, and the collection contains a list of children. When addin an element this the collection, I need to bind it to the parent.
So, I'm inheriting this SimpleList<T> class in SimpleTreeNodeList<>
If I serialize / deserilalize a SimpleList<T>, it works like a charm.
But if serialize / deserilalize a SimpleTreeNodeList<T> it doesn't work : no error, but the collection is empty when deserialized.
I don't understand why, as for other type it works correctly ?
I've read I've to set ProtoInclude attribute on the base class, it's what I've done but still the same problem
This is the implementation code:
[CollectionDataContract(IsReference = true)]
[Serializable]
[ProtoContract]
public class SimpleTreeNodeList<T> : SimpleList<SimpleTreeNode<T>>
{
#region CTor
public SimpleTreeNodeList()
: base()
{
}
public SimpleTreeNodeList(int capacity)
: base(capacity)
{
}
public SimpleTreeNodeList(SimpleTreeNode<T> parent)
{
this.Parent = parent;
}
#endregion
[IgnoreDataMember]
[ProtoMember(1, AsReference = true)]
public SimpleTreeNode<T> Parent { get; set; }
public override void Add(SimpleTreeNode<T> node)
{
base.Add(node);
node.Parent = Parent;
}
public void AddRange(IEnumerable<SimpleTreeNode<T>> collection)
{
foreach (var node in collection)
this.Add(node);
}
public override void Insert(int position, SimpleTreeNode<T> node)
{
base.Insert(position, node);
node.Parent = this.Parent;
}
public override void InsertRange(int position, IEnumerable<SimpleTreeNode<T>> nodes)
{
base.InsertRange(position, nodes);
foreach (var node in nodes)
node.Parent = this.Parent;
}
public SimpleTreeNode<T> Add(T Value)
{
SimpleTreeNode<T> node = new SimpleTreeNode<T>(Parent);
node.Value = Value;
base.Add(node);
return node;
}
public void Sort(Comparison<T> comparison)
{
this.Sort((x, y) => comparison(x, y));
}
public override string ToString()
{
return "Count=" + Count.ToString();
}
}
My simple test case:
[Test]
public void TestSimpleTreeNodeListSerialization()
{
var data = new SimpleTreeNodeList<string>();
data.Add("Un");
data.Add("Deux");
data.Add("Trois");
var path = Path.GetTempFileName();
try
{
File.WriteAllBytes(path, serialize(data));
var data2 = deserialize<SimpleTreeNodeList<string>>(File.ReadAllBytes(path));
Assert.IsNotNull(data2);
Assert.AreEqual(3, data2.Count);
for (int i = 0; i < data.Count; i++)
{
Assert.AreEqual(data[i].Value, data2[i].Value);
}
}
finally
{
if (File.Exists(path))
File.Delete(path);
}
}
private byte[] serialize<T>(T data)
{
var formatter = Serializer.CreateFormatter<T>();
using (var stream = new System.IO.MemoryStream())
{
formatter.Serialize(stream, data);
return stream.ToArray();
}
}
private T deserialize<T>(byte[] data)
{
var formatter = Serializer.CreateFormatter<T>();
using (var stream = new System.IO.MemoryStream(data))
{
return (T)formatter.Deserialize(stream);
}
}

Related

Deserialize to custom list [duplicate]

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.

PropertyGrid expandable collection

I want to automatically show every IList as expandable in my PropertyGrid (By "expandable", I obviously mean that the items will be shown).
I don't want to use attributes on each list (Once again, I want it to work for EVERY IList)
I tried to achive it by using a custom PropertyDescriptor and an ExpandableObjectConverter. It works, but after I delete items from the list, the PropertyGrid is not being refreshed, still displaying the deleted items.
I tried to use ObservableCollection along with raising OnComponentChanged, and also RefreshProperties attribute, but nothing worked.
This is my code:
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
private IList _collection;
private readonly int _index = -1;
internal event EventHandler RefreshRequired;
public ExpandableCollectionPropertyDescriptor(IList coll, int idx) : base(GetDisplayName(coll, idx), null)
{
_collection = coll
_index = idx;
}
public override bool SupportsChangeEvents
{
get { return true; }
}
private static string GetDisplayName(IList list, int index)
{
return "[" + index + "] " + CSharpName(list[index].GetType());
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override AttributeCollection Attributes
{
get
{
return new AttributeCollection(null);
}
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get
{
return _collection.GetType();
}
}
public override object GetValue(object component)
{
OnRefreshRequired();
return _collection[_index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return _index.ToString(); }
}
public override Type PropertyType
{
get { return _collection[_index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
_collection[_index] = value;
}
protected virtual void OnRefreshRequired()
{
var handler = RefreshRequired;
if (handler != null) handler(this, EventArgs.Empty);
}
}
.
internal class ExpandableCollectionConverter : ExpandableObjectConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
{
if (destType == typeof(string))
{
return "(Collection)";
}
return base.ConvertTo(context, culture, value, destType);
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
IList collection = value as IList;
PropertyDescriptorCollection pds = new PropertyDescriptorCollection(null);
for (int i = 0; i < collection.Count; i++)
{
ExpandableCollectionPropertyDescriptor pd = new ExpandableCollectionPropertyDescriptor(collection, i);
pd.RefreshRequired += (sender, args) =>
{
var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
};
pds.Add(pd);
}
// return the property descriptor Collection
return pds;
}
}
And I use it for all ILists with the following line:
TypeDescriptor.AddAttributes(typeof (IList), new TypeConverterAttribute(typeof(ExpandableCollectionConverter)));
Some Clarifications
I want the grid to automatically update when I change the list. Refreshing when another property changes, does not help.
A solution that works, is a solution where:
If you expand the list while it is empty, and then add items, the grid is refreshed with the items expanded
If you add items to the list, expand it, and then remove items (without collapsing), the grid is refreshed with the items expanded, and not throwing ArgumentOutOfRangeException because it is trying to show items that were deleted already
I want this whole thing for a configuration utility. Only the PropertyGrid should change the collections
IMPORTANT EDIT:
I did manage to make the expanded collections update with Reflection, and calling NotifyValueGivenParent method on the context object when the PropertyDescriptor GetValue method is called (when RefreshRequired event is raised):
var notifyValueGivenParentMethod = context.GetType().GetMethod("NotifyValueGivenParent", BindingFlags.NonPublic | BindingFlags.Instance);
notifyValueGivenParentMethod.Invoke(context, new object[] {context.Instance, 1});
It works perfectly, except it causes the event to be raised infinite times, because calling NotifyValueGivenParent causes a reload of the PropertyDescriptor, and therfore, raising the event, and so on.
I tried to solve it by adding a simple flag that will prevent the reloading if it is already reloading, but for some reason NotifyValueGivenParent behaves asynchronously, and therefore the reloading happens after the flag is turned off.
Maybe it is another direction to explore. The only problem is the recursion
There is no need for using ObservableCollection. You can modify your descriptor class as follows:
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
private IList collection;
private readonly int _index;
public ExpandableCollectionPropertyDescriptor(IList coll, int idx)
: base(GetDisplayName(coll, idx), null)
{
collection = coll;
_index = idx;
}
private static string GetDisplayName(IList list, int index)
{
return "[" + index + "] " + CSharpName(list[index].GetType());
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType)
return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments()
.Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return this.collection.GetType(); }
}
public override object GetValue(object component)
{
return collection[_index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return _index.ToString(CultureInfo.InvariantCulture); }
}
public override Type PropertyType
{
get { return collection[_index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
collection[_index] = value;
}
}
Instead of the ExpandableCollectionConverter I would derive the CollectionConverter class, so you can still use the ellipsis button to edit the collection in the old way (so you can add/remove items if the collection is not read-only):
public class ListConverter : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
IList list = value as IList;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
for (int i = 0; i < list.Count; i++)
{
object item = list[i];
items.Add(new ExpandableCollectionPropertyDescriptor(list, i));
}
return items;
}
}
And I would use this ListConverter on the properties where I want to see expandable list. Of course, you can register the type converter generally as you do in your example, but that overrides everything, which might not be overall intended.
public class MyClass
{
[TypeConverter(typeof(ListConverter))]
public List<int> List { get; set; }
public MyClass()
{
List = new List<int>();
}
[RefreshProperties(RefreshProperties.All)]
[Description("Change this property to regenerate the List")]
public int Count
{
get { return List.Count; }
set { List = Enumerable.Range(1, value).ToList(); }
}
}
Important: The RefreshProperties attribute should be defined for the properties that change other properties. In this example, changing the Count replaces the whole list.
Using it as propertyGrid1.SelectedObject = new MyClass(); produces the following result:
I don't want it to refresh when other property refreshes. I want it to refresh when the list is changed. I add items to the list, expand it, add more items, but the items are not updated
This is a typical misuse of PropertyGrid. It is for configuring a component, and not for reflecting the concurrent changes on-the-fly by an external source. Even wrapping the IList into an ObservableCollection will not help you because it is used only by your descriptor, while the external source manipulates directly the underlying IList instance.
What you can still do is an especially ugly hack:
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
// Subscribe to this event from the form with the property grid
public static event EventHandler CollectionChanged;
// Tuple elements: The owner of the list, the list, the serialized content of the list
// The reference to the owner is a WeakReference because you cannot tell the
// PropertyDescriptor that you finished the editing and the collection
// should be removed from the list.
// Remark: The references here may survive the property grid's life
private static List<Tuple<WeakReference, IList, byte[]>> collections;
private static Timer timer;
public ExpandableCollectionPropertyDescriptor(ITypeDescriptorContext context, IList collection, ...)
{
AddReference(context.Instance, collection);
// ...
}
private static void AddReference(object owner, IList collection)
{
// TODO:
// - serialize the collection into a byte array (BinaryFormatter) and add it to the collections list
// - if this is the first element, initialize the timer
}
private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// TODO: Cycle through the collections elements
// - If WeakReference is not alive, remove the item from the list
// - Serialize the list again and compare the result to the last serialized content
// - If there a is difference:
// - Update the serialized content
// - Invoke the CollectionChanged event. The sender is the owner (WeakReference.Target).
}
}
Now you can use it like this:
public class Form1 : Form
{
MyObject myObject = new MyObject();
public MyForm()
{
InitializeComponent();
ExpandableCollectionPropertyDescriptor.CollectionChanged += CollectionChanged();
propertyGrid.SelectedObject = myObject;
}
private void CollectionChanged(object sender, EventArgs e)
{
if (sender == myObject)
propertyGrid.SelectedObject = myObject;
}
}
But honestly, I would not use it at all. It has serious flaws:
What if a collection element is changed by the PropertyGrid, but the timer has not updated the last external change yet?
The implementer of the IList must be serializable
Ridiculous performance overhead
Though using weak references may reduce memory leaks, it does not help if the objects to edit have longer life cycle than the editor form, because they will remain in the static collection
Putting it all together, this works:
Here is the class with the lists that we will put an instance of in our property grid. Also to demonstrate usage with a list of a complex object, I have the NameAgePair class.
public class SettingsStructure
{
public SettingsStructure()
{
//To programmatically add this to properties that implement ILIST for the naming of the edited node and child items:
//[TypeConverter(typeof(ListConverter))]
TypeDescriptor.AddAttributes(typeof(IList), new TypeConverterAttribute(typeof(ListConverter)));
//To programmatically add this to properties that implement ILIST for the refresh and expansion of the edited node
//[Editor(typeof(CollectionEditorBase), typeof(System.Drawing.Design.UITypeEditor))]
TypeDescriptor.AddAttributes(typeof(IList), new EditorAttribute(typeof(CollectionEditorBase), typeof(UITypeEditor)));
}
public List<string> ListOfStrings { get; set; } = new List<string>();
public List<string> AnotherListOfStrings { get; set; } = new List<string>();
public List<int> ListOfInts { get; set; } = new List<int>();
public List<NameAgePair> ListOfNameAgePairs { get; set; } = new List<NameAgePair>();
}
public class NameAgePair
{
public string Name { get; set; } = "";
public int Age { get; set; } = 0;
public override string ToString()
{
return $"{Name} ({Age})";
}
}
Here is the ListConverter class to handle making the child nodes.
public class ListConverter : CollectionConverter
{
public override bool GetPropertiesSupported(ITypeDescriptorContext context)
{
return true;
}
public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{
IList list = value as IList;
if (list == null || list.Count == 0)
return base.GetProperties(context, value, attributes);
var items = new PropertyDescriptorCollection(null);
for (int i = 0; i < list.Count; i++)
{
object item = list[i];
items.Add(new ExpandableCollectionPropertyDescriptor(list, i));
}
return items;
}
public override object ConvertTo(ITypeDescriptorContext pContext, CultureInfo pCulture, object value, Type pDestinationType)
{
if (pDestinationType == typeof(string))
{
IList v = value as IList;
int iCount = (v == null) ? 0 : v.Count;
return $"({iCount} Items)";
}
return base.ConvertTo(pContext, pCulture, value, pDestinationType);
}
}
Here is the ExpandableCollectionPropertyDescriptor class for the individual items.
public class ExpandableCollectionPropertyDescriptor : PropertyDescriptor
{
private IList _Collection;
private readonly int _Index;
public ExpandableCollectionPropertyDescriptor(IList coll, int idx) : base(GetDisplayName(coll, idx), null)
{
_Collection = coll;
_Index = idx;
}
private static string GetDisplayName(IList list, int index)
{
return "[" + index + "] " + CSharpName(list[index].GetType());
}
private static string CSharpName(Type type)
{
var sb = new StringBuilder();
var name = type.Name;
if (!type.IsGenericType) return name;
sb.Append(name.Substring(0, name.IndexOf('`')));
sb.Append("<");
sb.Append(string.Join(", ", type.GetGenericArguments().Select(CSharpName)));
sb.Append(">");
return sb.ToString();
}
public override bool CanResetValue(object component)
{
return true;
}
public override Type ComponentType
{
get { return this._Collection.GetType(); }
}
public override object GetValue(object component)
{
return _Collection[_Index];
}
public override bool IsReadOnly
{
get { return false; }
}
public override string Name
{
get { return _Index.ToString(CultureInfo.InvariantCulture); }
}
public override Type PropertyType
{
get { return _Collection[_Index].GetType(); }
}
public override void ResetValue(object component)
{
}
public override bool ShouldSerializeValue(object component)
{
return true;
}
public override void SetValue(object component, object value)
{
_Collection[_Index] = value;
}
}
And then the CollectionEditorBase class for refreshing the property grid after the collection editor is closed.
public class CollectionEditorBase : CollectionEditor
{
protected PropertyGrid _PropertyGrid;
private bool _ExpandedBefore;
private int _CountBefore;
public CollectionEditorBase(Type type) : base(type) { }
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
//Record entry state of property grid item
GridItem giThis = (GridItem)provider;
_ExpandedBefore = giThis.Expanded;
_CountBefore = (giThis.Value as IList).Count;
//Get the grid so later we can refresh it on close of editor
PropertyInfo piOwnerGrid = provider.GetType().GetProperty("OwnerGrid", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
_PropertyGrid = (PropertyGrid)piOwnerGrid.GetValue(provider);
//Edit the collection
return base.EditValue(context, provider, value);
}
protected override CollectionForm CreateCollectionForm()
{
CollectionForm cf = base.CreateCollectionForm();
cf.FormClosing += delegate (object sender, FormClosingEventArgs e)
{
_PropertyGrid.Refresh();
//Because nothing changes which grid item is the selected one, expand as desired
if (_ExpandedBefore || _CountBefore == 0) _PropertyGrid.SelectedGridItem.Expanded = true;
};
return cf;
}
protected override object CreateInstance(Type itemType)
{
//Fixes the "Constructor on type 'System.String' not found." when it is an empty list of strings
if (itemType == typeof(string)) return string.Empty;
else return Activator.CreateInstance(itemType);
}
}
Now the usage produces:
And performing various operations produces:
You can tweak it to operate like you like.

Serialize Dictionary<string,string> member to XML elements and data

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>();
}
}

Linq query returning null with custom ConfigurationElementCollection implementing IEnumerable

I have a custom ConfigurationElementCollection called EnvironmentElementCollection to configure my different environments within a class library I'm writing. I have a static property in a class to get a collection of all the environments that are listed in that section. There is a property of the contained items (EnvironmentElement) that indicates the environment is a "login" environment. My goal is to be able to filter the collection with Linq to get only the "login" environments, but the Linq query always returns null. I implemented IEnumerable using this tutorial, but I can't figure out what I'm doing wrong.
Configuration Classes
public class EnvironmentSection : ConfigurationSection {
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propEnvironments;
static EnvironmentSection() {
propEnvironments = new ConfigurationProperty(null, typeof(EnvironmentElementCollection), null, ConfigurationPropertyOptions.IsDefaultCollection);
properties = new ConfigurationPropertyCollection { propEnvironments };
}
protected override ConfigurationPropertyCollection Properties {
get {
return properties;
}
}
public EnvironmentElementCollection Environments {
get {
return this[propEnvironments] as EnvironmentElementCollection;
}
}
}
public class EnvironmentElementCollection : ConfigurationElementCollection, IEnumerable<EnvironmentElement> {
private static ConfigurationPropertyCollection properties;
public EnvironmentElementCollection() {
properties = new ConfigurationPropertyCollection();
}
protected override ConfigurationPropertyCollection Properties {
get {
return properties;
}
}
public override ConfigurationElementCollectionType CollectionType {
get {
return ConfigurationElementCollectionType.BasicMap;
}
}
public EnvironmentElement this[int index] {
get {
return (EnvironmentElement)base.BaseGet(index);
}
}
public EnvironmentElement this[string name] {
get {
return (EnvironmentElement)base.BaseGet(name);
}
}
protected override string ElementName {
get {
return "add";
}
}
protected override ConfigurationElement CreateNewElement() {
return new EnvironmentElement();
}
protected override object GetElementKey(ConfigurationElement element) {
var elm = element as EnvironmentElement;
if (elm == null) throw new ArgumentNullException();
return elm.Name;
}
//for implementing IEnumerable
public new IEnumerator<EnvironmentElement> GetEnumerator() {
int count = base.Count;
for (int i = 0; i < count; i++) {
yield return (EnvironmentElement)base.BaseGet(i);
}
}
}
public class EnvironmentElement : ConfigurationElement {
private static ConfigurationPropertyCollection properties;
private static ConfigurationProperty propLoginEnabled;
public EnvironmentElement() {
propLoginEnabled = new ConfigurationProperty("loginEnabled", typeof(bool), null, ConfigurationPropertyOptions.None);
properties = new ConfigurationPropertyCollection { propLoginEnabled };
}
[ConfigurationProperty("loginEnabled")]
public bool LoginEnabled {
get {
return (bool)base[propLoginEnabled];
}
}
}
And here is my class to get environments:
public class Environment {
//Works fine to get all environments
public static EnvironmentElementCollection All {
get {
EnvironmentSection dls = ConfigurationManager.GetSection("environments") as EnvironmentSection;
return dls.Environments;
}
}
//Returns null
public static EnvironmentElementCollection Login {
get {
EnvironmentSection dls = ConfigurationManager.GetSection("environments") as EnvironmentSection;
EnvironmentElementCollection envs = dls.Environments.Where<EnvironmentElement>(e => e.LoginEnabled == true) as EnvironmentElementCollection;
return envs;
}
}
}
So I can't tell which part is breaking, if my implementation of IEnumerable is bad, or my Linq query, or something else.

Implementing IEnumerable to my object [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Implementing C# IEnumerable<T> for a LinkedList class
After searching the web for some hours now I still can't understand how IEnumerable/IEnumerator works and how to implement it.
I've constructed a simple LinkedList from scratch but now I want to implement IEnumerable for it so I can foreach it. How do I do that?
class Program
{
LL myList = new LL();
static void Main()
{
var gogo = new Program();
}
public Program()
{
myList.Add("test");
myList.Add("test1");
foreach (var item in myList) //This doesn't work because I havn't implemented Ienumerable
Console.WriteLine(item);
Console.Read();
}
}
class LL
{
private LLNode first;
public void Add(string s)
{
if (this.first == null)
this.first = new LLNode() { Value = s };
else
{
var node = this.first;
while (node.Next != null)
node = node.Next;
node.Next = new LLNode() { Value = s };
}
}
class LLNode
{
public string Value { get; set; }
public LLNode Next { get; set; }
}
It's really not that hard. To implement IEnumerable you just need to implement the GetEnumerator method.
To do that you need to create another class that implements IEnumerator. Implementing IEnumerator is pretty easy. Generally you will pass a reference to your collection when you create the enumerator (in GetEnumerator) and the enumerator will keep track of which item is the current item. Then it will provide MoveNext which just changes the Current to the next item (and returns false if it's at the end of the list) and Reset which just sets the Current back to before the first node.
So in very broad, untested code terms, you need something like:
public class MyLinkedListEnumerator : IEnumerator
{
private LL myList;
private LLNode current;
public object Current
{
get { return current; }
}
public MyLinkedListEnumerator(LL myList)
{
this.myList = myList;
}
public bool MoveNext()
{
if (current == null) {
current = myList.first;
}
else {
current = current.Next;
}
return current != null;
}
public void Reset()
{
current = null;
}
}
What you need to do is:
(1) Make your class implement IEnumerable<T> where T is the type of the enumerated items. (In your case, it looks like it would be LLNode).
(2) Write a public IEnumerator<T> GetEnumerator. Implement it using the "yield" keyword.
(3) Add a IEnumerator IEnumerable.GetEnumerator() method and just return GetEnumerator().
The following code should make this clear. Where I have <int>, you should put <LLNode>, assuming that is the correct type.
using System;
using System.Collections;
using System.Collections.Generic;
namespace Demo
{
internal class Program
{
private static void Main()
{
var test = new MyDemo();
foreach (int item in test)
{
Console.WriteLine(item);
}
}
}
public class MyDemo: IEnumerable<int>
{
public IEnumerator<int> GetEnumerator()
{
// Your implementation of this method will iterate over your nodes
// and use "yield return" to return each one in turn.
for (int i = 10; i <= 20; ++i)
{
yield return i;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
I would have modified your code to do it properly, but the code you posted won't compile.
[EDIT]
Now you've updated your code, I can see that you want to enumerate the values. Here's the completed code:
using System;
using System.Collections;
using System.Collections.Generic;
namespace Demo
{
internal class Program
{
private LL myList = new LL();
private static void Main()
{
var gogo = new Program();
}
public Program()
{
myList.Add("test");
myList.Add("test1");
foreach (var item in myList) // This now works.
Console.WriteLine(item);
Console.Read();
}
}
internal class LL: IEnumerable<string>
{
private LLNode first;
public void Add(string s)
{
if (this.first == null)
this.first = new LLNode
{
Value = s
};
else
{
var node = this.first;
while (node.Next != null)
node = node.Next;
node.Next = new LLNode
{
Value = s
};
}
}
public IEnumerator<string> GetEnumerator()
{
for (var node = first; node != null; node = node.Next)
{
yield return node.Value;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private class LLNode
{
public string Value { get; set; }
public LLNode Next { get; set; }
}
}
}

Categories