You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 4.8反序列化含init-only setter的泛型类抛出MissingMethodException

问题:含init-only setter的泛型类XML反序列化失败,非泛型类可正常工作

尝试反序列化包含init-only setter的泛型类时,抛出以下异常:

System.InvalidOperationException: 'XML文档(2, 2)中存在错误。'
内部异常:
MissingMethodException: 未找到方法:'Void Example.BrokenClass`1.set_MyString(System.String)'

但反序列化包含init-only setter的非泛型类却能正常执行。运行.NET 4.8最小化项目时,WorkingClass1和WorkingClass2可正常反序列化,BrokenClass会触发上述异常。

原因分析

.NET Framework 4.8的XmlSerializer对泛型类的init-only setter存在兼容性bug:

  • init-only setter在编译时会生成带有特殊标记的set方法,而非普通的setter;
  • 非泛型类场景下,XmlSerializer能识别并调用这个特殊的init方法完成反序列化;
  • 但泛型类场景下,XmlSerializer的类型解析逻辑无法正确定位到该特殊方法,仍尝试寻找普通的set_XXX方法,最终抛出MissingMethodException。

不升级.NET 4.8的解决办法

以下是几种可行的替代方案:

方案1:实现IXmlSerializable接口手动控制序列化逻辑

通过手动实现序列化和反序列化过程,绕开XmlSerializer对init-only setter的识别问题:

public class BrokenClass<T> : IXmlSerializable
{
    public string MyString { get; init; }

    public XmlSchema GetSchema() => null;

    public void ReadXml(XmlReader reader)
    {
        // 读取XML节点值,通过私有构造函数+字段赋值完成初始化
        reader.ReadStartElement();
        var value = reader.ReadContentAsString();
        reader.ReadEndElement();
        
        // 假设类中有私有字段和构造函数
        // 示例:替换为实际的私有字段和构造函数实现
        typeof(BrokenClass<T>).GetField("_myString", BindingFlags.Instance | BindingFlags.NonPublic)
                              ?.SetValue(this, value);
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteElementString(nameof(MyString), MyString);
    }
}

注:实际实现时建议用私有字段配合构造函数完成初始化,避免反射开销。

方案2:替换init-only setter为私有setter

保留属性的公开get访问器,将setter改为私有,让XmlSerializer能通过反射访问私有setter:

public class BrokenClass<T>
{
    public string MyString { get; private set; }
}

这种方式牺牲了init-only的不可变性,但能直接兼容XmlSerializer。

方案3:使用DataContractSerializer替代XmlSerializer

DataContractSerializer对init-only setter的支持更完善,无论泛型还是非泛型类都能正常处理:

private static void TestDataContractSerializer<T>(T myClass)
{
    DataContractSerializer serializer = new DataContractSerializer(typeof(T));

    using (MemoryStream stream = new MemoryStream())
    {
        serializer.WriteObject(stream, myClass);
        stream.Position = 0;
        var output = (T)serializer.ReadObject(stream);
    }
}

需要给类添加[DataContract]特性,属性添加[DataMember]特性:

[DataContract]
public class BrokenClass<T>
{
    [DataMember]
    public string MyString { get; init; }
}

方案4:添加辅助私有属性供XmlSerializer使用

保留init-only属性,同时添加一个私有辅助属性,让XmlSerializer序列化/反序列化这个辅助属性,再同步到init-only属性:

public class BrokenClass<T>
{
    public string MyString { get; init; }

    [XmlElement("MyString")]
    private string MyStringHelper
    {
        get => MyString;
        set => typeof(BrokenClass<T>).GetProperty(nameof(MyString))
                                     .SetValue(this, value);
    }
}

利用XmlElement特性让辅助属性映射到原XML节点,通过反射完成init-only属性的赋值。

完整测试代码

Program.cs

using System.ComponentModel;
using System.IO;
using System.Xml.Serialization;

namespace Example
{
    internal class Program
    {
        static void Main(string[] args)
        {
            WorkingClass1<string> workingClass1 = new WorkingClass1<string>() { MyString = "Hello World" };
            TestXmlSerializer(workingClass1);

            WorkingClass2 workingClass2 = new WorkingClass2() { MyString = "Hello World" };
            TestXmlSerializer(workingClass2);

            BrokenClass<string> brokenClass = new BrokenClass<string>() { MyString = "Hello World" };
            TestXmlSerializer(brokenClass);
        }

        private static void TestXmlSerializer<T>(T myClass)
        {
            XmlSerializer serializer = new(typeof(T));

            StringWriter writer = new();
            serializer.Serialize(writer, myClass);

            StringReader reader = new(writer.ToString());
            var output = (T)serializer.Deserialize(reader);
        }
    }

    public class BrokenClass<T>
    {
        public string MyString { get; init; }
    }

    public class WorkingClass1<T>
    {
        public string MyString { get; set; }
    }

    public class WorkingClass2
    {
        public string MyString { get; init; }
    }
}

namespace System.Runtime.CompilerServices
{
    [EditorBrowsable(EditorBrowsableState.Never)]
    public static class IsExternalInit { }
}

Example.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net48</TargetFramework>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>
</Project>

内容的提问来源于stack exchange,提问作者Ryan W

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:16:19