Converting xsd enums to C#
I have an xsd file from which I am generating a C# class. In order to provide easier maintenance, I'd like to define an enumeration within the xsd file only so that when I have to change the enum, I only have to update it in one place. I know how to create the enum, but when the C# code is generated, I need the enum members to have custom values, so the result would be similar to:
public enum SetupTypeEnum {
开发者_如何学Python None = 0,
NewInstall = 1,
Modify = 2,
Upgrade = 4,
Uninstall = 8
}
Is there any way to write the xsd to accomplish this?
You can add annotations specific for code generation (this only works for svcutil, not for xsd.exe) to your xsd file. The xsd definition for your enum would be something like this:
<xs:simpleType name="SetupTypeEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="None">
<xs:annotation>
<xs:appinfo>
<EnumerationValue xmlns="http://schemas.microsoft.com/2003/10/Serialization/">0</EnumerationValue>
</xs:appinfo>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="NewInstall">
<xs:annotation>
<xs:appinfo>
<EnumerationValue xmlns="http://schemas.microsoft.com/2003/10/Serialization/">1</EnumerationValue>
</xs:appinfo>
</xs:annotation>
</xs:enumeration>
...
</xs:restriction>
</xs:simpleType>
These annotations allow you to explicitly define the numerical value of each enum value. You can find an example on this msdn page if you search for "EnumerationValue".
Update: John Saunders correctly states in his comment that this doesn't work if you use xsd.exe. However, if you use svcutil.exe to create the C# code, then the annotations will work.
Example of using svcutil.exe
:
svcutil /dconly "D:\test.xsd" /o:"D:\test.cs"
If you use svcutil
instead of xsd.exe
, then the generated code will by slightly different. The most important difference is that svcutil
will generate attributes for DataContractSerialization instead of XmlSerialization.
The concept of "enumeration" in XSD has nothing to do with the concept of "enum" in C#.
"enumeration" in XML Schema is a way of restricting the possible lexical values of a type to an enumerated list of values. For instance:
<xs:simpleType name="SummerMonth">
<xs:restriction base="xs:gMonth">
<xs:enumeration value="--07"/>
<xs:enumeration value="--08"/>
<xs:enumeration value="--09"/>
</xs:restriction>
</xs:simpleType>
This type restricts the value space to the set of "Summer" months (July, August and September).
Clearly, this has no correspondence to a "enum" in C# or any other programming language I know.
I believe XSD enumerations are a more purist implementation of enumerations than .NET enumerations in the sense that they don't need and don't support numeric values associated with the enumerated names. Of course the generated code, being .NET code, will associate a numeric value with each named value internally, but this is an implementation detail that is not inherent to the nature of an enumeration as defined by the XSD standard. In this purist implementation of an enumeration, I believe the proper way to associate explicit numeric values with each enumerated name would be to define a separate collection/class that links enumerated values to numeric values. Or define additional enumerated values that represent the combined values that you support (NewInstallOrModify).
Edit:
Here's a sample of what a converter might look like.
// Generated code
public enum SetupTypeEnum
{
None,
NewInstall,
Modify,
Upgrade,
Uninstall
}
// End generated code
public struct IntMappedEnum<T> where T : struct
{
public readonly int originalValue;
public IntMappedEnum(T value)
{
originalValue = (int)Enum.ToObject(typeof(T), value);
}
public IntMappedEnum(int originalValue)
{
this.originalValue = originalValue;
}
public static implicit operator int(IntMappedEnum<T> value)
{
return 1 << value.originalValue;
}
public static implicit operator IntMappedEnum<T>(T value)
{
return new IntMappedEnum<T>(value);
}
public static implicit operator IntMappedEnum<T>(int value)
{
int log;
for (log = 0; value > 1; value >>= 1)
log++;
return new IntMappedEnum<T>(log);
}
public static explicit operator T(IntMappedEnum<T> value)
{
T result;
Enum.TryParse<T>(value.originalValue.ToString(), out result);
return result;
}
}
class Program
{
static void Main(string[] args)
{
SetupTypeEnum s = SetupTypeEnum.Uninstall;
IntMappedEnum<SetupTypeEnum> c = s;
int n = c;
IntMappedEnum<SetupTypeEnum> c1 = n;
SetupTypeEnum s1 = (SetupTypeEnum)c1;
Console.WriteLine("{0} => {1} => {2}", s, n, s1);
}
}
Edit 2:
If your enum starts at 0 (as your example does) these two changes are necessary to my example:
Updated int converter:
public static implicit operator int(IntMappedEnum<T> value)
{
return (value.originalValue == 0)?0:1 << (value.originalValue - 1);
}
The line after int log
should be:
for (log = 0; value > 0; value >>= 1)
精彩评论