2

MRE: https://dotnetfiddle.net/M7WeMH

I have this generic utility:

    static T readValue<T>(string value) 
    {
        return (T)Convert.ChangeType(value, typeof(T));
    }

Reading some data from a file that should all be decimal values, I get an exception when the text is in scientific notation e.g. "1E-5" - this occurs rarely in the files. Testing further I find that decimal.Parse has the same problem:

using System;
                    
public class Program
{
    static T readValue<T>(string value) 
    {
        return (T)Convert.ChangeType(value, typeof(T));
    }

    public static void Main()
    {
        try
        {
            Console.WriteLine($"{readValue<decimal>("1E-5")}");
        }
        catch(Exception e)
        {
            Console.WriteLine(e);
        }
        try
        {
            Console.WriteLine($"{decimal.Parse("1E-5")}");
        }
        catch(Exception e)
        {
            Console.WriteLine(e);
        }
    }
}

System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Convert.ToDecimal(String value, IFormatProvider provider) at System.String.System.IConvertible.ToDecimal(IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider) at System.Convert.ChangeType(Object value, Type conversionType) at Program.readValue[T](String value)
at Program.Main()

System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type) at System.Number.ParseDecimal(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info) at System.Decimal.Parse(String s) at Program.Main()

This question explains how to fix it for decimal.Parse (Parse a Number from Exponential Notation) but I this generic method is used for loading all sorts of file data from CSV files in many places... is there an equivalent fix to avoid a lot of code refactoring?

Mr. Boy
  • 60,845
  • 93
  • 320
  • 589
  • 2
    There's absolutely nothing about your method that is preventing you from switching inside and performing different behavior for outliers like scientific notation. – Logarr Sep 09 '21 at 21:09
  • seriously, marked as a dupe of the question I explicitly include as _part of_ my question... that does not answer my question about doing this in a generic method. – Mr. Boy Sep 09 '21 at 21:23
  • 1
    I will post an answer when I see it re-opened, but for now you need three parts: `if (typeof(T) == typeof(decimal))` for control flow based on type (you can put `typeof(T)` into a variable for multiple checks). A check for scientific notation within the `value` string. A convoluted series of parse, convert, and cast: `return (T)Convert.ChangeType(decimal.Parse(value, NumberStyles.Float), typeof(T));` – Logarr Sep 09 '21 at 21:28
  • @Logarr that last step is the one I had issues with. I thought you could switch on type and return explicit type but the compiler is not _quite_ smart enough I guess :) – Mr. Boy Sep 09 '21 at 21:29
  • @Logarr however that allowed me to easily get things working so thankyou for taking the time! – Mr. Boy Sep 09 '21 at 21:45
  • As a final note I will also link you to [this answer](https://stackoverflow.com/a/49758530/1127924) which raises a really good point about what makes a method "generic". I have written a lot of generic code in the last couple of months, and had to stop myself from going overboard. Since you have specific cases to handle, different methods would be much cleaner to read/maintain, and only slightly more work to consume. – Logarr Sep 09 '21 at 21:49
  • @Logarr I note this was re-opened. Your comments seem to form a workable solution if you would care to provide an answer? If not I might add my own answer based on your help. – Mr. Boy Sep 10 '21 at 09:30

2 Answers2

2

This is ugly as all get out, but here's an example of how specific type behavior could be done:

static T readValue<T>(string value) where T : struct
{
    var type = typeof(T);
    if (type == typeof(decimal))
    {
        // Put this return statement in a block that verifies the content of the string is scientific notation.
        return (T)Convert.ChangeType(decimal.Parse(value, NumberStyles.Float), typeof(T));
    }
    return (T)Convert.ChangeType(value, typeof(T));
}
Logarr
  • 2,120
  • 1
  • 17
  • 29
1

The reason is because the default style used by Decimal.Parse and Decimal.TryParse is like this

[ws][sign][digits,]digits[.fractional-digits][ws]

which doesn't allow the exponent. If you read Parse's documentation you'll see that the full number format is

[ws][$][sign][digits,]digits[.fractional-digits][e[sign]digits][ws]

where

e: The 'e' or 'E' character, which indicates that the value is represented in exponential notation. The s parameter can represent a number in exponential notation if style includes the NumberStyles.AllowExponent flag.

That means you need to use NumberStyles.Float (which includes NumberStyles.AllowExponent) as above to make it work. Some simple demo:

var style = System.Globalization.NumberStyles.Float;
var culture = CultureInfo.GetCultureInfo("en-US");
string s = "1E-5";
var i = decimal.Parse(s, style);
decimal dec = 0;
decimal.TryParse(s, style, culture, out dec);
phuclv
  • 37,963
  • 15
  • 156
  • 475
  • this question isn't about decimal.parse... I include a link to a question which explains that in my question – Mr. Boy Sep 10 '21 at 16:53