4

I've got a Session that contains particular integer values, which are indexed with given controls. Normally, the following would work just fine:

int value;
int.TryParse(Session["Key"].ToString(), out value);

However, I do need to account for null. Where, if the string fails the default out would return a null. Except I noticed that int.TryParse doesn't work with:

int? value = null;
int.TryParse(Session["Key"].ToString(), out value);

So how can you try that parse, if fails it results in the null?

I found this question and the Microsoft Developer Network dictates:

When this method returns, contains the signed integer value equivalent of the number contained in s, if the conversion succeeded, or zero if the conversion failed. The conversion fails if the string parameter is null or String.Empty, is not of the correct format, or represents a number less than Min Value or greater than Max Value. This parameter is passed uninitialized.

Which plainly states, if int.TryParse fails the integer will hold a value of zero. In the instance of my usage, zero could be a valid value. So I need null, any thoughts?

Community
  • 1
  • 1
Greg
  • 11,302
  • 2
  • 48
  • 79
  • 1
    Why would not regular cast work? `(int?)(Session["Key"])` – Alexei Levenkov Nov 10 '14 at 23:49
  • @AlexeiLevenkov I could potentially do: `Session["Key"] as int?` where it returns nullable if it fails. – Greg Nov 10 '14 at 23:54
  • I'm sort of confused - I assume you put nullable into session state - so there will be either valid `int?` value or key missing - both should work with cast/as successfully. I see no case where "it fails"... – Alexei Levenkov Nov 10 '14 at 23:56
  • @AlexeiLevenkov Your correct, but other developers may do 'bad data'. Though they shouldn't. – Greg Nov 10 '14 at 23:59
  • @AlexeiLevenkov The other potential issue with a regular cast, if it fails it throws an exception within IIS which you wouldn't want users to see. – Greg Nov 12 '14 at 15:09

3 Answers3

9

Sure; utilize the return value of int.TryParse (which returns if the conversion succeeded or not):

int? retValue = null;
int parsedValue = 0;

if (int.TryParse(Session["Key"].ToString(), out parsedValue))
    retValue = parsedValue;
else
    retValue = null;

return retValue;

A little verbose I'll admit, but you could wrap it in a function.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
6
int tmp;
int? value = int.TryParse(Session["Key"].ToString(), out tmp) ? (int?)tmp : null;
AlexD
  • 32,156
  • 3
  • 71
  • 65
  • This code obviously throws NullReferenceException when key does not exist/has `null` value (exactly the same behavior as OP wants, but not necessary one what others may need. Beware). – Alexei Levenkov Nov 10 '14 at 23:52
  • @AlexeiLevenkov No doubt, but I guess it is beyond the scope of the question to cover this case. – AlexD Nov 10 '14 at 23:54
2

The problem is the word "null." What does it mean? null could mean the value was indeterminable, an exception was thrown, simply that the value is null, or some other contextual meaning. Your question is a perfect example, because you, yourself, are arbitrarily stating that, in your opinion, null means the parsing of the string failed.

Microsoft's TryParse paradigm is great, but for limited usage. Consider these Scenarios:

  • string == "89"
  • string == null
  • string == "Hello World"
  • string == ""
  • string == "2147483650"

Yet, your only options are to assign an Integer or Null to your output, and to return true or false.

Assuming it worked, what are you going to do with that information? Something like this?

int? value = null;
if (int.TryParse(Session["Key"].ToString(), out value)) {
    if (value == null)
        // Handle "Appropriate" null
    else
        // Handle appropriate numeric value
}
else {
    // Note: value == null here, and TryParse failed
    // Handle null...
    // What if the reason it failed was because the number was too big?
    // What if the string was Empty and you wanted to do something special?
    // What if the string was actually junk?  Like "(423)322-9876" ?
    // Long-Story Short: You don't know what to do here without more info.
}

Consider this NullableInt TryParse example:

public bool TryParseNullableInt(string input, out int? output)
{
    int tempOutput;
    output = null;
    if (input == null) return true;
    if (input == string.Empty) return true; // Would you rather this be 0?

    if (!int.TryParse(input, out tempOutput))
        return false; // What if string was "2147483650"... or "Twenty Three"?
    output = tempOutput;

    return true;
}

One solution is to use an enumeration TryParse instead of a boolean TryParse:

public ParseStatus TryParseNullableInt(string input, out int? output)
{
    int tempInteger;
    output = null;
    if (input == null) return ParseStatus.Success;
    if (input == string.Empty) { output = 0; return ParseStatus.Derived; }

    if (!int.TryParse(input, out tempInteger)) {
        if (ParseWords(input, out tempInteger)) { // "Twenty Three" = 23
            output = tempInteger;
            return ParseStatus.Derived;
        }
        long tempLong;
        if (long.TryParse(input, out tempLong))
            return ParseStatus.OutOfRange;
        return ParseStatus.NotParsable;
    }
    output = tempInteger;

    return ParseStatus.Success;
}

Another problem is the existence of the out variable. Your third option is to use a descriptive monad, something like this:

public Maybe<int?> TryParseNullableInt(string input)
{
    if (input == null) return Maybe.Success(null);
    if (input == string.Empty) { return Maybe.Derived(0); }

    int tempInteger;
    if (!int.TryParse(input, out tempInteger)) {
        if (ParseWords(input, out tempInteger)) { // "Twenty Three" = 23
            return Maybe.Derived(tempInteger);
        }
        long tempLong;
        if (long.TryParse(input, out tempLong))
            return Maybe.OutOfRange();
        return Maybe.NotParsable();
    }

    return Maybe.Success(tempInteger);
}

You can use Monads as Single-Enumerable Values, or like so:

Maybe<int?> result = TryParseNullableInt("Hello");
if (result.HasValue) {
    if (result.Status == ParseStatus.Success)
        // Do something you want...
    else if (result.Status == ParseStatus.Derived)
        // Do something else... more carefully maybe?
}
else if (result.Status == ParseStatus.OutOfRange)
    MessageUser("That number is too big or too small");
else if (result.Status == ParseStatus.NotParsable)
    // Do something

With Monads, and possibly enumeration TryParses, you now have all the info you need from a descriptive return and nobody has to guess what null might mean.

Suamere
  • 5,691
  • 2
  • 44
  • 58
  • Great answer! I've just thinking about replacing TryParse pattern with simply Maybe monad. I think that 'out' parameter adds unnecessary complexity. For simpler scenarios I even use a named tuple that has better support nowadays. `(success, result) = FuncName(..);` `public (bool success, Subject result) FuncName (args..) {}` – proximab Apr 21 '21 at 11:36