7

Is there a single-line way of casting an object to a decimal? data type?

My code looks something like this:

foreach(DataRow row in dt.Rows)
{
    var a = new ClassA()
    {
         PropertyA = row["ValueA"] as decimal?,   
         PropertyB = row["ValueB"] as decimal?,
         PropertyC = row["ValueC"] as decimal?
    };

    // Do something

}

However casting an object to a decimal? doesn't work the way I expect it to, and returns null every time.

The data rows are read from an Excel file, so the data type of the object in each row is either a double if there is a value, or a null string if it's left blank.

The suggested method to perform this cast is to use decimal.TryParse, however I do not want to create a temp variable for every decimal property on the class (I have about 7 properties on my actual class that are decimals, not 3).

decimal tmpvalue;
decimal? result = decimal.TryParse((string)value, out tmpvalue) ?
                  tmpvalue : (decimal?)null;

Is there a way I can cast a potentially null value to a decimal? in a single line?

I tried the answer posted here, however it doesn't appear to be working and gives me a null value as well because row["ValueA"] as string returns null.

Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
  • 7
    casting is very different from *converting*. What is the actual type of the starting variable? Is it a string, or a decimal, or an integer or a double, or what? If it's a string you need to convert it, not cast it. There's no way around that. If it's actually a decimal, and it's just that the compiler doesn't know it, then it's appropriate to cast it. – Servy Jan 02 '13 at 17:34
  • @Servy Cyborg, the DataRow is read from an Excel file, so providing there is a value it reads as a `double`, otherwise I think it's a `string`. I'll add that information to my question – Rachel Jan 02 '13 at 17:37
  • 2
    why not just put the TryParse logic in a function and then just do `PropertyA = FunctionRachelWroteToTryParseADecimal(row["ValueA"])` – Cristian Lupascu Jan 02 '13 at 17:37
  • @w0lf I'll probably end up doing that. I thought there would be a single-line way of casting to a `decimal?` already that I wasn't aware of, but it's beginning to sound like that isn't the case – Rachel Jan 02 '13 at 17:39

7 Answers7

6

Your best bet is to create an extension method. This is what I use, although you can tweak it to return a decimal? instead.

public static class ObjectExtensions
{
    public static decimal ToDecimal(this object number)
    {
        decimal value;
        if (number == null) return 0;
        if (decimal.TryParse(number.ToString().Replace("$", "").Replace(",", ""), out value))
            return value;
        else
            return 0;
    }
    public static decimal? ToNullableDecimal(this object number)
    {
        decimal value;
        if (number == null) return null;
        if (decimal.TryParse(number.ToString().Replace("$", "").Replace(",", ""), out value))
            return value;
        else
            return null;
    }

}

You would then use it by calling .ToDecimal() on any object, the same way you would call .ToString().

It's not one line, but it is only a single function call at the point where it's used, and it's very reusable.

Edited to add nullable version

Bobson
  • 13,498
  • 5
  • 55
  • 80
  • 1
    I would recommend combining this with Ulises's answer, to use a nullable instead of returning 0, for a more explicit result. – Claus Jørgensen Jan 02 '13 at 17:41
  • 1
    The problem here is that the `object` isn't always a string. Sometimes it's actually a numeric value. It's quite wasteful and expensive to take a numeric value, convert it to a string, and then parse it back into a numeric value. It's much better to check first if it's already a numeric value and just cast it, when applicable. – Servy Jan 02 '13 at 17:45
  • @ClausJørgensen - It really depends on your overall usage. For my code, I'd be calling `GetValueOrDefault()` on the result a lot if it returned `null`s. But I edited in a nullable version just to cover all bases. – Bobson Jan 02 '13 at 17:48
  • What are the `"$"` and `","` magic values in the code? Is it part of some Excel-specific format, or is it another case where the user isn't working correctly if he doesn't happen to speak US-English? – dtb Jan 02 '13 at 17:51
  • @dtb - The latter. It enables this to handle parsing US dollar values like `$4,201.88` (Four thousand, two hundred and one dollars and 88 cents). They can be stripped out, replaced with your local currency, or whatever. – Bobson Jan 02 '13 at 17:55
  • @dtb My guess is this is a general purpose method the answerer copied from what he uses himself. In this specific case there's no need for it. – Servy Jan 02 '13 at 17:57
  • Yep. "This is what I use". It's why it didn't even return a `decimal?` at first - it was more of an example than a "Copy this directly" answer. – Bobson Jan 02 '13 at 17:59
  • Thanks, an extension method is definitely the most readable way to accomplish this, however I am accepting [Servy's answer](http://stackoverflow.com/a/14126609/302677) because it has slightly better performing extension method for my situation, and because he posted a one-line alternative as well :) – Rachel Jan 02 '13 at 18:10
4

providing there is a value it reads as a double, otherwise I think it's a string

Okay, so this complicates matters as sometimes you need to parse it and sometimes you don't.

The first thing we'll want to do is check if it's already a double and cast it, because casting is much cheaper than parsing; parsing is expensive.

Given that this logic will be non-trivial, it belongs in it's own method:

public static deciaml? getDecimal(object rawValue)
{
    decimal finalValue;

    double? doubleValue = rawValue as double?;
    if(doubleValue.HasValue)
        return (decimal) doubleValue.Value;
    else if(decimal.TryParse(rawValue as string, out finalValue))
    {
        return finalValue;
    }
    else
    {
        return null;//could also throw an exception if you wanted.
    }
}

If you know that it will always be a double, and you'll never want the string values, then it's easier still. In that case you need to cast it to a double, first, since that's it's real type, and then you can easily convert it to a decimal:

PropertyA = (decimal?)(row["ValueA"] as double?);
Servy
  • 202,030
  • 26
  • 332
  • 449
  • 2
    I really like your second answer. Very simple and elegant. – Bobson Jan 02 '13 at 18:01
  • 1
    @Bobson If the value is *always* a `double` it's ideal. If it's sometime a `double` and sometimes a `string`, and you need to handle both, then you would need to use the first instead. – Servy Jan 02 '13 at 18:06
1

decimal.TryParse is the way to go. Just create a helper method and return the value. Pretty much the same code you mentioned:

private decimal? convertToNullableDecimal(object value){
  decimal tmpvalue;
  return decimal.TryParse((string)value, out tmpvalue) ? tmpvalue : (decimal?)null;
}

UPDATE

This assumes you need a nullable decimal back. Otherwise do as suggested in other answers, use the built-in method: Convert.ToDecimal

Ulises
  • 13,229
  • 5
  • 34
  • 50
0

Try something like

Func<object, decimal?> l_convert =
    (o) => decimal.TryParse((o ?? "").ToString(), out tmpvalue) ? tmpvalue : (decimal?)null;

var a = new ClassA()
{
     PropertyA = l_convert(row["ValueA"]),   
     PropertyB = l_convert(row["ValueB"]),
     PropertyC = l_convert(row["ValueC"])
};
JDB
  • 25,172
  • 5
  • 72
  • 123
0

Hope this helps

decimal temp;
foreach(DataRow row in dt.Rows)
{
    var a = new ClassA()
    {
         PropertyA = Decimal.TryParse(row["ValueA"].ToString(), out temp) ? Convert.ToDecimal(row["ValueA"].ToString()) : (Decimal?) null,   
         ....
    };

    // Do something

}
codingbiz
  • 26,179
  • 8
  • 59
  • 96
0

You may try the Decimal.Parse method.

public static decimal ToDecimal(this string value){

    try
    {
        return Decimal.Parse(value);
    }   
    catch (Exception)
    {
        //  catch FormatException, NullException
        return 0;
    }
}

//  use
PropertyA = row["ValueA"].ToString().ToDecimal();
PropertyB = row["ValueB"].ToString().ToDecimal();
PropertyC = row["ValueC"].ToString().ToDecimal();
João Simões
  • 1,351
  • 1
  • 10
  • 20
0

Convert.ToDecimal(object) almost does what you want. It will convert the double value properly (without going to a string first), and also handle other types that can convert to decimal. The one thing it doesn't do well is the null conversion (or converting of String.Empty to null). This I would try to handle with an extension method.

public static class ObjectToDecimal
{
  public static decimal? ToNullableDecimal(this object obj)
  {
    if(obj == null) return null;
    if(obj is string && String.Empty.Equals(obj)) return null;
    return Convert.ToDecimal(obj);
  }
}

Just remember that Convert.ToDecimal(object) can thrown exceptions, so you'll have to watch out for that.

Joel Rondeau
  • 7,486
  • 2
  • 42
  • 54