I use .NET 3.5 for this.
I have an enum:
[System.SerializableAttribute()]
public enum MyEnum
{
[System.Xml.Serialization.XmlEnumAttribute("035")]
Item1,
Item2
}
I use this enum in a class:
[System.SerializableAttribute()]
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public MyEnum MyEnum { get; set; }
}
Now I want to create a new Eplomyee-instance, set the MyEnum-property by casting it from a string.
Then serialize it and save it in a file.
Employee bob = new Employee() {Id = 1, Name = "Bob"};
bob.MyEnum = (MijnEnum)Enum.Parse(typeof(MyEnum), string.Format(CultureInfo.InvariantCulture, "{0}", "035"));
XmlSerializer serializer = new XmlSerializer(typeof(Employee));
FileInfo fi = new FileInfo(#"C:\myfile.xml");
using (FileStream stream = fi.OpenWrite())
{
XmlWriterSettings xmlWriterSettings = new XmlWriterSettings { Encoding = Encoding.UTF8, OmitXmlDeclaration = true, Indent = true };
using (XmlWriter writer = XmlWriter.Create(stream, xmlWriterSettings))
{
serializer.Serialize(writer, bob); // this is place where it goes wrong
}
}
If I debug this, I see that the value of bob.MyEnum is 35
When I try to serialize, I get an exception:
There was an error generating the XML document.
Instance validation error: '35' is not a valid value for
WindowsFormsApplication.MyEnum.
What is going wrong and how can I solve this?
Let's start:
[System.SerializableAttribute()] // useless, valuetype is implicitly so
public enum MyEnum
{
[System.Xml.Serialization.XmlEnumAttribute("035")]
Item1,
Item2
}
Now the XmlEnumAttribute controls how that value is serialized and deserialized in XML.
IT HAS NOTHING TO WITH THE REST OF YOUR CODE! (sorry for the caps, but no-one else seems to get this).
So when a value of MyEnum.Item1 get serialized, "035" will be emitted.
Now the problem is how you want to assign this.
It is simple. Just assign like you would normally do. None these attributes change semantics of normal code, everything stays the same.
Example:
Employee bob = new Employee() {Id = 1, Name = "Bob", MyEnum = MyEnum.Item1};
There is abolutely no reason why Enum.Parse should even be considered here. The enum type and value is statically known.
If you did want to use Enum.Parse, use it like normal, example:
Enum.Parse(typeof(MyEnum), "Item1")
This is happening because, Enums are internally store as int. Hence your statement bob.MyEnum = (MijnEnum)Enum.Parse(typeof(MyEnum), string.Format(CultureInfo.InvariantCulture, "{0}", "035")); is running without issue. If you debug, value of bob.MyEnum is 35. When you deserialize this, deserializer searches for matching enum with int value 35, which is not there, as you are specifying Item1 and Item2. Hence you get an error.
This will work
bob.MyEnum = (MyEnum)Enum.Parse(typeof(MyEnum), "35");
public enum MyEnum {
Item1 = 35,
Item2
}
Ideally you should be doing this
bob.MyEnum = (MyEnum)Enum.Parse(typeof(MyEnum), "Single");
public enum MyEnum {
[System.Xml.Serialization.XmlEnumAttribute("Single")]
Item1,
Item2
}
Hope this helps you.
I changed the enum-parsing.
I used reflection to parse the string to the enum, as described in this article: http://www.codeguru.com/csharp/csharp/cs_syntax/enumerations/article.php/c5869
And now it works.
Related
Any serialized object before 2.3.0 will not be correctly deserialized in 2.3.0 or later if it contains enum values and is using DataMember with InferTagFromNameDefault instead of ProtoMember.
[DataContract]
public class ClassWithEnum
{
[DataMember]
public MyEnum Enum { get; set; }
}
public enum MyEnum
{
FirstValue,
SecondValue
}
I have this class. Serializing it using this code.
{
RuntimeTypeModel.Default.InferTagFromNameDefault = true;
var v = new ClassWithEnum { Enum = MyEnum.SecondValue };
using (var memoryStream = new MemoryStream())
{
Serializer.Serialize(memoryStream, v);
var bytes = memoryStream.ToArray();
}
}
Before 2.3.0 this will result in a byte[] { 8, 2 }
and after 2.3.0 it will result in byte [] { 8, 1 }
Is there any way I can make later versions serialize the same way as the earlier ones?
This is ... unanticipated. I don't yet understand what happened here, but you're right that there was a change. It looks like historically there was an offset of 1 on the enum values when using InferTagFromNameDefault, and the "enum passthru" check fails to account for this (with "enum passthru" now evaluating to true here, where it would have been false previously).
The following seems to fix this at runtime:
RuntimeTypeModel.Default.Add(typeof(MyEnum), true).EnumPassthru = false;
or via attributes:
[ProtoContract(EnumPassthru = false)]
public enum MyEnum
{...}
I will investigate what happened here.
Is is possible to serialize a custom struct as an xml attribute?
Sample code:
public class Dummy<T>
{
private Dummy() { }
public Dummy(T item1)
{
Item1 = item1;
Item2 = item1;
}
public T Item1 { get; set; }
[XmlAttribute]
public T Item2 { get; set; }
}
public struct Meh
{
public int Prop { get; set; }
}
[Test]
public void XmlMehTest()
{
var meh = new Meh{Prop = 1};
var dummy = new Dummy<Meh>(meh);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
// System.InvalidOperationException : Cannot serialize member 'Meh2' of type Meh.
// XmlAttribute/XmlText cannot be used to encode complex types.
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
[Test]
public void XmlDateTimeTest()
{
var dummy = new Dummy<DateTime>(DateTime.Now);
using (var writer = new StringWriter())
{
var serializer = new XmlSerializer(dummy.GetType());
serializer.Serialize(writer, dummy);
Console.Write(writer.ToString());
}
}
Please ignore that the struct is mutable, wrote it like that for a compact sample.
This is truly a first-world-developer-problem but I'm still curious :)
The documentation says:
You can assign the XmlAttributeAttribute only to public fields or public properties that return a value (or array of values) that can be mapped to one of the XML Schema definition language (XSD) simple types (including all built-in datatypes derived from the XSD anySimpleType type). The possible types include any that can be mapped to the XSD simple types, including Guid, Char, and enumerations.
So to do this, we should be able to create our own type definition for XSD,I guess we can do that.Because this documentation contains full explanation about it.But what we can't do is, we can't include our definition to this list.Initially XML Serializer uses these types to figure out your type's XSD type definition.You can use this attribute with DateTime because it's definition creating with this method and storing in a HashTable:
AddPrimitive(typeof(DateTime), "dateTime", "DateTime",
TypeFlags.XmlEncodingNotRequired |
TypeFlags.HasCustomFormatter |
TypeFlags.CanBeElementValue | **TypeFlags.CanBeAttributeValue**);
AddPrimitive method:
private static void AddPrimitive(Type type, string dataTypeName, string formatterName, TypeFlags flags)
{
XmlSchemaSimpleType dataType = new XmlSchemaSimpleType {
Name = dataTypeName
};
TypeDesc desc = new TypeDesc(type, true, dataType, formatterName, flags);
if (primitiveTypes[type] == null)
{
primitiveTypes.Add(type, desc);
}
primitiveDataTypes.Add(dataType, desc);
primitiveNames.Add(dataTypeName, "http://www.w3.org/2001/XMLSchema", desc);
}
And this definition calling from XmlReflectionImporter like this (which is generating the exception according to StackTrace):
this.GetTypeDesc(name, ns, TypeFlags.CanBeElementValue | TypeFlags.CanBeTextValue | TypeFlags.CanBeAttributeValue);
I guess most important thing is here TypeFlags.CanBeAttributeValue and I think it's specify that this type can be attibute value.So as a result maybe we can serialize custom structs as an XmlAttirube but we can't do it with standart XmlSerializer.Because as I said it's using this list to figure out XSD type definition.And it's an initial list and it's impossible to add new element to that list.
P.S. You might want take a look at here http://msdn.microsoft.com/en-us/library/8w07bk3h(v=vs.80).aspx
I have a class that looks like this (heavily simplified):
public class Foo
{
public enum Value
{
ValueOne,
ValueTwo
}
[XmlAttribute]
public Value Bar { get; set; }
}
I'm receiving an XML file from an external source. Their documentation states that the Foo element will only ever have "ValueOne" or "ValueTwo" in the Bar attribute (they don't supply an XSD).
So, I deserialize it like this:
var serializer = new XmlSerializer(typeof(Foo));
var xml = "<Foo Bar=\"ValueTwo\" />";
var reader = new StringReader(xml);
var foo = (Foo)serializer.Deserialize(reader);
... and that all works.
However, last night, they sent me some XML looking like this instead, and my deserialization failed (as it should):<Foo Bar="" />
Is there a good way to defensively code around this? Ideally I'd like to say something like "default to ValueOne, if something goes wrong here". I don't want to throw away the whole XML file, because a single attribute was mangled.
you can set you enum like this , so this will set "" value of enum to Unknown = 0 somewhat like default value
public enum Foo
{
[XmlEnum(Name = "")]
Unknown =0,
[XmlEnum(Name = "Single")]
One,
[XmlEnum(Name = "Double")]
Two
}
more detail check : XmlEnumAttribute Class
You can manually parse enum value by creating another property with the same XmlAttribute name:
public enum Value
{
// Default for unknown value
Unknown,
ValueOne,
ValueTwo
}
[XmlIgnore]
public Value Bar { get; set; }
[XmlAttribute("Bar")]
public string _Bar
{
get { return this.Bar.ToString(); }
set { this.Bar = Enum.TryParse(value, out Value enumValue) ? enumValue : Value.Unknown; }
}
Usage the same as your before
var serializer = new XmlSerializer(typeof(Foo));
var xml = "<Foo Bar=\"invalid value\" />";
var reader = new StringReader(xml);
var foo = (Foo)serializer.Deserialize(reader);
Shoot down XmlSerializer..
Use LINQ2XML for this simple task
XElement doc=XElement.Load("yourStreamXML");
List<Foo> yourList=doc.Descendants("Foo")
.Select(x=>
new Foo
{
Bar=(Enum.GetNames(typeof(Value)).Contains(x.Attribute("Bar").Value))?((this.Value)x.Attribute("Bar")):(this.Value.ValueOne);
}
).ToList<Foo>();
So,I am basically doing this
if(Enum.GetNames(typeof(Value)).Contains(x.Attribute("Bar").Value))
//if the xml bar value is a valid enum
Bar=(this.Value)x.Attribute("Bar");
else//xml bar value is not a valid enum..so use the default enum i.eValueOne
Bar=this.Value.ValueOne;
If I have the following:
public enum TYPE
{
One = 1,
Two = 2,
Three = 3
}
When I do:
var a = TYPE.One;
I would like it to populate the variable a with a string in the format "01". In other words two digits with a leading zero.
Is it possible to do this by assigning some method to the SomeEnum? I realized I could use TYPE.One.ToString("00")but I would like to have it self-contained in the enum and something very simple to use.
You can add a string to each enum element using the description attribute.
e.g.
public Enum MyEnum
{
[Description("Value A Description")]
ValueA,
[Description[("Value B Description")]
ValueB
}
To retrieve the description value, use an extender class
public static class MyEnumExtender
{
public static string Description(this Enum Value)
{
FieldInfo FI = Value.GetType().GetField(Value.ToString());
IEnumerable<DescriptionAttribute> Attributes = FI.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast<DescriptionAttribute>();
return (Attributes.Any()) ? Attributes.First().Description : Value.ToString();
}
}
....
MyEnum EnumVar = MyEnum.ValueA;
string Description = EnumVar.Description();
can do something like this :
public static class Ext {
public static string ToMyString(this Enumer en ) {
return ((int)en).ToString("00");
}
}
and after use this like:
public enum TYPE { One = 1, Two = 2, Three = 3 }
Type t = TYPE.One;
string s = t.ToMyString();
Yes, conceptually it's the same as like declaring a string , but it's hidden inside extension method.
Other solution is: to simply avoid, at this point, using enums in that way.
Don't use enums for that, but something like a Dictionary or Hash. Enums are there when there is a limited set of possibilities and you do not want or need a value. How it is stored is irrelevant.
I got a XML File, looking familiar to this :
<root>
<carnumber>12</carnumber>
<carcolor>2</carcolor>
<cartype>5</cartype>
</root>
Like you see I got some Elements with values/text in it. The car element for example can take values from 1 to 1000. But the element carcolor can take values from 1 - 5 and the cartype from 1 - 10.
The important thing is that the values of the carcolor and cartype elements mean something. carcolor "2" means red, "1" blue and so on.
So I need to present the user not the values but the real meaning of the values.
I found myself creating some classes that represent the elements with there valid values and things got really complicated and I dont know if this was/is the best way.
A friend of mine suggested me to use XML serialization because my XML file is static. It will never change.
My question is simple. I just wanna know how you would solve this problem. My idea contains classes that represent the XML element, for example cartype within this class I have a Dictonary with a pair. This represent the values within the XML file and the string is the meaning of this value. And I use a lot of Linq to navigate and edit the values.
Thanks again!
This can be as complicated or easy as you want it to be. I would, however, second your friends suggestion to use XML Serialization, something like:
[XmlRoot("Car")]
public class Car
{
public Car()
{
}
[XmlElement("Number")]
public int Number { get; set; }
[XmlElement("Color")]
public int Color { get; set; }
[XmlElement("Type")]
public int Type { get; set; }
}
Serialization:
Car myCar = new Car();
myCar.Number = 1;
myCar.Color = 2;
myCar.Type = 3;
XmlSerializer s = new XmlSerializer(typeof(Car));
TextWriter w = new StreamWriter( #"c:\Car.xml" );
s.Serialize(w, myCar);
w.Close();
Deserialization:
Car myCar;
TextReader r = new StreamReader("Car.xml");
myCar = (Car)s.Deserialize(r);
r.Close();
You could further improve this by exposing a custom enum for the likes of your Type field and internally serializing the number relating to it. Also perhaps exposing the Color enum for the car and internally storing a numerical value.
Give this a try:
[XmlRoot("root")]
public class Car
{
private static XmlSerializer serializer = new XmlSerializer(typeof(Car));
[XmlElement("carnumber")]
public int Number { get; set; }
[XmlElement("carcolor")]
public int Color { get; set; }
[XmlElement("cartype")]
public int Type { get; set; }
[XmlIgnore]
public CarColor CarColor
{
get
{
return (CarColor)Color;
}
set
{
Color = (int)value;
}
}
[XmlIgnore]
public CarType CarType
{
get
{
return (CarType)Type;
}
set
{
Type = (int)value;
}
}
public string CarColorString
{
get
{
return this.CarColor.ToString().Replace('_', ' ');
}
}
public string CarTypeString
{
get
{
return this.CarType.ToString().Replace('_', ' ');
}
}
public string Serialize()
{
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
serializer.Serialize(writer, this);
}
return sb.ToString();
}
public static Car Deserialize(string xml)
{
using (StringReader reader = new StringReader(xml))
{
return (Car)serializer.Deserialize(reader);
}
}
}
public enum CarColor
{
Red = 1,
Blue = 2,
Green = 3,
Light_Brown = 4
// and so on...
}
public enum CarType
{
Sedan = 1,
Coupe = 2,
Hatchback = 3,
SUV = 4,
Pickup_Truck = 5
// and so on...
}
I've added some enums to allow for presentation.
You can set the values of a Car and serialize it to an xml string:
Car car = new Car();
car.Number = 1;
car.CarColor = CarColor.Blue;
car.CarType = CarType.Coupe;
string xml = car.Serialize();
And deserialize an xml string into a car:
string example =
#"<root>
<carnumber>12</carnumber>
<carcolor>2</carcolor>
<cartype>5</cartype>
</root>";
Car car = Car.Deserialize(example);
For presentation, you can use the CarColorString and CarTypeString properties, which, in case your enum values contain more than one word, replace underscores with spaces.
Console.WriteLine(car.CarColorString);
Console.WriteLine(car.CarTypeString);
Why not format your XML file more like:
<root>
<carnumber code="2" name="John's Car"/>
<carcolor code="3" name="Red"/>
<cartype code="2" name="Hatchback"/>
</root>
I would build the UI in WPF, not WinForms. Set up a data context that binds to the XML as an XML data source, write type converters to round trip the data between the internal and human-readable values, bind combo boxes to the various elements, and Bob's your uncle.
I recognize that this answer isn't very useful to you if you're not using WPF.