none
XSD.exe 根据xsd生成类的Bug RRS feed

  • 问题

  • 当xsd定义为如下情况时,xsd.exe会错误的将两种不同类型判断为同一类型:

    <?xml version="1.0" encoding="UTF-8"?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
    	<xs:element name="Children" type="ChildrenType"/>
    	<xs:complexType name="ChildrenType">
    		<xs:annotation>
    			<xs:documentation>Comment describing your root element</xs:documentation>
    		</xs:annotation>
    		<xs:choice>
    			<xs:element name="BoyIDList" type="BoyIDListType"/>
    			<xs:element name="GirlIDList" type="GirlIDListType"/>
    		</xs:choice>
    	</xs:complexType>
    	<xs:complexType name="BoyIDListType">
    		<xs:annotation>
    			<xs:documentation/>
    		</xs:annotation>
    		<xs:sequence>
    			<xs:element name="BoyID" type="xs:int" maxOccurs="unbounded"/>
    		</xs:sequence>
    	</xs:complexType>
    	<xs:complexType name="GirlIDListType">
    		<xs:annotation>
    			<xs:documentation/>
    		</xs:annotation>
    		<xs:sequence>
    			<xs:element name="GirlID" type="xs:int" maxOccurs="unbounded"/>
    		</xs:sequence>
    	</xs:complexType>
    </xs:schema>


    生成的类如下,Item应为object类型,但却被生成为BoyIDListType类型,可见xsd.exe错误的把BoyIDListType和GirlIDListType认为是同一种类型了:

    //------------------------------------------------------------------------------
    // <auto-generated>
    //     This code was generated by a tool.
    //     Runtime Version:4.0.30319.269
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    using System.Xml.Serialization;
    
    // 
    // This source code was auto-generated by xsd, Version=4.0.30319.1.
    // 
    
    
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlRootAttribute("Children", Namespace="", IsNullable=false)]
    public partial class ChildrenType {
        
        private BoyIDListType itemField;
        
        private ItemChoiceType itemElementNameField;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("BoyIDList", typeof(BoyIDListType))]
        [System.Xml.Serialization.XmlElementAttribute("GirlIDList", typeof(GirlIDListType))]
        [System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemElementName")]
        public BoyIDListType Item {
            get {
                return this.itemField;
            }
            set {
                this.itemField = value;
            }
        }
        
        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public ItemChoiceType ItemElementName {
            get {
                return this.itemElementNameField;
            }
            set {
                this.itemElementNameField = value;
            }
        }
    }
    
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    public partial class BoyIDListType {
        
        private int[] boyIDField;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("BoyID")]
        public int[] BoyID {
            get {
                return this.boyIDField;
            }
            set {
                this.boyIDField = value;
            }
        }
    }
    
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    public partial class GirlIDListType {
        
        private int[] girlIDField;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("GirlID")]
        public int[] GirlID {
            get {
                return this.girlIDField;
            }
            set {
                this.girlIDField = value;
            }
        }
    }
    
    /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
    [System.SerializableAttribute()]
    [System.Xml.Serialization.XmlTypeAttribute(IncludeInSchema=false)]
    public enum ItemChoiceType {
        
        /// <remarks/>
        BoyIDList,
        
        /// <remarks/>
        GirlIDList,
    }

    此时如果尝试用序列化生成工具(Sgen.exe)创建一个 XML 序列化程序集,则会报如下错误:

    Error: Unable to generate a temporary class (result=1).
    error CS0030: Cannot convert type 'BoyIDListType' to 'GirlIDListType'
    error CS0029: Cannot implicitly convert type 'GirlIDListType' to 'BoyIDListType'

    我的解决方案是手工修改生成的类,把Item改为object类型,但是如果xsd今后有改动的话,难免会忘记手工修改。

    2013年1月4日 10:45

答案

  • 我是在命令行用 xsd.exe xxx.xsd /c 命令生成的类文件,也就是说只有一个xsd文件就可以生成类了,也许是不应该这么做吗?

    如果你用xsd来自动生成代码的话,因为xsd无法自动识别 xs:choice 标记包含的自定义类型,也就是说它没法智能的给你提供一个基类(暂时不知道为什么这样设计,可能是你的例子足够简单,所以实在不明白为什么没法自动推导出基类。如果用wsdl设计器来编写xsd的话,是要求你必须先自定义一个基类的),那么你需要修改你的.xsd文件,显式的指定一个基类,如下:

      <xs:element name="Children" type="ChildrenType"/>
      <xs:complexType name="ChildrenType">
        <xs:annotation>
          <xs:documentation>Comment describing your root element</xs:documentation>
        </xs:annotation>
        <xs:choice>
          <xs:element name="BoyIDList" type="BoyIDListType" />
          <xs:element name="GirlIDList" type="GirlIDListType" />      
        </xs:choice>
      </xs:complexType>
      
      <xs:complexType name="IDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>    
      </xs:complexType>
      
      <xs:complexType name="BoyIDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>
        <xs:complexContent>
          <xs:extension base ="IDListType">
            <xs:sequence>
              <xs:element name="BoyID" type="xs:int" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:extension>
        </xs:complexContent>
      </xs:complexType>
      
      <xs:complexType name="GirlIDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>
        <xs:complexContent>
          <xs:extension base ="IDListType">
            <xs:sequence>
              <xs:element name="GirlID" type="xs:int" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:extension>
        </xs:complexContent>
      </xs:complexType>

    • 已标记为答案 Dustin Lv 2013年1月5日 9:28
    2013年1月5日 5:36

全部回复

  • 这不是BUG,因为 xs:ComplexType 表示定义用户自定义类型,而不是 xs 命名空间下已经申明的基础类型,比如 xs:int。另外,考虑到跨平台特性,xs 命名空间下是没有 object 类型的定义的。

    xsd.exe 没有把 BoyIDListType 和 GirlIDListType 认为是同一种类型,而是认为是两种类型。

    2013年1月5日 1:23
  • /// <remarks/>
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.1")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlRootAttribute("Children", Namespace="", IsNullable=false)]
    public partial class ChildrenType {
        
        private BoyIDListType itemField;
        
        private ItemChoiceType itemElementNameField;
        
        /// <remarks/>
        [System.Xml.Serialization.XmlElementAttribute("BoyIDList", typeof(BoyIDListType))]
        [System.Xml.Serialization.XmlElementAttribute("GirlIDList", typeof(GirlIDListType))]
        [System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemElementName")]
        public BoyIDListType Item {
            get {
                return this.itemField;
            }
            set {
                this.itemField = value;
            }
        }
        
        /// <remarks/>
        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public ItemChoiceType ItemElementName {
            get {
                return this.itemElementNameField;
            }
            set {
                this.itemElementNameField = value;
            }
        }
    }

    在这个生成的类中,属性Item被定义为BoyIDListType,也就是说只可以接受BoyIDListType类型的子节点,这是我无法理解的。

    我个人的猜想,当实例化XmlSerializer对象时,会检查类型,发现'BoyIDListType' 和 'GirlIDListType'之间不能转换,于是报错。

    可能我的表达不够清楚,我的意思是Item被定义为BoyIDListType,就好像是因为xsd.exe 把 BoyIDListType 和 GirlIDListType 认为是同一种类型了一样。但实际上他们是两种类型。

    2013年1月5日 3:02
  • 请注意你的wsdl,

     <xs:complexType name="BoyIDListType">
    <xs:annotation>
    <xs:documentation/>
    </xs:annotation>
    <xs:sequence>
    <xs:element name="BoyID" type="xs:int" maxOccurs="unbounded"/>
    </xs:sequence>
    </xs:complexType>
    <xs:complexType name="GirlIDListType">
    <xs:annotation>
    <xs:documentation/>
    </xs:annotation>
    <xs:sequence>
    <xs:element name="GirlID" type="xs:int" maxOccurs="unbounded"/>
    </xs:sequence>
    </xs:complexType>

    这里定义了两个自定义类型,虽然它们实际上都是 int 数组。'BoyIDListType' 和 'GirlIDListType' 是肯定没有显示转换的,因为这是两个没有任何关系的类型。你提到实例化的问题,那么只有可能是你错误的在该使用 BoyIDListType 时,使用了 GirlIDListType 来调用服务接口。

    我不知道这是你自己手写的,还是通过一个已有的服务生成的。


    2013年1月5日 3:31
  • “这里定义了两个自定义类型,虽然它们实际上都是 int 数组。'BoyIDListType' 和 'GirlIDListType' 是肯定没有显示转换的,因为这是两个没有任何关系的类型。”

    我和你的意见是一致的。

    //------------------------------------------------------------------------------
    // <auto-generated>
    //     This code was generated by a tool.
    //     Runtime Version:4.0.30319.269
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    using System.Xml.Serialization;
    
    // 
    // This source code was auto-generated by xsd, Version=4.0.30319.1.
    // 

    类代码是自动生成的。

    我只想问一个问题,那就是为什么“属性Item被定义为BoyIDListType”

    Again,类代码是自动生成的,如果不介意的话,可以在命令行执行一下,或者按照原帖的步骤执行一下,其中除了xsd的定义之外没有任何我手工添加或修改的代码。

    2013年1月5日 3:38
  • 我很高兴您注意到了这两个实际上都是int数组。

    我曾做过一个实验,把其中一个在xsd中定义为string数组,另一个还是int数组,通过xsd.exe自动生成类,则结果Item属性就被定义为了object类型,所以我才猜想这是xsd.exe的一个bug。

    2013年1月5日 3:41
  • “这里定义了两个自定义类型,虽然它们实际上都是 int 数组。'BoyIDListType' 和 'GirlIDListType' 是肯定没有显示转换的,因为这是两个没有任何关系的类型。”

    我和你的意见是一致的。

    //------------------------------------------------------------------------------
    // <auto-generated>
    //     This code was generated by a tool.
    //     Runtime Version:4.0.30319.269
    //
    //     Changes to this file may cause incorrect behavior and will be lost if
    //     the code is regenerated.
    // </auto-generated>
    //------------------------------------------------------------------------------
    
    using System.Xml.Serialization;
    
    // 
    // This source code was auto-generated by xsd, Version=4.0.30319.1.
    // 

    类代码是自动生成的。

    我只想问一个问题,那就是为什么“属性Item被定义为BoyIDListType”

    Again,类代码是自动生成的,如果不介意的话,可以在命令行执行一下,或者按照原帖的步骤执行一下,其中除了xsd的定义之外没有任何我手工添加或修改的代码。

    为什么被定义为 BoyIDListType,这是由服务接口申明的。也就是说你得找到服务接口(operation)的定义,它会申明返回值的类型。
    2013年1月5日 3:44
  • 我是在命令行用 xsd.exe xxx.xsd /c 命令生成的类文件,也就是说只有一个xsd文件就可以生成类了,也许是不应该这么做吗?
    2013年1月5日 3:57
  • 我是在命令行用 xsd.exe xxx.xsd /c 命令生成的类文件,也就是说只有一个xsd文件就可以生成类了,也许是不应该这么做吗?

    如果你用xsd来自动生成代码的话,因为xsd无法自动识别 xs:choice 标记包含的自定义类型,也就是说它没法智能的给你提供一个基类(暂时不知道为什么这样设计,可能是你的例子足够简单,所以实在不明白为什么没法自动推导出基类。如果用wsdl设计器来编写xsd的话,是要求你必须先自定义一个基类的),那么你需要修改你的.xsd文件,显式的指定一个基类,如下:

      <xs:element name="Children" type="ChildrenType"/>
      <xs:complexType name="ChildrenType">
        <xs:annotation>
          <xs:documentation>Comment describing your root element</xs:documentation>
        </xs:annotation>
        <xs:choice>
          <xs:element name="BoyIDList" type="BoyIDListType" />
          <xs:element name="GirlIDList" type="GirlIDListType" />      
        </xs:choice>
      </xs:complexType>
      
      <xs:complexType name="IDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>    
      </xs:complexType>
      
      <xs:complexType name="BoyIDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>
        <xs:complexContent>
          <xs:extension base ="IDListType">
            <xs:sequence>
              <xs:element name="BoyID" type="xs:int" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:extension>
        </xs:complexContent>
      </xs:complexType>
      
      <xs:complexType name="GirlIDListType">
        <xs:annotation>
          <xs:documentation/>
        </xs:annotation>
        <xs:complexContent>
          <xs:extension base ="IDListType">
            <xs:sequence>
              <xs:element name="GirlID" type="xs:int" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:extension>
        </xs:complexContent>
      </xs:complexType>

    • 已标记为答案 Dustin Lv 2013年1月5日 9:28
    2013年1月5日 5:36
  • 很感谢你的建议。你提到应该为xs:choice 标记包含的自定义类型显示的写一个基类,这样写是OK的。

    但是如果就原来的例子来的话(没有显示写一个基类),无论是把BoyID还是GirlID的类型变成string,生成类里面是会有一个object的基类的。

    然而当两者类型一样的(int或者string)时,生成类才会“把两种类型混淆”,而不是引用一个object的基类。


    2013年1月5日 6:09
  • 非常感谢您优雅的解决了这个问题,以后设计XSD的时候一定要注意某些情况下要抽象出基类。
    2013年1月5日 9:28