runtime: PlatformNotSupportedException in XmlSerializer constructor when type to be serialized contains properties with XmlElementAttribute(Type=...)
I think I found a bug in the constructor of XmlSerializer. It happens when the class to be serialized contains a member with an XmlElement-Attribute which specifies a Type. The constructor will (irritatingly) throw a PlatformNotSupportedException with the text “Compiling JScript/CSharp scripts is not supported”.
[TestFixture]
public class XmlSerializerTests
{
[Test]
public void CanSerialize()
{
var testObject = new TestClass();
var serializer = new XmlSerializer(typeof(TestClass)); // throws PlatformNotSupportedException
}
}
public class TestClass
{
[XmlElement(Type = typeof(SomeSerializationHelper<AnotherTestClass>))]
public AnotherTestClass SomeMember { get; set; }
}
public class AnotherTestClass
{
}
public class SomeSerializationHelper<T>
{
}
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 21 (9 by maintainers)
If you just want the short answer which will most likely solve your problem, read the last paragraph. If you want to understand a little history and details of why this isn’t going to get fixed in the RefEmit serializer, along withanother potential solution, read this wall of text:
@wexman, addressing your comment here, there is not a bug in the RefEmit implementation. The RefEmit implementation matches what the documentation says. It behaves exactly as designed. When you attempt to do something which the documentation says won’t work, and it doesn’t work, that’s not a bug.
On .NET Framework, the XmlSerializer was originally created using code generation and then compiling of that code to create a serializer assembly. When the RefEmit implementation was created, as it is a very large code base and this was a brand new implementation, there was a potential for bugs. So as a safety fallback, if there was an exception when creating the serializer, it would switch to using the legacy mechanism using code generation. You have been fortunate so far on .NET Framework that your incorrect code worked by accident because of your implicit conversion operator which the C# compiler would discover and then emit the relevant IL code to do the conversion.
To add this capability into the RefEmit implementation is going to be risky and likely to lead to even more confusion in the future.
It’s risky as the IL code often needs to have corresponding IL in multiple places. For example, emitting IL which places something on the stack might not have the IL which consumes that item on the stack until quite a bit later. And sometimes our RefEmit implementation has that matching code spread in different methods, and could be duplicated based on conditions which affect other code that’s emitted. It’s very easy to make a change which only sometimes causes an illegal IL sequence to be generated and it’s possible to miss it in testing.
As for confusion, there’s a very good chance that there are other scenarios where use of the implicit cast operator could be used. We then would have implicit cast working in some places but not others. Also how should you deal with derived types which have an implicit case operator? Now you need to look at the known types to see if the derived type is known or not. Then articulating the order or precedence in documentation is very confusing.
I am strongly against adding this capability to the RefEmit implementation. Having said all that, you have existing application code that runs on .NET Framework that you want to get running. There are two possibilities to potentially solve your problem.
First, we do have a secondary runtime implementation of the serializer which uses runtime reflection to serialize your class. That’s all code based and no RefEmit anywhere. Implementing a new feature there is a lot less risky as you can’t generate invalid IL. It’s also a lot easier to write code than write meta code and is a lot easier to debug so the risks are a lot lower. This serializer is still slower at runtime in some scenarios so there is a cost. If it is slower for your scenario, it’s not horrendous. It’s typically 20%-30% slower. We would also need to add code to fallback to that serializer similar to how we used to fallback to the codegen version,
The second option is to pre-generate your serializer. Details can be found here. This will generate code (as .NET Core lacks the capability to dump a dynamic assembly created with RefEmit to a file) and compile it to an assembly which you ship with your app. It’s possible the code gen might fail on validation when generating the code, but the history of how this implementation was created makes me think that’s unlikely.
Didn’t read that the type had to be derived from the declared property type. It didn’t have to in .net framework, so it is at least inconsistent behavior. And even if that would be intended, the exception is extremely misleading, both exception type (platform not supported?) and text (Jscript?!? Compiling scripts?!?) are not what I would expect.
In .net framework, it didn’t actually have to be derived, even if the docs say so. It was sufficient if the type specified in the XmlElement attribute could be assigned to / from the declared property type (by having an implicit operator). So SomeSerializationHelper<AnotherTestClass> in my example above should actually look like this:
What I’m doing is basically porting some code based on this to .net core, which is a rather popular solution for xml serializing derived types without having to know the types at compile time.