TL;DR - using an implementation of ixmlserializable fails only in builds, not the editor, and the error log message doesn’t make sense.
The game uses an implementation of IXmlSerializable as found in the top answer here c# - XML Serialization and Inherited Types - Stack Overflow
For ease of understanding here is the code as used
using System;
using System.Xml.Serialization;
namespace Utility.Xml
{
public class AbstractXmlSerialiser<AbstractType> : IXmlSerializable
{
// Override the Implicit Conversions Since the XmlSerializer
// Casts to/from the required types implicitly.
public static implicit operator AbstractType(AbstractXmlSerialiser<AbstractType> o)
{
return o.Data;
}
public static implicit operator AbstractXmlSerialiser<AbstractType>(AbstractType o)
{
return o == null ? null : new AbstractXmlSerialiser<AbstractType>(o);
}
private AbstractType _data;
/// <summary>
/// [Concrete] Data to be stored/is stored as XML.
/// </summary>
public AbstractType Data
{
get { return _data; }
set { _data = value; }
}
/// <summary>
/// **DO NOT USE** This is only added to enable XML Serialization.
/// </summary>
/// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
public AbstractXmlSerialiser()
{
// Default Ctor (Required for Xml Serialization - DO NOT USE)
}
/// <summary>
/// Initialises the Serializer to work with the given data.
/// </summary>
/// <param name="data">Concrete Object of the AbstractType Specified.</param>
public AbstractXmlSerialiser(AbstractType data)
{
_data = data;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null; // this is fine as schema is unknown.
}
public void ReadXml(System.Xml.XmlReader reader)
{
// Cast the Data back from the Abstract Type.
string typeAttrib = reader.GetAttribute("type");
// Ensure the Type was Specified
if (typeAttrib == null)
throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because no 'type' attribute was specified in the XML.");
Type type = Type.GetType(typeAttrib);
// Check the Type is Found.
if (type == null)
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the type specified in the XML was not found.");
// Check the Type is a Subclass of the AbstractType.
if (!type.IsSubclassOf(typeof(AbstractType)))
throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
"' because the Type specified in the XML differs ('" + type.Name + "').");
// Read the Data, Deserializing based on the (now known) concrete type.
reader.ReadStartElement();
this.Data = (AbstractType)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
// Write the Type Name to the XML Element as an Attrib and Serialize
Type type = _data.GetType();
// BugFix: Assembly must be FQN since Types can/are external to current.
writer.WriteAttributeString("type", type.AssemblyQualifiedName);
new XmlSerializer(type).Serialize(writer, _data);
}
#endregion
}
}
The code to use it is basically the same as a default XmlSerialiser with the addition of a type declaration in the Xml tagging whatsits
public class Ability
{
[XmlIgnore]
public Character thisCharacter;
public string description;
public string identifier;
[XmlArray("Effects")]
[XmlArrayItem("Effect", Type = typeof(AbstractXmlSerialiser<Effect>))]
public List<Effect> effects;
...
And the container/serializer class
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using UnityEngine;
[XmlRoot("EnemyCollection")]
public class EnemyContainer
{
[XmlArray("Enemies"),XmlArrayItem("Enemy")]
public List<Enemy> Enemies = new List<Enemy>();
private static XmlSerializer Serialiser
{
get
{
if (serialiser == null)
{
Debug.Log(typeof(EnemyContainer) + " is type.");
serialiser = new XmlSerializer(typeof(EnemyContainer)); //line getting error
Debug.Log("created.");
}
return serialiser;
}
}
static XmlSerializer serialiser;
public void Save(string path)
{
using (StreamWriter stream = new StreamWriter(path, false, Encoding.GetEncoding("UTF-8")))
{
Serialiser.Serialize(stream, this);
}
}
public static EnemyContainer Load(string path)
{
using (StreamReader stream = new StreamReader(path, Encoding.GetEncoding("UTF-8")))
{
Debug.Log("Stream created.");
return Serialiser.Deserialize(stream) as EnemyContainer;
}
}
}
This all works perfectly in a program made entirely in visual studio and also works perfectly in the unity editor’s play mode. However when used in a unity build it fails and the log file contains the following
SystemException: Error running C:\Users\<User>\Desktop\mono\mini\mono.exe: The system cannot find the file specified.
at Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] fileNames) [0x00160] in <ae22a4e8f83c41d69684ae7f557133d9>:0
at Microsoft.CSharp.CSharpCodeGenerator.FromSourceBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x00094] in <ae22a4e8f83c41d69684ae7f557133d9>:0
at Microsoft.CSharp.CSharpCodeGenerator.System.CodeDom.Compiler.ICodeCompiler.CompileAssemblyFromSourceBatch (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x0000f] in <ae22a4e8f83c41d69684ae7f557133d9>:0
at System.CodeDom.Compiler.CodeDomProvider.CompileAssemblyFromSource (System.CodeDom.Compiler.CompilerParameters options, System.String[] sources) [0x00006] in <ae22a4e8f83c41d69684ae7f557133d9>:0
at System.Xml.Serialization.Compiler.Compile (System.Reflection.Assembly parent, System.String ns, System.Xml.Serialization.XmlSerializerCompilerParameters xmlParameters, System.Security.Policy.Evidence evidence) [0x00144] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at System.Xml.Serialization.TempAssembly.GenerateAssembly (System.Xml.Serialization.XmlMapping[] xmlMappings, System.Type[] types, System.String defaultNamespace, System.Security.Policy.Evidence evidence, System.Xml.Serialization.XmlSerializerCompilerParameters parameters, System.Reflection.Assembly assembly, System.Collections.Hashtable assemblies) [0x004c2] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at System.Xml.Serialization.TempAssembly..ctor (System.Xml.Serialization.XmlMapping[] xmlMappings, System.Type[] types, System.String defaultNamespace, System.String location, System.Security.Policy.Evidence evidence) [0x0006a] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at System.Xml.Serialization.XmlSerializer.GenerateTempAssembly (System.Xml.Serialization.XmlMapping xmlMapping, System.Type type, System.String defaultNamespace) [0x0000e] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at System.Xml.Serialization.XmlSerializer..ctor (System.Type type, System.String defaultNamespace) [0x000a9] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at System.Xml.Serialization.XmlSerializer..ctor (System.Type type) [0x00000] in <1d98d70bb7d8453b80c25aa561fdecd1>:0
at EnemyContainer.get_Serialiser () [0x00028] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\Enemies\EnemyContainer.cs:35
at EnemyContainer.Load (System.String path) [0x0001e] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\Enemies\EnemyContainer.cs:56
at NewTestAdventure.Start () [0x00093] in C:\Users\<User>\Desktop\unity\My Adventure Party\Assets\Scripts\NewAdventures\NewTestAdventure.cs:88
Testing around I’ve discovered that the error only occurs when this implementation of IXmlSerializable is called for, when only using XmlSerializer as default it “works” but obviously cannot work out subclasses. I would definately like to just be able to use that and be done with it, but it would be a maintenance headache to have to statically declare all subclasses as you normally do with XmlSerialiser.
This error also is present in different projects, i.e. it is not a project-specific problem. Stepping through and into the code I also know that the error is created at some point when creating the serializer but before reaching the abstractxmlserializer constructor.
If anyone knows why it only has a problem in builds and not the editor/visual studio or any alternative that does work all help is apreciated.