question: how to instantiate a different type during serializing (or, is IDictionary<k,v> bugged?)
See original GitHub issueFirst: HI! This library is amazing. Thank you for working on it.
I -think- I have a likely a documentation/understanding question, but, there’s a small chance this might be a bug.
If I use a regular Dictionary<somekey,somevalue>
class, serialization works great.
ISSUE:
When I tried to use a third-party dictionary class, I was seeing exceptions with an invalid attempt to cast to IDictionary
Here is a test reproducing the issue with a type called DictionaryExtended that inherits from IDictionary<TKey, TValue>
using System.Collections;
using System.Collections.Generic;
using ExtendedXmlSerializer.Configuration;
using ExtendedXmlSerializer.Tests.ReportedIssues.Support;
using Xunit;
namespace ExtendedXmlSerializer.Tests.ReportedIssues
{
public sealed class DictionarySerializerNewIssue
{
[Fact]
public void Verify()
{
var serializer = new ConfigurationContainer()
.Create().ForTesting();
// my custom IDictionary<k,v> based type
var testInstance = new DictionaryExtended {
[0] = "Test0",
[1] = "Test1",
[2] = "Test2"
};
// this line throws
serializer.Serialize(testInstance);
// TODO .Should().Be(@"some-xml");
}
[Fact]
public void Configuration()
{
var serializer = new ConfigurationContainer()
.Create()
.ForTesting();
}
// Custom simple dictionary class. The key thing seems to be:
// this class inherits from IDictionary<int,string>
// BUT it does NOT inherit from the non-generic IDictionary (the one with no types attached)
//
// this class was created minimally by deriving from IDictionary<int,string>
// right click -> implement missing members by delegating to new object (dictionary).
// it creates a bunch of members to implement to the interface
// then add the initializer for the _dict
public class DictionaryExtended : IDictionary<int, string>
{
IDictionary<int, string> _dict = new Dictionary<int, string>();
public IEnumerator<KeyValuePair<int, string>> GetEnumerator()
{
return _dict.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) _dict).GetEnumerator();
}
public void Add(KeyValuePair<int, string> item)
{
_dict.Add(item);
}
public void Clear()
{
_dict.Clear();
}
public bool Contains(KeyValuePair<int, string> item)
{
return _dict.Contains(item);
}
public void CopyTo(KeyValuePair<int, string>[] array, int arrayIndex)
{
_dict.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<int, string> item)
{
return _dict.Remove(item);
}
public int Count => _dict.Count;
public bool IsReadOnly => _dict.IsReadOnly;
public bool ContainsKey(int key)
{
return _dict.ContainsKey(key);
}
public void Add(int key, string value)
{
_dict.Add(key, value);
}
public bool Remove(int key)
{
return _dict.Remove(key);
}
public bool TryGetValue(int key, out string value)
{
return _dict.TryGetValue(key, out value);
}
public string this[int key]
{
get => _dict[key];
set => _dict[key] = value;
}
public ICollection<int> Keys => _dict.Keys;
public ICollection<string> Values => _dict.Values;
}
}
}
I expect that to work, but, I see the following exception when I run it:
ExtendedXmlSerializer.Tests.ReportedIssues.DictionarySerializerNewIssue.Verify
System.InvalidCastException: Unable to cast object of type 'DictionaryExtended' to type 'System.Collections.IDictionary'.
System.InvalidCastException
Unable to cast object of type 'DictionaryExtended' to type 'System.Collections.IDictionary'.
at ExtendedXmlSerializer.Tests.ReportedIssues.DictionarySerializerNewIssue.Verify() in D:\projects\extendedxmlserializer\test\ExtendedXmlSerializer.Tests.ReportedIssues\DictSerializeIssue.cs:line 25
ExtendedXmlSerializer is rightfully complaining, this type can’t be cast to IDictionary. For instance, if I run the same cast manually, it fails:
(IDictionary)testInstance;
That cast is happening inside ExtendedXmlSerializer at DictionaryEnumerators.cs:17
public IEnumerator Get(IEnumerable parameter) => (IEnumerator ((IDictionary)parameter)?.GetEnumerator() ?? _entries.GetEnumerator();
the key part there that fails is when it tries this cast:
(IDictionary)parameter
full stack trace below (this is from a slightly different project but same result)
System.InvalidCastException
at ExtendedXmlSerializer.ReflectionModel.DictionaryEnumerators.Get(IEnumerable parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ReflectionModel\DictionaryEnumerators.cs:line 17
at ExtendedXmlSerializer.ContentModel.Members.InstanceMemberWalkerBase`1.<Enumerate>d__5.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
at System.Linq.Enumerable.<ConcatIterator>d__59`1.MoveNext()
at System.Linq.Enumerable.<SelectManyIterator>d__17`2.MoveNext()
at System.Linq.Enumerable.<DistinctIterator>d__64`1.MoveNext()
at System.Linq.Buffer`1..ctor(IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at System.Collections.Immutable.ImmutableArray.CreateRange[T](IEnumerable`1 items)
at ExtendedXmlSerializer.ExtensionModel.References.ReferenceAwareSerializers.Serializer.Write(IFormatWriter writer, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\References\ReferenceAwareSerializers.cs:line 60
at ExtendedXmlSerializer.ExtensionModel.Xml.Write.Execute(Writing parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\Write.cs:line 21
at ExtendedXmlSerializer.ExtensionModel.Xml.Serializer.Serialize(XmlWriter writer, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\Serializer.cs:line 19
at ExtendedXmlSerializer.ExtensionModel.Xml.InstanceFormatter.Get(Object parameter) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionModel\Xml\InstanceFormatter.cs:line 33
at ExtendedXmlSerializer.ExtensionMethodsForSerialization.Serialize(IExtendedXmlSerializer this, XmlWriterSettings settings, Object instance) in C:\projects\extendedxmlserializer\src\ExtendedXmlSerializer\ExtensionMethodsForSerialization.cs:line 43
at Diz.Test.SerializerTest.Serializer() in D:\projects\src\diztinguish\Diz.Test\SerializerTest.cs:line 66
If I change DictionaryExtended to ALSO inherit from IDictionary, I get a different error about converting keys and values (would need to track it down further to reproduce).
I know I can’t directly cast from IDictionary to IDictionary<TKey, TValue>. I’m looking for advice on best way to work around this in ExtendedXmlSerializer. It feels like not being able to serialize an IDictionary<k,v> derived type is a bug, but, I’m not sure. Or, maybe I messed up something with my enumerators, or maybe this is all a crazy approach. I just want serialized dictionaries 😃
I suspect the new Interceptor approach in #451 is a good way to solve this, but, I couldn’t get it working. That seems like the right way to say ‘hey, anytime you think you should cast to IDictionary for an object of type DictionaryExtended, stop, and instead call my code so I can just create a DictionaryExtended for you, then pick it up from there’.
Either that or, could this be a weird interaction with the serializer not handling the generic params correctly? i.e. it’s trying to create and cast to IDictionary when it should instead be casting to IDictionary<int,string>
And perhaps it’s an issue with ExtendedXmlSerializer thinking it’s OK to cast DictionaryExtended to IDictionary when in fact it’s not actually derived form that interface (just a confusingly similarly named IDictionary<TKey,TValue>)?
Unrelated question [sorry], is there a way to use .TypeOf<>() to select all generic classes? i.e. say I have that same DictionaryExtended<K,V> class and it has a member named Test i’d like to ignore.
Right now it seems I have to type:
.TypeOf<DictionaryExtended<int,string>>().Member(x => x.Test).Ignore()
i.e. I have to specify every combo of generic params (say, <int,string>,<int,int>,<SomeType,SomeOtherType>
etc).
Did I miss a way to just say something like:
.TypeNameMatches("DictionaryExtended").Member(x => x.Test).Ignore()
Thanks so much for your time, I know it’s tight right now from your other issue. \m/
Issue Analytics
- State:
- Created 3 years ago
- Reactions:1
- Comments:9 (4 by maintainers)
Top GitHub Comments
YESSSS. that does the trick really nicely. thank you so much!
I’ve integrated it into https://github.com/Dotsarecool/DiztinGUIsh/pull/18 and was able to rip out the wrapper stuff. it all seems to be working great!
Thanks again!
Cool! thanks for looking at it, I really appreciate it.
The project I’m using this for is over here, I actually did end up figuring out how to build a self-registering system for generic types though… it’s probably too hacky for real use. However, it’s a neat proof of concept: https://github.com/binary1230/DiztinGUIsh/blob/master/Diz.Core/util/ObservableDictionaryAdaptor.cs#L18
The key being I use reflection to go through the loaded assemblies, find all the combo of generic types that I want to serialize, and then create a list of functions to apply with ConfigurationContainer. So, effectively, at the start of serialization, I call this on each generic type automatically:
(here, OdWrapper<TKey,Tvalue> is my custom type that I want to apply .Member().Ignore to every instance of it)