I have written a helper method which computes the sum of values in some custom grid, given the column indexes. The method appears to work (for a decimal - as Anthony pointed out, I need to test this further), but I am not at ease with the following conversions:
decimal valIfNull = (decimal)(object)valueIfNull;
return (T)(object)sum;
They seem too indirect, but Convert.ChangeType
also returns an object
type, so another cast is needed anyway. Also, I believe that Convert.ChangeType
can convert a string such as "123.456" into a proper decimal value, but this is not the behavior I want in this case. I would like to see an invalid cast exception in that case. So, is what I have good enough, or is there a better way?
private T SumColumns<T>(T valueIfNull, params short[] columnIndexes)
where T : struct, IConvertible
{
// Since we do not know what exactly T is, we will compute things as if it was a decimal.
// If this works reasonably well, then this trick should be used elsewhere.
// I am not sure if this is the best way to go about casting
decimal valIfNull = (decimal)(object)valueIfNull;
// Here this.GetCurrentRowValue<decimal> is another generic function I wrote ...
decimal sum = columnIndexes.Select(columnIndex => this.GetCurrentRowValue<decimal>(columnIndex: columnIndex, valueIfNull: valIfNull)).Sum();
return (T)(object)sum;
}
1 Answer 1
You've already placed a constraint on the first parameter that it should be IConvertible
, so the methods of that interface is available to you.
decimal decimalValueIfNull = valueIfNull.ToDecimal(CultureInfo.InvariantCulture);
Converting back to the generic type won't be as straight forward. When casting between an object
and a value type, no conversions will be made and you are just boxing/unboxing the value. Since you have a constraint that the type be a value type, the casts won't really work here. In fact, I don't think it's even possible to do so generically. If a conversion exists from decimal
to the type you wouldn't be able to express that here. And you won't be able to use the Convert class to do the conversion, there's no way to "add" conversions to existing convertible types.
Your best (and probably only) option would be to convert it to a dynamic
variable and let the runtime figure out whether or not that conversion exists. That way if you have a custom ValueType
here that has an implicit or explicit conversion from a decimal
, it will be used.
return (T)(dynamic)sum;
If you know for sure that the types that are used here can be converted from a decimal
, then you could convert back using IConvertible.ToType()
.
return ((IConvertible)sum).ToType(typeof(T), CultureInfo.InvariantCulture);
-
\$\begingroup\$ Why not use
IConvertible.ToType()
for the conversion back? \$\endgroup\$svick– svick2012年04月25日 08:08:03 +00:00Commented Apr 25, 2012 at 8:08 -
\$\begingroup\$ If
T
was a custom type (and not a builtin), the conversion would fail always sinceIConvertible.ToType()
only considers the conversions defined by the object that implemented the interface.decimal
will not have a conversion defined for your custom object and as I mentioned, it's not possible to add a conversion. This approach ensures that if the custom type provided a conversion from adecimal
, it will be used. \$\endgroup\$Jeff Mercado– Jeff Mercado2012年04月25日 13:44:35 +00:00Commented Apr 25, 2012 at 13:44 -
\$\begingroup\$ Jeff, thanks a bunch. I do not plan on using a custom IConvertible type here. By the way, I have to supply an
IFormatProvider
to theToDecimal
method. \$\endgroup\$Leonid– Leonid2012年04月25日 15:51:25 +00:00Commented Apr 25, 2012 at 15:51 -
\$\begingroup\$ @svick, did you mean
Convert.ChangeType
? I was not sure how to invokeIConvertible.ToType()
on mydecimal sum
. \$\endgroup\$Leonid– Leonid2012年04月25日 15:58:37 +00:00Commented Apr 25, 2012 at 15:58 -
\$\begingroup\$ @Leonid: You would need to cast the
decimal
toIConvertible
to be able to invoke that method. I'll include that detail in the answer. \$\endgroup\$Jeff Mercado– Jeff Mercado2012年04月25日 18:13:57 +00:00Commented Apr 25, 2012 at 18:13
T
is notdecimal
. When you box a value type, you can only unbox to the same type (or its nullable counterpart). For example, givenlong L = long.MaxValue;
, you could not have(decimal)(object)L;
\$\endgroup\$