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.
Related
This question already has answers here:
What does Collection.Contains() use to check for existing objects?
(6 answers)
Closed 1 year ago.
So this is the code that I have tried, but it adds the same object more than once:
namespace TestComparison
{
public interface IAddable
{
int RandomIntValue { get; set; } // often Times this value will repeat.
}
public class AdditionManager<T> where T : IAddable
{
private List<T> addables;
public AdditionManager()
{
addables = new List<T>();
}
public void Add(T _addable)
{
if (!addables.Contains(_addable))
{
addables.Add(_addable);
}
}
}
public class TestAddable : IAddable
{
public int RandomIntValue { get; set; }
public Data UniqueData = new Data() { UniqueId = 10023 }; // This is what really make each item unique
}
public class Data
{
public int UniqueId { get; set; }
}
}
I've heard about the IEqualityComparer and I have implemented it in non-generic classes, but I'm not quite sure how to implement it here.
Your problem indeed seems to be related to a missing IEqualityComparer.
Imagine the following:
class TestClass
{
public int x;
}
class Program
{
static void Main(string[] args)
{
TestClass nine = new TestClass() { x = 9 };
TestClass twelve = new TestClass() { x = 12 };
TestClass anotherNine = new TestClass() { x = 9 };
Console.WriteLine(nine == twelve);
Console.WriteLine(nine == anotherNine);
}
}
What will this program output? The "surprising" answer is that it outputs False two times. This is because the objects are compared to each other, not the members of the objects. To achieve an actual value comparison which will compare the objects by their content instead of their reference you need to consider quite a few things. If you want to be really complete, you need IComparable, IEquality, GetHashcode etc etc. It's very easy to make a mistake there.
But since C# 9.0 there's a new type which can be used instead of class. The type is record. This new record type has all the stuff I mentioned implemented by default. If you want to go the long route, I suggest you to look into the new record type and what it actually is.
This means all you need to do is change the type of your TestAddable and Data from class to record and you should be fine.
You can use dependency injection to provide you with generic implementation. Doing so you'll need to provide the custom IEqualityComparer<T> implementation that you want at the point of generic object's construction.
public class AdditionManager<T> where T : IAddable
{
private List<T> addables;
private IEqualityComparer<T> comparer;
public AdditionManager()
: this (EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
addables = new List<T>();
comparer = _comparer;
}
public void Add(T _addable)
{
if (!addables.Contains(_addable, comparer))
{
addables.Add(_addable);
}
}
}
However, if you are looking for you list of addables to be unique based on some constraint, I would not use the above implementation for performance reasons. As the List<T>.Contains check will become slower as the list grows larger.
If the order of the list does not matter change your List<T> to a HashSet<T>. HashSet<T>.Contains will be just as quick as a Dictionary<TKey, TValue> lookup. But this call can be avoided altogether with HashSet<T> as the Add call will first check to see if the item is in the set before adding it, and return true or false to indicate it was added or not`
So if the order of addables is of not concern, then I would use the following implementation.
public class AdditionManager<T> where T : IAddable
{
private HashSet<T> addables;
public AdditionManager()
: this(EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
addables = new HashSet<T>(_comparer);
}
public void Add(T _addable)
{
// will not add the item to the HashSet if it is already present
addables.Add(_addable);
}
}
If you need to maintain the order of addables then I suggest maintaining the list of objects in both a HashSet<T> and List<T>. This will provide you with the performance of the above implementation, but maintain the addition order on your items. In this implementation any of the operations you need to perform, do them against the List<T> and only use the HashSet<T> to make sure the item isn't already present when adding to List<T> If you are going to have some type of Remove operation, make sure to remove the item from both the HashSet<T> and List<T>
public class AdditionManager<T> where T : IAddable
{
private HashSet<T> set;
private List<T> list;
public AdditionManager()
: this(EqualityComparer<T>.Default)
{ }
public AdditionManager(IEqualityComparer<T> _comparer)
{
set = new HashSet<T>(_comparer);
list = new List<T>();
}
public void Add(T _addable)
{
if (set.Add(_addable))
list.Add(_addable);
}
}
To create this object using TestAddable you'll need an IEqualityComparer<TestAddable> like the following. As others have suggested, the field(s) you are doing your comparison on should be made immutable, as a mutable key is going to cause bugs.
public class TestAddableComparer : IEqualityComparer<TestAddable>
{
public bool Equals(TestAddable x, TestAddable y)
{
return x.UniqueData.Equals(y.UniqueData);
}
public int GetHashCode(TestAddable obj)
{
// since you are only comparing use `UniqueData` use that here for the hash code
return obj.UniqueData.GetHashCode();
}
}
Then to create the manager object do:
var additionManager = new AdditionManager<TestAddable>(new TestAddableComparer());
You can use a dictionary instead of a list. If you need a list in other parts of your code, it is easy to add a property that exposes the Values only.
public class AdditionManager<T> where T : IAddable
{
private Dictionary<int,T> addables;
public AdditionManager()
{
addables = new Dictionary<int,T>();
}
public void Add(T _addable)
{
if (!addables.ContainsKey(_addable.Data.RandomIntValue))
{
addables.Add(_addable.Data.RandomIntValue, _addable);
}
}
public Dictionary<int,T>.ValueCollection Values => _addables.Values;
}
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?
I need help as to how do I go about the structure of classes. How do I use Indexers? I want to have something like
Company.Employees[empId].Employee["Designation"].Salary
To be more specific something like
Grid.Rows[rowIndex].Columns["CurrentColumnName"].Width
Add a method like
public string this[string s]
{
get{
if(s == ...)
return this.property;
}
}
Yet, this seems to be more a Situation for Collections, but
see here for a complete example.
Actually indexers are used to get element by index, and your EmpId is not a good candidate for indexing as these may be compost or non sequential.
If you still want to use it here is the code. It will mimic as Indexer but its modified version.
class Employee
{
public int EmpId { get; set; }
public float Salary { get; set; }
public string Designation { get; set; }
}
class Employees
{
List<Employee> EmpList = new List<Employee>();
public Employee this[int empId]
{
get
{
return EmpList.Find(x => x.EmpId == empId);
}
}
}
I would rather have a method because I can make it generic.
public T GetPropertyValue<T>(string property)
{
var propertyInfo = GetType().GetProperty(property);
return (T)propertyInfo.GetValue(this, null);
}
var emp = employee.GetPropertyValue<Employee>("Designation");
var salary = emp.Salary;
That said... Be careful for having so many dot notations. When you get that NullReferenceException on your line in a log file, it is very difficult to find out what exactly was null. So rather break things up a bit and have more lines then you have less trouble of resolving bugs.
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?
I'm trying to deserialize json to an object model where the collections are represented as IList<T> types.
The actual deserializing is here:
JavaScriptSerializer serializer = new JavaScriptSerializer();
return serializer.Deserialize<IList<Contact>>(
(new StreamReader(General.GetEmbeddedFile("Contacts.json")).ReadToEnd()));
Before i post the exception i'm getting you should know what the implicit conversions are. This is the Contact type:
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
public LazyList<ContactDetail> Details { get; set; }
//public List<ContactDetail> Details { get; set; }
}
And this is the ContactDetail type:
public class ContactDetail
{
public int ID { get; set; }
public int OrderIndex { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
The important thing to know with the LazyList<T> is that it implements IList<T>:
public class LazyList<T> : IList<T>
{
private IQueryable<T> _query = null;
private IList<T> _inner = null;
private int? _iqueryableCountCache = null;
public LazyList()
{
this._inner = new List<T>();
}
public LazyList(IList<T> inner)
{
this._inner = inner;
}
public LazyList(IQueryable<T> query)
{
if (query == null)
throw new ArgumentNullException();
this._query = query;
}
Now this LazyList<T> class definition was fine until i tried deserializing Json into it. The System.Web.Script.Serialization.JavaScriptSerializer seems to want to serialize lists to List<T> which makes sense coz of it's age but i need them in the type IList<T> so they will cast into my LazyList<T> (at least that's where i think i am going wrong).
I get this exception:
System.ArgumentException: Object of type 'System.Collections.Generic.List`1[ContactDetail]' cannot be converted to type 'LazyList`1[ContactDetail]'..
When i try using List<ContactDetail> in my Contact type (as you can see commented above) it seems to work. But i dont want to use List<T>'s. I even tried having my LazyList<T> inheriting from List<T> which seemed to execute but passing the List<T>'s internal T[] to my implementation was a nightmare and i simply don't want the bloat of List<T> anywhere in my model.
I also tried some other json libraries to no avail (it's possible i may not be using these to their full potential. I more or less replaced the references and attempted to repeat the code quoted at the top of this question. Maybe passing settings params will help??).
I dont know what to try now. Do i go with another deserializer? Do i tweak the deserializing itself? Do i need to change my types to please the deserializer? Do i need to worry more about implicit casting or just implement another interface?
It is not possible to deserialize directly to an interface, as interfaces are simply a contract. The JavaScriptSerializer has to deserialize to some concrete type that implements IList<T>, and the most logical choice is List<T>. You will have to convert the List to a LazyList, which given the code you posted, should be easy enough:
var list = serializer.Deserialize<IList<Contact>>(...);
var lazyList = new LazyList(list);
Unfortunately you will probably need to fix your class, as there is no way for a deserializer to know that it should be of type IList, since List is an implementation of IList.
Since the deserializers at http://json.org have source available you could just modify one to do what you want.
I ended up using the Json.NET lib which has good linq support for custom mapping. This is what my deserializing ended up looking like:
JArray json = JArray.Parse(
(new StreamReader(General.GetEmbeddedFile("Contacts.json")).ReadToEnd()));
IList<Contact> tempContacts = (from c in json
select new Contact
{
ID = (int)c["ID"],
Name = (string)c["Name"],
Details = new LazyList<ContactDetail>(
(
from cd in c["Details"]
select new ContactDetail
{
ID = (int)cd["ID"],
OrderIndex = (int)cd["OrderIndex"],
Name = (string)cd["Name"],
Value = (string)cd["Value"]
}
).AsQueryable()),
Updated = (DateTime)c["Updated"]
}).ToList<Contact>();
return tempContacts;