在.NET中序列化对象时省略所有xsi和xsd命名空间?

155

代码看起来像这样:

StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using (XmlWriter xmlWriter = XmlWriter.Create(builder, settings))
{
    XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
    s.Serialize(xmlWriter, objectToSerialize);
}

生成的序列化文档包含命名空间,如下所示:

<message xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
    xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" 
    xmlns="urn:something">
 ...
</message>

要去除xsi和xsd命名空间,我可以按照如何将对象序列化为XML而不获取xmlns =“…”?中的答案进行操作。

我希望我的message标签为<message>(不带任何命名空间属性)。 我该怎么做?


2
我知道你认为这样做可以让你的xml看起来更好,但是提供命名空间和相应的xsd是更好的实践。 - user1228
2
我希望我的 XML 只包含<message>元素,也就是省略xmlns:xsi和xmlns:xsd命名空间。 - NetSide
5
记录一下:总的来说,这是个愚蠢的错误。命名空间存在有其原因,移除它们会导致某些问题,例如反序列化。请注意保留命名空间。 - John Saunders
75
请注意,有时候不做某些事并不是愚蠢或错误的。例如,可能需要生成文档片段并稍后将它们组合在一起。就我个人而言,我需要生成许多类似且非常大的文档。所有这些文档在树的深处都有相同的大部分内容。因此,我必须先生成不变的部分,并在生成文档时将它们作为字节数组插入。为了使输出更易读且更小,我需要省略一些内部部分的命名空间声明,因为它们存在于更高级别中。 - Dmitry Tashkinov
@JohnSaunders 我认为.NET添加命名空间以保证兼容性是没问题的。但问题在于,.NET并不会为每一个创建的元素都一致地添加命名空间。因此,即便我对此没有意见,最终还是需要手动修改。 - symbiont
5个回答

263
...
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);

2
我想补充一点,删除默认命名空间可能会产生意想不到的后果:例如,如果您使用XmlInclude属性来序列化派生类型,则这些元素的命名空间将被添加到每个元素中,无论您是否需要,因为它们对反序列化是必要的。 - Thomas Levesque
4
这并不能完全移除所有的XML命名空间,就像问题所问的那样。它只会移除xsi和xsd命名空间,就像https://dev59.com/cXVC5IYBdhLWcg3wixw0中提到的那样,这也在*这个*问题中引用了。 - Cheeso
1
正如我在自己的答案中提到的那样,微软也不支持这种方式。它并不总是有效,特别是当您的类型可能与其他具有名称空间的类型一起使用时。 - fourpastmidnight
4
感谢Thomas Levesque提供的答案。由于我也没有生活在一座象牙塔中,其中所有东西都符合最佳实践,这个答案完美地解决了我的问题。 - Sigurd Garshol
14
可以简写为s.Serialize(writer, objectToSerialize, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty })); - Xeevis
显示剩余3条评论

29

这是两个答案中的第二个。

如果您想在序列化期间从文档中任意地剥离所有命名空间,可以通过实现自己的XmlWriter来完成此操作。

最简单的方法是从XmlTextWriter派生并覆盖StartElement方法发出命名空间。 StartElement方法由XmlSerializer在发出任何元素(包括根)时调用。 通过为每个元素覆盖命名空间,并将其替换为空字符串,您已从输出中剥离了命名空间。

public class NoNamespaceXmlWriter : XmlTextWriter
{
    //Provide as many contructors as you need
    public NoNamespaceXmlWriter(System.IO.TextWriter output)
        : base(output) { Formatting= System.Xml.Formatting.Indented;}

    public override void WriteStartDocument () { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        base.WriteStartElement("", localName, "");
    }
}

假设这是类型:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
    // private fields backing the properties
    private int _Epoch;
    private string _Label;

    // explicitly define a distinct namespace for this element
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        set {  _Label= value; } 
        get { return _Label; } 
    }

    // this property will be implicitly serialized to XML using the
    // member name for the element name, and inheriting the namespace from
    // the type.
    public int Epoch
    {
        set {  _Epoch= value; } 
        get { return _Epoch; } 
    }
}

以下是在序列化期间如何使用此功能的示例:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
        var builder = new System.Text.StringBuilder();
        using ( XmlWriter writer = new NoNamespaceXmlWriter(new System.IO.StringWriter(builder)))
        {
            s2.Serialize(writer, o2, ns2);
        }            
        Console.WriteLine("{0}",builder.ToString());

XmlTextWriter存在一些问题。 根据参考文档,它在编写时不会检查以下内容:

  • 属性和元素名称中的无效字符。

  • 不适合指定编码的Unicode字符。如果Unicode字符不适合指定的编码,则XmlTextWriter不会将Unicode字符转义为字符实体。

  • 重复的属性。

  • DOCTYPE公共标识符或系统标识符中的字符。

这些与XmlTextWriter有关的问题从.NET Framework的v1.1版本就已经存在,并且为了向后兼容而继续存在。 如果您不担心这些问题,那么请放心使用XmlTextWriter。 但是大多数人希望更加可靠。

要获得这种可靠性,同时在序列化期间抑制命名空间,请定义一个具体实现抽象XmlWriter及其24种方法。

一个例子在这里:

public class XmlWriterWrapper : XmlWriter
{
    protected XmlWriter writer;

    public XmlWriterWrapper(XmlWriter baseWriter)
    {
        this.Writer = baseWriter;
    }

    public override void Close()
    {
        this.writer.Close();
    }

    protected override void Dispose(bool disposing)
    {
        ((IDisposable) this.writer).Dispose();
    }

    public override void Flush()
    {
        this.writer.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return this.writer.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        this.writer.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        this.writer.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        this.writer.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        this.writer.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        this.writer.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        this.writer.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        this.writer.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        this.writer.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        this.writer.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        this.writer.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        this.writer.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        this.writer.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        this.writer.WriteRaw(data);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        this.writer.WriteRaw(buffer, index, count);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        this.writer.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument()
    {
        this.writer.WriteStartDocument();
    }

    public override void WriteStartDocument(bool standalone)
    {
        this.writer.WriteStartDocument(standalone);
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        this.writer.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteString(string text)
    {
        this.writer.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        this.writer.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteValue(bool value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(DateTime value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(decimal value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(double value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(int value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(long value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(object value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(float value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(string value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteWhitespace(string ws)
    {
        this.writer.WriteWhitespace(ws);
    }


    public override XmlWriterSettings Settings
    {
        get
        {
            return this.writer.Settings;
        }
    }

    protected XmlWriter Writer
    {
        get
        {
            return this.writer;
        }
        set
        {
            this.writer = value;
        }
    }

    public override System.Xml.WriteState WriteState
    {
        get
        {
            return this.writer.WriteState;
        }
    }

    public override string XmlLang
    {
        get
        {
            return this.writer.XmlLang;
        }
    }

    public override System.Xml.XmlSpace XmlSpace
    {
        get
        {
            return this.writer.XmlSpace;
        }
    }        
}

然后,提供一个派生类来重写StartElement方法,就像以前一样:

public class NamespaceSupressingXmlWriter : XmlWriterWrapper
{
    //Provide as many contructors as you need
    public NamespaceSupressingXmlWriter(System.IO.TextWriter output)
        : base(XmlWriter.Create(output)) { }

    public NamespaceSupressingXmlWriter(XmlWriter output)
        : base(XmlWriter.Create(output)) { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        base.WriteStartElement("", localName, "");
    }
}

然后像这样使用这个编写器:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
        var builder = new System.Text.StringBuilder();
        var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
        using ( XmlWriter innerWriter = XmlWriter.Create(builder, settings))
            using ( XmlWriter writer = new NamespaceSupressingXmlWriter(innerWriter))
            {
                s2.Serialize(writer, o2, ns2);
            }            
        Console.WriteLine("{0}",builder.ToString());

这要归功于Oleg Tkachenko


3
我发现我还需要重写LookupPrefix(string ns)方法,始终返回空字符串以删除所有模式声明。 - Kevin Brock
这并不是技术上的答案 - 你正在使用XmlTextWriter,而不是XmlWriter。我注意到这一点是因为我想使用XmlWriter,以便我可以使用它的XmlWriterSettings。 - Abacus
@Abacus你看过这段代码吗?它同时使用了“XmlWriter”和“XmlWriterSettings”。 - Cheeso
我的错,我可能漏掉了那个。 - Abacus
1
很好的回答。除了@KevinBrock添加的方法外,我还需要重载WriteStartAttribute(string prefix, string localName, string ns)方法,这样我的代码才能剥离所有命名空间。值得注意的是,我的命名空间前缀从b2p1变成了p2,这促使我检查其他使用前缀的方法。 - Mabdullah

17
阅读了微软的文档和几个在线解决方案后,我已经发现了这个问题的解决方法。它适用于内置的XmlSerializer和通过IXmlSerialiazble进行的自定义XML序列化。
具体来说,我将使用到目前为止在回答这个问题中使用的相同的MyTypeWithNamespaces XML示例。
[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int epoch) : this( )
    {
        this._label = label;
        this._epoch = epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._epoch; }
        set { this._epoch = value; }
    }
    private int _epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

这就是这个类的全部内容。现在,有些人反对在其类中添加一个 XmlSerializerNamespaces 对象;但正如您所看到的,我将其妥善地放置在默认构造函数中,并公开了一个属性以返回命名空间。

当需要序列化该类时,您可以使用以下代码:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

完成此操作后,您应该得到以下输出:

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

我最近在一个深层次的类层次结构中成功地使用了这种方法,这些类被序列化为XML以进行Web服务调用。微软的文档没有很清楚地说明一旦你创建了公开访问的XmlSerializerNamespaces成员,该怎么处理它,因此许多人认为它是无用的。但是通过遵循他们的文档并按照上面所示的方式使用它,您可以自定义XmlSerializer生成类的XML的方式,而不必采用不受支持的行为或通过实现IXmlSerializable来“滚动自己”的序列化。

我希望这个答案能够彻底解决如何摆脱XmlSerializer生成的标准xsixsd命名空间的问题。

更新:我只想确保我回答了OP关于删除所有命名空间的问题。我的上面的代码将适用于此;让我向您展示如何操作。现在,在上面的示例中,您真的无法摆脱所有命名空间(因为有两个命名空间在使用中)。在您的XML文档中的某个位置,您需要像xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo那样的东西。如果示例中的类是大型文档的一部分,则必须在一个命名空间上方声明命名空间,Abracadbra和/或Whoohoo之一。如果没有,那么一个或两个命名空间中的元素必须用某种前缀装饰(您不能有两个默认命名空间,对吧?)。因此,对于这个示例,Abracadabra是默认命名空间。我可以在我的MyTypeWithNamespaces类内部添加一个Whoohoo命名空间前缀,如下所示:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

现在,在我的类定义中,我指定<Label/>元素位于命名空间"urn:Whoohoo"中,因此我不需要进行任何进一步的操作。当我使用上面的序列化代码对该类进行序列化时,输出如下:
<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

因为<Label>与文档中的其余部分位于不同的命名空间中,所以必须以某种方式“装饰”它以使用该元素。请注意,仍然没有xsixsd命名空间。


微软的文档明确表示不支持。您能分享一下在哪里可以找到这个信息吗? - Dave Van den Eynde
Dave,正如你在我的类似问题的答案中发布的那样,XmlSerializer:删除不必要的xsi和xsd命名空间,链接在这里:XmlSerializerNamespaces Class - fourpastmidnight
1
你仍然将命名空间传递给Serialize方法。我认为提供公共成员的想法是您不必这样做?但是,如果不将其传递给Serialize方法,我无法使其正常工作。不幸的是,我无法访问该方法调用。我只能设置要使用的XmlSerializer实例。 - crush
我发现实际上是 XmlWriter 被包含在 XmlMediaTypeFormatter 中,强制将 xsi 和 xsd 命名空间输出到我的内容中。这只影响使用 WebApi 的默认 XmlMediaTypeFormatter 的人们。我复制了它的源代码,并修改以将我的 Namespaces 属性传递给 Serialize 方法,以防止 XmlWriter 自动添加这两个默认值。请参见此答案 - crush
@crush,你链接的答案是误导性的——不是错误,但它的说法并不全都正确。如果你看一下我回答中的第一个代码片段,你会看到一个注释,明确说明了当你公开一个类型为XmlSerializerNamespaces且带有XmlNamespacesDeclarationAttribute修饰的公共成员时,XmlSerializer的工作原理。这是直接从MSDN中获取的,并且基本上使用那些声明的命名空间来代替XmlSerializer提供的默认命名空间。 - fourpastmidnight
@crush 另外,我已经很久没有看过这个了,实际上,可能我不需要将命名空间传递给 Serialize 方法,因为它们已经在类型上声明了。但是我还没有尝试过。而且我们不要忘记,上面的代码只是演示如何让序列化器不发出那些 xsixsd 命名空间的示例代码。 - fourpastmidnight

12
XmlSerializer sr = new XmlSerializer(objectToSerialize.GetType());
TextWriter xmlWriter = new StreamWriter(filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
sr.Serialize(xmlWriter, objectToSerialize, namespaces);

正如我在答案中已经记录的那样,根据MS Docs(以前是MSDN)的说明,不支持使用string.Empty,因为它并不总是适用于所有情况。 - fourpastmidnight

7

这是我对这个问题的两个答案中的第一个。

如果您想对命名空间进行精细控制——例如,如果您想省略其中一些命名空间但不省略其他命名空间,或者如果您想用另一个命名空间替换一个命名空间,您可以使用XmlAttributeOverrides来实现。

假设您有这个类型定义:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
    // private fields backing the properties
    private int _Epoch;
    private string _Label;

    // explicitly define a distinct namespace for this element
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        set {  _Label= value; } 
        get { return _Label; } 
    }

    // this property will be implicitly serialized to XML using the
    // member name for the element name, and inheriting the namespace from
    // the type.
    public int Epoch
    {
        set {  _Epoch= value; } 
        get { return _Epoch; } 
    }
}

并且这是序列化的伪代码:

        var o2= new MyTypeWithNamespaces() { ..initializers...};
        ns.Add( "", "urn:Abracadabra" );
        XmlSerializer s2 = new XmlSerializer(typeof(MyTypeWithNamespaces));
        s2.Serialize(System.Console.Out, o2, ns);

您会得到类似于以下的XML内容:
<MyTypeWithNamespaces xmlns="urn:Abracadabra">
  <Label xmlns="urn:Whoohoo">Cimsswybclaeqjh</Label>
  <Epoch>97</Epoch>
</MyTypeWithNamespaces>

请注意,根元素上有一个默认命名空间,并且“Label”元素上还有一个不同的命名空间。这些命名空间是由上面代码中装饰类型的属性所决定的。
.NET中的Xml序列化框架包括显式覆盖实际代码所装饰的属性的可能性。您可以使用XmlAttributesOverrides类和相关类来实现此操作。假设我有相同的类型,并以以下方式对其进行序列化:
        // instantiate the container for all attribute overrides
        XmlAttributeOverrides xOver = new XmlAttributeOverrides();

        // define a set of XML attributes to apply to the root element
        XmlAttributes xAttrs1 = new XmlAttributes();

        // define an XmlRoot element (as if [XmlRoot] had decorated the type)
        // The namespace in the attribute override is the empty string. 
        XmlRootAttribute xRoot = new XmlRootAttribute() { Namespace = ""};

        // add that XmlRoot element to the container of attributes
        xAttrs1.XmlRoot= xRoot;

        // add that bunch of attributes to the container holding all overrides
        xOver.Add(typeof(MyTypeWithNamespaces), xAttrs1);

        // create another set of XML Attributes
        XmlAttributes xAttrs2 = new XmlAttributes();

        // define an XmlElement attribute, for a type of "String", with no namespace
        var xElt = new XmlElementAttribute(typeof(String)) { Namespace = ""};

        // add that XmlElement attribute to the 2nd bunch of attributes
        xAttrs2.XmlElements.Add(xElt);

        // add that bunch of attributes to the container for the type, and
        // specifically apply that bunch to the "Label" property on the type.
        xOver.Add(typeof(MyTypeWithNamespaces), "Label", xAttrs2);

        // instantiate a serializer with the overrides 
        XmlSerializer s3 = new XmlSerializer(typeof(MyTypeWithNamespaces), xOver);

        // serialize
        s3.Serialize(System.Console.Out, o2, ns2);

结果看起来像这样:
<MyTypeWithNamespaces>
  <Label>Cimsswybclaeqjh</Label>
  <Epoch>97</Epoch>
</MyTypeWithNamespaces>

你已经剥离了命名空间。
一个常见的问题是,在序列化过程中,是否可以从任意类型中剥离所有命名空间,而不需要显示覆盖? 答案是肯定的,下一步将会告诉你如何实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接