5
\$\begingroup\$

I'm working with an XML structure that requires booleans to be represented as 1 or 0.

So, suppose the XML output has to look like:

<valid>1</valid>

Several nodes can be represented this way, so I made a struct to handle this. The solution I found works, but lacks oomph. OK, no, it doesn't just lack oomph, it sucks.

(Implemented in .NET 4)

public struct MyCrappyBool : IXmlSerializable
{
    private int _IntValue;
    private bool _BoolValue;

    public int IntValue
    {
        get { return _IntValue; }
        set
        {
            if (value < 0 || value > 1)
                throw new ArgumentOutOfRangeException();
            _IntValue = value;
            _BoolValue = value == 1;
        }
    }
    public bool BoolValue
    {
        get { return _BoolValue; }
        set
        {
            _BoolValue = value;
            _IntValue = value ? 1 : 0;
        }
    }

    public MyCrappyBool(int intValue) : this()
    {
        IntValue = intValue;
    }
    public MyCrappyBool(bool boolValue): this()
    {
        BoolValue = boolValue;
    }

    #region IXmlSerializable Members
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader)
    {
        IntValue = int.Parse(reader.ReadString());
    }
    public void WriteXml(XmlWriter writer)
    {
        writer.WriteString(IntValue.ToString());
    }
    #endregion
}

This is incredibly tedious, first and foremost because I need to instantiate every time:

MyCrappyBool isValid = new MyCrappyBool(true);

But even then, some nifty implicit overloading can address that:

    public static implicit operator MyCrappyBool(bool boolValue)
    {
        return new MyCrappyBool(boolValue);
    }
    public static implicit operator MyCrappyBool(int intValue)
    {
        return new MyCrappyBool(intValue);
    }

That's still a lot of code for one... crappy boolean.

Later, it can be (XML) serialized as part of any other class:

[XmlElement("valid")]
public MyCrappyBool IsValid { get; set; }
[XmlElement("active")]
public MyCrappyBool IsActive { get; set; }

Is there a simpler way about this I'm not seeing?


Update: Reverse implicit operator overloading!

    public static implicit operator bool(MyCrappyBool myCrappyBool)
    {
        return myCrappyBool.BoolValue;
    }

    public static implicit operator int(MyCrappyBool myCrappyBool)
    {
        return myCrappyBool.IntValue;
    }

This means all my properties can be private.

\$\endgroup\$
4
  • \$\begingroup\$ Do you need the IntValue property? Could you get away with just having a bool Value property and do the appropiate read and writes in the serialization. Not much different but does away with the Int and Bool unless they are both required for some reason? \$\endgroup\$
    – dreza
    Sep 15, 2011 at 2:23
  • \$\begingroup\$ It's used in the constructor that receives a boolean and in ReadXml. I'd still need its code, I think. I could make it private, though. \$\endgroup\$
    – MPelletier
    Sep 15, 2011 at 2:28
  • 1
    \$\begingroup\$ +1000 Ha, I was just wanting to do the exact review implicit operator overload in some code. Brilliant timing. \$\endgroup\$
    – dreza
    Sep 15, 2011 at 3:25
  • \$\begingroup\$ Linking to your related SO question \$\endgroup\$ Sep 17, 2011 at 15:16

4 Answers 4

6
\$\begingroup\$

I'd implement it like this:

public struct Bit : IEquatable<Bit>, IXmlSerializable
{
    private int value;
    public Bit(bool value) { this.value = value ? 1 : 0; }
    public Bit(int value) : this(value != 0) { }

    public bool Equals(Bit other) { return this.value == other.value; }
    public override bool Equals(object obj) { return obj is Bit && this.Equals((Bit)obj); }
    public override int GetHashCode() { return value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }

    public static implicit operator int(Bit value)  { return value.value; }
    public static explicit operator bool(Bit value) { return value.value != 0; }

    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { throw new NotImplementedException(); }
    void IXmlSerializable.ReadXml(XmlReader reader)  { this.value = reader.ReadElementContentAsInt(); }
    void IXmlSerializable.WriteXml(XmlWriter writer) { writer.WriteValue(this.value); }
}

It would be better to store your value as an integer as that is the "true" value that you're interested in. It's value is normalized using C semantics (i.e., false if 0, true otherwise as opposed to true if 1, false otherwise). That would feel more natural to me but it's up to you.

I'd be careful with the operator overloading. Don't do it just because you can, do it because it makes sense. I'd stop at implementing casting operators from the type explicitly (since that's the only way to get at the value), use the constructors otherwise. And make only one of them an implicit cast, otherwise you will run into ambiguity errors if you use this a lot in your code.

Consider implementing the IEquatable<T> interface and other appropriate overrides. If you intend to use this in your code, this could be invaluable.

Consider implementing the IXmlSerializable interface explicitly. If you intend to use this type as you would any other in your code, you probably wouldn't want to see the serialization methods all the time. Otherwise if you are only using this for serialization, implement it implicitly.

\$\endgroup\$
1
  • \$\begingroup\$ "I'd be careful with the operator overloading. Don't do it just because you can." Curses, you found me out! OK, I guess that's sensible. I will revise my struct. I think you're dead on with storing as int, that's pretty good. \$\endgroup\$
    – MPelletier
    Sep 15, 2011 at 21:18
3
\$\begingroup\$

And now for something completely different. While this does not have the flexibility of implicit conversion to and from bool, I think it's the next best thing:

public enum BoolEnum
{
    [XmlEnum("0")]
    False = 0,
    [XmlEnum("1")]
    True = 1
}

And... that's it! It serializes BoolEnum.True as "1", and can deserialize from it too. Error catching is nonexistent, though. If the value in the file should be other than 1 or 0, an InvalidOperationException inside another is thrown. If there was a way to customize that exception, I'd like to know it.

\$\endgroup\$
3
  • \$\begingroup\$ And yes, mods, I'm breaking a rule, I'm using a question almost as a forum thread. But this really is a different answer, not a followup. \$\endgroup\$
    – MPelletier
    Sep 17, 2011 at 4:47
  • \$\begingroup\$ +1 yes i like, and with method you could decorate the values with other attributes to achieve different information \$\endgroup\$
    – dreza
    Sep 17, 2011 at 19:59
  • \$\begingroup\$ @dreza I wonder what attributes are available at this level, especially in terms of validation. The IDE kinda lists everything and anything. \$\endgroup\$
    – MPelletier
    Sep 17, 2011 at 22:23
1
\$\begingroup\$

Here's a possible answer although I'm not sure it goes far enough for you

public struct MyCrappyBool : IXmlSerializable
{
public bool Value
{
    get; private set;
}

public MyCrappyBool(object v)
{
   bool value = false;

   if(Boolean.TryParse(v, out value))
       Value = value;
   else
       throw new ArgumentException("Parameter not a valid boolean value");
}

#region IXmlSerializable Members
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
    Value = int.Parse(reader.ReadString()) == 1;
}
public void WriteXml(XmlWriter writer)
{
    writer.WriteString(Value ? "1" : "0");
}
#endregion
}

I would constrain the type of the element in the xml by specifying it's allowed values in the XSD definition. Otherwise I'd just have another SetBoolean(int) method that through the exception if the int value was not 0.

I'm sure there are other ways. I'm not sure if the System.Xml.Serialization.XmlElementAttribute attribute would already do this for you but may be worth looking into.

\$\endgroup\$
3
  • \$\begingroup\$ That makes me realize I could also have an overload of ToString. My oh my, does it ever end?!? \$\endgroup\$
    – MPelletier
    Sep 15, 2011 at 3:26
  • \$\begingroup\$ Changed to only one constructor. Might help make it end :) \$\endgroup\$
    – dreza
    Sep 15, 2011 at 3:55
  • \$\begingroup\$ I don't know about that last change. I have to sleep on it. \$\endgroup\$
    – MPelletier
    Sep 15, 2011 at 3:58
0
\$\begingroup\$

Based on dreza's ideas, I now have: (and somehow I see this better as an answer than an edit on the question):

public struct MyCrappyBool : IXmlSerializable
{
    private bool Value;

    public MyCrappyBool(int intValue) : this(intValue == 1)
    { }
    public MyCrappyBool(bool boolValue) : this()
    {
        Value = boolValue;
    }

    public override string ToString()
    {
        //return ((int)this).ToString();
        return Value ? "1" : "0";
    }

    public static implicit operator MyCrappyBool(bool boolValue)
    {
        return new MyCrappyBool(boolValue);
    }
    public static implicit operator MyCrappyBool(int intValue)
    {
        return new MyCrappyBool(intValue);
    }
    public static implicit operator bool(MyCrappyBool myCrappyBool)
    {
        return myCrappyBool.Value;
    }
    public static implicit operator int(MyCrappyBool myCrappyBool)
    {
        return myCrappyBool ? 1 : 0;
    }

    #region IXmlSerializable Members
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader)
    {
        Value = int.Parse(reader.ReadString()) == 1;
    }
    public void WriteXml(XmlWriter writer)
    {
        writer.WriteString(ToString());
    }
    #endregion
}

To be honest, ((int)this).ToString() seems convoluted and evil.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ hmm, I think your right. Not sure of a better answer but I personally would find the Value ? "1" : "0" notation easier to read inside the ToString() method. \$\endgroup\$
    – dreza
    Sep 15, 2011 at 3:57
  • \$\begingroup\$ I just wanted to avoid having the logic (even if minimal) twice. But that can be backwards sometimes. \$\endgroup\$
    – MPelletier
    Sep 15, 2011 at 4:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.