protobuf.net c# obervablecollection - c#

I am trying to get my observablelist which is derived from observablecollection to be serialized. For some reason the collection has always 0 Elements when I deserialize it. When I change the collectiontype from observablelist to observablecollection in class "Test" it works fine. So, how can I achive that my class is also handled like a normal list. Hope anyone can help me. Here is my Code:
[Serializable]
[ProtoContract]
public class ObservableList<T> : ObservableCollection<T>
{
...
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Test
{
public string Name { get; set; }
public ObservableList<Hobby> Hobbies { get; set; } = new ObservableList<Hobby>();
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Hobby
{
public string Name { get; set; }
}
KR Manuel

If I use this Class it's not working but, if i rename the Add() function to AddRange() for example it is. Can anyone tell me the reason for this strange behaviour?
public class ObservableList<T> : ObservableCollection<T>
{
public void Add(IEnumerable<T> list)
{
foreach (var item in list)
Add(item);
}
}
protobuf-net needs to support arbitrary lists / collections - not just those that derive from List<T> or support specific interfaces. It does this by attempting to resolve a suitable GetEnumerator() method (for serialization) and Add() method (for deserialization). There are more than a few checks and priorities built into this, but it sounds like in this specific case it is getting confused as to your intent.

It seems to work fine here... using your code, and:
static void Main()
{
var test = new Test
{
Hobbies =
{
new Hobby { Name = "abc" }
}
};
var clone = Serializer.DeepClone(test);
Console.WriteLine("Same object? {0}",
ReferenceEquals(test, clone));
Console.WriteLine("Sub items: {0}",
clone.Hobbies.Count);
foreach (var x in clone.Hobbies)
{
Console.WriteLine(x.Name);
}
}
gives the output:
Same object? False
Sub items: 1
abc
So: the deserialized object has the correct sub-item.

I found out whats going on. Here again my Observablelist Class:
[Serializable]
[ProtoContract]
public class ObservableList<T> : ObservableCollection<T>
{
public void Add(IEnumerable<T> list)
{
foreach (var item in list)
Add(item);
}
}
If I use this Class it's not working but, if i rename the Add() function to AddRange() for example it is. Can anyone tell me the reason for this strange behaviour?

Related

Reference known property in (List<someClass>)someObject

have tried some searches. Probably my lack of knowledge that I'm not using the right search terms or perhaps just not understanding the answers.
I have a method that is being passed an object, which I want to output a particular value to a text file.
I already know the object will be a List< someClass > of a few different possible classes (customers/employees/items etc). But all of the classes contain the same string property (e.g.) string idNumber.
So something like this:
public static void OutputFile(object myInput)
{
foreach (someGenericObject in (List<anyType>)myInput)
{
string textToOutput = someGenericObject.idNUmber;
//output the text to somewhere else here
}
}
I feel like as long as I know that it will always contain a this "idNumber" property regardless of the type, that I should be able to reference it somehow. But I just can't seem to get my head around it?
The error I typically get is something like:
Cannot cast List< Employee > to List< object > etc.
Thanks in advance
As I suggested in the comments, if you have the ability to modify these classes, you can have them all inherit from an interface IHasIdNumber with an idNumber property.
Your method would then become:
public static void OutputFile(IEnumerable<IHasIdNumber> myInput)
{
foreach (var item in myInput)
{
string textToOutput = item.idNUmber;
//output the text to somewhere else here
}
}
There are a few ways you can solve this.
Recommended way: Implement common interface:
public interface INumberable { // I'm sure you can come up with a better name...
string IDNumber { get; set; }
}
And then all the possible classes that can be passed into the method will implement INumberable. Your method can then look like this:
public static void OutputFile(List<INumerable> myInput)
{
foreach (var someGenericObject in myInput)
{
string textToOutput = someGenericObject.idNUmber;
//output the text to somewhere else here
}
}
Not-so-recommended way: Reflection:
Type t = someGenericObject.GetType();
var p = t.GetProperty("idNumber");
string theStringYouWant = (string)p.GetValue(someGenericObject);
Note that this is not very safe.
You can use [dynamic].
foreach (var someGenericObject in (dynamic)myInput)
{
//...
}
If all your classes have the same property you want to access in foreach loop you can do in via interface.
public interface ISmth {
int MyProperty { get; set; }
}
public class Student : ISmth {
public int MyProperty { get; set; }
}
public class Employee : ISmth {
public int MyProperty { get; set; }
}
public static void DoSmth(object myObj) {
foreach(ISmth item in (List<object>)myObj) {
Console.Write(item.MyProperty);
}
}
List<Student> stdList = new List<Student>();
DoSmth(stdList.Cast<object>().ToList());

Protobuf-net KeyedCollection serialization

I need to serialize/deserialize a KeyedCollection with protobuf-net, can I just serialize a list?
If so, what is the most efficient way to convert-back the List to the KeyedCollection?
Here follows a sample code that shows the case:
public class FamilySurrogate
{
public List<Person> PersonList { get; set; }
public FamilySurrogate(List<Person> personList)
{
PersonList = personList;
}
public static implicit operator Family(FamilySurrogate surrogate)
{
if (surrogate == null) return null;
var people = new PersonKeyedCollection();
foreach (var person in surrogate.PersonList) // Is there a most efficient way?
people.Add(person);
return new Family(people);
}
public static implicit operator FamilySurrogate(Family source)
{
return source == null ? null : new FamilySurrogate(source.People.ToList());
}
}
public class Person
{
public Person(string name, string surname)
{
Name = name;
Surname = surname;
}
public string Name { get; set; }
public string Surname { get; set; }
public string Fullname { get { return $"{Name} {Surname}"; } }
}
public class PersonKeyedCollection : System.Collections.ObjectModel.KeyedCollection<string, Person>
{
protected override string GetKeyForItem(Person item) { return item.Fullname; }
}
public class Family
{
public Family(PersonKeyedCollection people)
{
People = people;
}
public PersonKeyedCollection People { get; set; }
}
Solution?
.NET Platform Extensions 6 has an implementation of the KeyedCollection, KeyedByTypeCollection Class. This has a constructor which accepts an IEnumerable. The downside to this implementation is that the keys are the items, and it doesn't appear to allow you to change that. If you're already inheriting KeyedCollection, you may as well follow the implementation here and go by Microsoft's lead; they just iterate and call Add().
See also
Uses of KeyedByTypeCollection in .Net?
Can't seem to resolve KeyedByTypeCollection?
What are .NET Platform Extensions on learn.microsoft.com?
Linq with custom base collection
Collection Initializers
Previous thoughts
I'm also trying to tackle this issue from a Linq query perspective, possibly related posts:
Cannot implicitly convert type System.Collections.Generic.List back to Object after linq query
dotnet/runtime: Why is KeyedCollection abstract?
The core issue seems to be that KeyedCollectedion does not contain a constructor that takes any form of ICollection to initialize its data with. The base class of KeyedCollection, Collection, does however. The only option seems to be writing your own constructor for your KeyedCollection class that iterates over a collection and adds each element to the current instance.
using System.Collections.Generic;
using System.Collections.ObjectModel;
public class VariableList<T> : KeyedCollection<string, T>
{
// KeyedCollection does not seem to support explicitly casting from an IEnumerable,
// so we're creating a constructor who's sole purpose is to build a new KeyedCollection.
public VariableList(IEnumerable<T> items)
{
foreach (T item in items)
Add(item);
}
// insert other code here
}
This seems really inefficient though, so I hope someone corrects me...
Edit: John Franco wrote a blogpost wherein they hack together a solution for genericly casting a List with covariants (in 2009!) This doesn't look like a very good way to do things.
Looking at System.Linq.Enumerable's implementation of ToList, Linq also iterates and Adds to the new collection.

Generic Covariance issue

I am trying to work out some generic interfaces that includes a Dictionary and the items that it contains, both of which currently look like the code below.
As you can see I punted on the Dictionary, making the value be object. I would ideally like an interface with a covariant KEy of TParentMOdel and a covariant value of TModel, just like the item is, but I haven't been able to work that out so far (not sure its possible either).
What I do have seems to work until I try to add the last item in the last usage example below. The GenderVm is essentially an ISatteliteVm
It seems like the problem is with Gender being an Enum, which doesn't fully make sense to me. TParentModel in this case is Person, which is a subclass of Party. Covariance seems to be working here as I can add other items where TParentModel is a Person.
Which is why I say it seems like the problem is the value Gender. It's an Enum, and although an Enum is an object I think the type constraint system doesn't support Enums.
Is there an easy fix, such as a cast? A Does anyone see a better way to design SatelliteMap?
Cheers,
Berryl
Item
public interface ISatelliteVm<out TParentModel, out TModel> : ISatelliteVm
{
TParentModel ParentModel { get; }
TModel Model { get; }
}
Dictionary
public class SatelliteVmMap<TParentModel> : Dictionary<Type, ISatelliteVm<TParentModel, object>>, IEditableObject, IIsDirty
{
public void Add(ISatelliteVm<TParentModel, object> item) {
if (item == null)
throw new ArgumentNullException("item");
Add(item.GetType(), item);
}
}
Usage (abstract class that contains the SatelliteMap)
public interface IHubViewModel<out TModel> where TModel : Entity
{
public void AddSatelliteVm(ISatelliteVm<TModel, object> vm) {
if (_satelliteVmMap == null) {
_satelliteVmMap = new SatelliteVmMap<TModel>();
}
if (_satelliteVmMap.ContainsKey(vm)) return;
_satelliteVmMap.Add(vm);
}
}
Usage (subclass that contains several entries of ISatelliteVm)
public abstract class PartyDetailVm : HubViewModel<Party>
{
...
public LifespanVm LifespanVm { get { return GetSatelliteVm<LifespanVm>(); } }
public AvatarVm AvatarVm { get { return GetSatelliteVm<AvatarVm>(); } }
public TelecomNumberPcmShellVm TelecomNumberPcmShellVm { get { return GetSatelliteVm<TelecomNumberPcmShellVm>(); } }
...
}
Usage (subclass that contains several entries of ISatelliteVm)
public class PersonDetailVm : PartyDetailVm
{
...
public PersonNameVm PersonNameVm { get { return GetSatelliteVm<PersonNameVm>(); } }
public HonorificVm HonorificVm { get { return GetSatelliteVm<HonorificVm>(); } }
// THIS is the problem child I cannot add to the map
** public GenderVm GenderVm { get { return GetSatelliteVm<GenderVm>(); } } **
}
ERROR
Error 82 Argument 1: cannot convert from 'Parties.Presentation.ViewModels.PimDetailVms.PersonDetailVms.GenderVm' to
'Core.Presentation.Wpf.ViewModels.MasterDetailVms.DetailVms.SatelliteVms.ISatelliteVm'
Edit for Billy
Billy, SatelliteVm is just a base class that implements ISatelliteVm. Person is a subclass of Party and Gender is an enum.
public class GenderVm : SatelliteViewModel<Person, Gender>
{
}
Changes to GenderVm that seem to solve the problem (not sure why!)
public class GenderVm : SatelliteViewModel<Person, Gender>, ISatelliteVm<Party, object>
{
Party ISatelliteVm<Party, object>.ParentModel { get { return base.ParentModel; } }
object ISatelliteVm<Party, object>.Model { get { return base.Model; } }
}
See the documentation:
Variance in generic interfaces is supported for reference types only. Value types do not support variance. For example, IEnumerable<int> cannot be implicitly converted to IEnumerable<object>, because integers are represented by a value type.
This must address your issue.
Maybe you should change Gender to be a class?

C# xml serializer - serialize derived objects

I want to serialize the following:
[Serializable]
[DefaultPropertyAttribute("Name")]
[XmlInclude(typeof(ItemInfo))]
[XmlInclude(typeof(ItemInfoA))]
[XmlInclude(typeof(ItemInfoB))]
public class ItemInfo
{
public string name;
[XmlArray("Items"), XmlArrayItem(typeof(ItemInfo))]
public ArrayList arr;
public ItemInfo parentItemInfo;
}
[Serializable]
[XmlInclude(typeof(ItemInfo))]
[XmlInclude(typeof(ItemInfoA))]
[XmlInclude(typeof(ItemInfoB))]
public class ItemInfoA : ItemInfo
{
...
}
[Serializable]
[XmlInclude(typeof(ItemInfo))]
[XmlInclude(typeof(ItemInfoA))]
[XmlInclude(typeof(ItemInfoB))]
public class ItemInfoB : ItemInfo
{
...
}
The class itemInfo describes a container which can hold other itemInfo objects in the array list, the parentItemInfo describes which is the parent container of the item info.
Since ItemInfoA and ItemInfoB derive from ItemInfo they can also be a member of the array list and the parentItemInfo, therefore when trying to serialize these objects (which can hold many objects in hierarchy) it fails with exception
IvvalidOperationException.`there was an error generating the xml file `
My question is:
What attributes do I need to add the ItemInfo class so it will be serializable?
Note: the exception is only when the ItemInfo[A]/[B] are initialized with parentItemInfo or the arrayList.
Help please!
Thanks!
With the edited question, it looks like you have a loop. Note that XmlSerializer is a tree serializer, not a graph serializer, so it will fail. The usual fix here is to disable upwards traversal:
[XmlIgnore]
public ItemInfo parentItemInfo;
Note you will have to manually fixup the parents after deserialization, of course.
Re the exception - you need to look at the InnerException - it probably tells you exactly this, for example in your (catch ex):
while(ex != null) {
Debug.WriteLine(ex.Message);
ex = ex.InnerException;
}
I'm guessing it is actually:
"A circular reference was detected while serializing an object of type ItemInfoA."
More generally on the design, honestly that (public fields, ArrayList, settable lists) is bad practice; here's a more typical re-write that behaves identically:
[DefaultPropertyAttribute("Name")]
[XmlInclude(typeof(ItemInfoA))]
[XmlInclude(typeof(ItemInfoB))]
public class ItemInfo
{
[XmlElement("name")]
public string Name { get; set; }
private readonly List<ItemInfo> items = new List<ItemInfo>();
public List<ItemInfo> Items { get { return items; } }
[XmlIgnore]
public ItemInfo ParentItemInfo { get; set; }
}
public class ItemInfoA : ItemInfo
{
}
public class ItemInfoB : ItemInfo
{
}
as requested, here's a general (not question-specific) illustration of recursively setting the parents in a hive (for kicks I'm using depth-first on the heap; for bredth-first just swap Stack<T> for Queue<T>; I try to avoid stack-based recursion in these scenarios):
public static void SetParentsRecursive(Item parent)
{
List<Item> done = new List<Item>();
Stack<Item> pending = new Stack<Item>();
pending.Push(parent);
while(pending.Count > 0)
{
parent = pending.Pop();
foreach(var child in parent.Items)
{
if(!done.Contains(child))
{
child.Parent = parent;
done.Add(child);
pending.Push(child);
}
}
}
}

Enumerate ICollection<T> property of class using Reflection

I'm trying to create a base class for my POCO objects in .NET 4, which will have an Include(string path) method, where path is a "." delimited navigation path of nested ICollection properties of the inheriting class to be enumerated.
For example, given the following classes;
public class Region
{
public string Name { get; set; }
public ICollection<Country> Countries { get; set; }
}
public partial class Region : EntityBase<Region> {}
public class Country
{
public string Name { get; set; }
public ICollection<City> Cities { get; set; }
}
public partial class Country : EntityBase<Country> {}
public class City
{
public string Name { get; set; }
}
public partial class City : EntityBase<City> {}
I want to be able to do something like this;
Region region = DAL.GetRegion(4);
region.Include("Countries.Cities");
So far I have the following;
public class EntityBase<T> where T : class
{
public void Include(string path)
{
// various validation has been omitted for brevity
string[] paths = path.Split('.');
int pathLength = paths.Length;
PropertyInfo propertyInfo = type(T).GetProperty(paths[0]);
object propertyValue = propertyInfo.GetValue(this, null);
if (propertyValue != null)
{
Type interfaceType = propertyInfo.PropertyType;
Type entityType = interfaceType.GetGenericArguments()[0];
// I want to do something like....
var propertyCollection = (ICollection<entityType>)propertyValue;
foreach(object item in propertyCollection)
{
if (pathLength > 1)
{
// call Include method of item for nested path
}
}
}
}
}
Clearly, the "var list = ...>" line doesn't work but you hopefully get the gist, and the foreach will not work unless is the propertyCollection is enumerable.
So it's the last bit, i.e. how do I enumerate an ICollection property of a class when I do not know the type of T until runtime?
Thanks
You don’t need Reflection for this. In order to enumerate it, you only need an IEnumerable. ICollection<T> inherits IEnumerable, so all of your collections will be enumerables. Therefore,
var propertyCollection = (IEnumerable) propertyValue;
foreach (object item in propertyCollection)
// ...
will work.
Generics are normally used when the client can resolve the generic type at compile-time.
Leaving that aside, since all you need to do is enumerate the propertyCollection (viewing each element of the sequence simply as a System.Object) all you need to do is:
var propertyCollection = (IEnumerable)propertyValue;
foreach(object item in propertyCollection)
{
...
}
This is perfectly safe since ICollection<T> extends IEnumerable<T>, which in turn extends IEnumerable. What T actually ends up being at run-time is irrelevant since the loop only requires object.
The real question is: Is System.Object sufficient for your purposes inside the loop?

Categories