views:

399

answers:

3

I'm building an XML Deserializer for a project and I run across this type of code situation fairly often:

var myVariable = ParseNDecimal(xml.Element("myElement")) == null ? 
                 0 : ParseNDecimal(xml.Element("myElement")).Value;

Is there a better way to write this statement?

EDIT : Perhaps I should have clarified my example as I do have a helper method to parse the string into a decimal.

+6  A: 

You can use extension method:

public static T TryGetValue<T>( this XmlElement element ) {
    if ( null == element ) return default(T);
    return (T)element.Value;
}
...
...
var myVariable = xml.Element("myElement").TryGetValue<decimal>();

EDIT:

The "universal" solution:

static class Program {
 static void Main() {
  var xmlDecimal = new XElement( "decimal" );
  xmlDecimal.Value = ( 123.456m ).ToString();
  decimal valueOfDecimal_1 = xmlDecimal.ValueAs<decimal>( decimal.TryParse );
  bool valueOfBool_1 = xmlDecimal.ValueAs<bool>( bool.TryParse );

  var xmlBool = new XElement( "bool" );
  xmlBool.Value = true.ToString();
  decimal valueOfDecimal_2 = xmlBool.ValueAs<decimal>( decimal.TryParse );
  bool valueOfBool_2 = xmlBool.ValueAs<bool>( bool.TryParse );
 }
}

public static class StaticClass {
 public delegate bool TryParseDelegate<T>( string text, out T value );
 public static T ValueAs<T>( this XElement element, TryParseDelegate<T> parseDelegate ) {
  return ValueAs<T>( element, parseDelegate, default( T ) );
 }
 public static T ValueAs<T>( this XElement element, TryParseDelegate<T> parseDelegate, T defaultValue ) {
  if ( null == element ) { return defaultValue; }

  T result;
  bool ok = parseDelegate( element.Value, out result );
  if ( ok ) { return result; }

  return defaultValue;
 }
}
TcKs
+1 Nice one, beat me to it.
AnthonyWJones
Although I would consider a different name such as GetValueOrDefault. The TryXXXX has the expectation to return a bool and take an out parameter.
AnthonyWJones
I doubt that this can be generic in the real code, which I suspect uses XAttribute rather than XElement.Value. How can a cast from string to decimal ever work?
Jon Skeet
@Jon Skeet: You can use Convert.ToDecimal( element.Value );
TcKs
But at that point it's not the original code. That's my point - I don't think the question shows the actual code.
Jon Skeet
TcKs: you're missing the point. Jon is right, you can't do that in a generic extension function and expect it to work for every type.
OJ
@OJ: I understand now. Look at edited code in post, there is "universal" solution.
TcKs
+5  A: 

Edit: Given the edited question, this is much simpler.

Again it uses an extension method, but now there's no need to do a conversion in the method.

var myVariable = ParseNDecimal(xml.Element("myElement").ValueOrDefault("0"));

...

public static string ValueOrDefault(this XElement element, 
                                     string defaultValue)
{
    return element != null ? element.Value : defaultValue;
}

If you don't like the method taking a string parameter, you could make it take object and call ToString, then call it like this:

var myVariable = ParseNDecimal(xml.Element("myElement").ValueOrDefault(0m));

However, that feels a little bit wrong to me. It assumes that the parsing will be the reverse of ToString formatting.

Original answer

There's nothing particularly in the language to help you. (I'm not sure that you've got the exact code right - don't you mean something with an XAttribute?) I'd suggest writing a utility method:

var myVariable = xml.Element("myElement").ValueOrDefault(0m);

...

public static decimal ValueOrDefault(this XElement element, 
                                     decimal defaultValue)
{
    return element != null ?(decimal) element.Value : defaultValue;
}

If you adjust the code in the question, I'll do likewise for the code here. I suspect you did mean to use XAttribute, which leads to a problem with generics - I haven't written the above in a generic way, because I believe you will want to call the XAttribute "conversion to decimal" operator. A generic cast won't do that, as it doesn't know what kind of conversion you want at compile time. You can, however, overload the above method for all the result types you're interested in.

Jon Skeet
@Jon: Did you mean (element != null) ?
Zach Scrivena
+1  A: 

You can use the ?? operator to write this a bit more cleanly, but I'm not sure that you should ...

Element() returns null of there is no child element with that name, so that's where you could use ?? to slide in a default element. You need to do this prior to the invocation of the (decimal) cast:

var myVariable 
    = (decimal)(xml.Element("myElement") ?? new XElement("myElement", 0));

Like I said though, while this will work, I'm not sure that you should do this. YMMV.

Bevan