概要
.NET Coreアプリは、.NET Framework(4.7.2)が成功する一方で、列挙値を含むオブジェクトのXMLシリアル化に失敗します。これは既知の重大な変更ですか?その場合、どうすれば回避できますか?
コード例
次のコンソールアプリケーションは、.NET Framework 4.7.2プロジェクトで例外をスローしません。
public enum MyEnum
{
One,
}
public class ValueContainer
{
public object Value;
}
class Program
{
static void Main(string[] args)
{
XmlSerializer newSerializer = XmlSerializer.FromTypes(
new[] { typeof(ValueContainer)})[0];
var instance = new ValueContainer();
instance.Value = MyEnum.One;
using (var memoryStream = new MemoryStream())
{
newSerializer.Serialize(memoryStream, instance);
}
}
}
.NET Core 3.0コンソールアプリケーションのまったく同じコードは、Serialize
を呼び出すときに次の例外をスローします。
System.InvalidOperationException
HResult=0x80131509
Message=There was an error generating the XML document.
Source=System.Private.Xml
StackTrace:
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
at System.Xml.Serialization.XmlSerializer.Serialize(Stream stream, Object o, XmlSerializerNamespaces namespaces)
at System.Xml.Serialization.XmlSerializer.Serialize(Stream stream, Object o)
at CoreXml.Program.Main(String[] args) in C:\Users\vchel\source\repos\CoreXml\CoreXml\Program.cs:line 28
Inner Exception 1:
InvalidOperationException: The type CoreXml.MyEnum may not be used in this context.
私のコードで何か悪いことをしていますか?これは.NET Frameworkと.NET Coreの間の重大な変更ですか?
回避策はありますか?
更新
.NET 4.7.2でシリアル化すると、Valueに対して次の(望ましい)出力が得られることを指摘しておく必要があります。
<Value xsi:type="xsd:int">0</Value>
.NET Coreが提案するすべてのソリューションが同じXMLも出力するようにしたいと思います。NETStandardを使用していない既存のファイルや古いバージョンのアプリとの互換性を維持する必要があるためです。
更新2
元の質問にこの情報を含める必要がありましたが、回答を実装しようとしているので、最初は考えていなかったいくつかの要件があることがわかります。
まず、シリアル化されているオブジェクトもロジックで使用されており、ロジックは列挙体である値に格納されているオブジェクトに依存します。したがって、値を永続的に整数に変換すると(セッターでキャストするなど)、アプリケーションのロジックに影響が及ぶため、実行できません。
次に、私の例は.NET Frameworkと.NET Coreの違いを示すために簡略化されていますが、実際のアプリケーションでは多くの列挙を使用しています。したがって、このソリューションでは、複数の列挙値を使用できるようにする必要があります。
まあ、なぜこのように違うのか分からない。しかし、私は以下のような回避策があります:
public enum MyEnum
{
One,
}
public class ValueContainer
{
[XmlIgnore]
private object _value;
public object Value
{
get
{
return _value;
}
set
{
var type = value.GetType();
_value = type.IsEnum ? (int)value : value;
}
}
}
class Program
{
static void Main(string[] args)
{
var newSerializer = XmlSerializer.FromTypes(
new[] { typeof(ValueContainer))[0];
var instance = new ValueContainer();
instance.Value = MyEnum.One;
var memoryStream = new MemoryStream();
newSerializer.Serialize(memoryStream, instance);
var str = Encoding.Default.GetString(memoryStream.ToArray());
}
}
出力
<?xml version="1.0"?>
<ValueContainer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Value xsi:type="xsd:int">0</Value>
</ValueContainer>
EDIT:この回避策は<Value>One</Value>
としてシリアル化された値が以前のものよりも汚れていることに気付かず、うまくいきます。
EDIT 2:@Victor Chelaruがコメントで述べたように、私は両方の回避策を維持することを決定しましたが、どちらにもタイプが失われるという同じ欠点があることを述べる必要がありますシリアル化されたxml出力を伴うenumに関する情報。
[XmlType(typeName: "int",Namespace="http://www.w3.org/2001/XMLSchema")]
public enum MyEnum : int
{
[XmlEnum("0")]
One,
}
public class ValueContainer
{
public object Value;
}
public static void Main()
{
var newSerializer = XmlSerializer.FromTypes(new[]{typeof(ValueContainer), typeof(MyEnum)})[0];
var instance = new ValueContainer();
instance.Value = MyEnum.One;
var memoryStream = new MemoryStream();
newSerializer.Serialize(memoryStream, instance);
var str = Encoding.Default.GetString(memoryStream.ToArray());
str.Dump();
}
編集3:上記のコメントで述べた@Simon Mourierのように、次のようにXmlAttributeOverrides
を使用して列挙型を直接変更せずに回避策を実現できます。
[XmlType(typeName: "int")]
public enum MyEnum : int
{
One,
}
public class ValueContainer
{
public object Value;
}
public static void Main()
{
var ov = new XmlAttributeOverrides();
ov.Add(typeof(MyEnum), nameof(MyEnum.One), new XmlAttributes { XmlEnum = new XmlEnumAttribute("0") });
var newSerializer = new XmlSerializer(typeof(ValueContainer), ov, new[] { typeof(MyEnum) }, null, null);
var instance = new ValueContainer();
instance.Value = MyEnum.One;
var memoryStream = new MemoryStream();
newSerializer.Serialize(memoryStream, instance);
var str = Encoding.Default.GetString(memoryStream.ToArray());
str.Dump();
}