tags:

views:

386

answers:

6

I am trying to combine a bunch of similar methods into a generic method. I have several methods that return the value of a querystring, or null if that querystring does not exist or is not in the correct format. This would be easy enough if all the types were natively nullable, but I have to use the nullable generic type for integers and dates.

Here's what I have now. However, it will pass back a 0 if a numeric value is invalid, and that unfortunately is a valid value in my scenarios. Can somebody help me out? Thanks!

public static T GetQueryString<T>(string key) where T : IConvertible
{
    T result = default(T);

    if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
    {
        string value = HttpContext.Current.Request.QueryString[key];

        try
        {
            result = (T)Convert.ChangeType(value, typeof(T));  
        }
        catch
        {
            //Could not convert.  Pass back default value...
            result = default(T);
        }
    }

    return result;
}
+3  A: 

What about this? Change the return type from T to Nullable<T>

public static Nullable<T> GetQueryString<T>(string key) where T : struct, IConvertible
        {
            T result = default(T);

            if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
            {
                string value = HttpContext.Current.Request.QueryString[key];

                try
                {
                    result = (T)Convert.ChangeType(value, typeof(T));  
                }
                catch
                {
                    //Could not convert.  Pass back default value...
                    result = default(T);
                }
            }

            return result;
        }
Ngu Soon Hui
Error: The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'.
Mike C.
You also need to specify `where T : struct`.
Aaronaught
Same error.....
Mike C.
@Mike C: You should not be getting the same error. The edited code definitely compiles.
Aaronaught
Yup, got it now. So what happens when I want to call this for the String type? It won't accept it as is now.
Mike C.
@MikeC, don't think that's possible because `string` is a `nullable` value
Ngu Soon Hui
Yeah, but I would like one function to handle String, Integer, Date, and Guid values.
Mike C.
+5  A: 

I know, I know, but...

public static bool TryGetQueryString<T>(string key, out T queryString)
Jay
LOL. That is my last resort.
Mike C.
+1 Excellent suggestion.
280Z28
The `Try`-pattern should be well known to any .NET developer. It is not a bad thing if you ask me. In F# or NET 4.0 you would use Option (or Choice)
SealedSun
If for no other reason, I try to avoid it because I hate having to "pre-declare" that output variable, especially if it never even gets used -- a waste of what could otherwise have been a perfectly good line of code.
Jay
Actually it is the simplest way to solve your problem -- define one function like above + two helpers which would use this function (those would be 4 liners).
macias
I hate the Try pattern for the same reason as Jay stated. I would prefer one generic function if possible, which was my original goal.
Mike C.
Also, it violates the Single Responsibility Principle.
Mike C.
Hmmm... that seems like an over-application of the principle.
Jay
A: 
Sam
A: 

I like to start with a class like this class settings { public int X {get;set;} public string Y { get; set; } // repeat as necessary

 public settings()
 {
    this.X = defaultForX;
    this.Y = defaultForY;
    // repeat ...
 }
 public void Parse(Uri uri)
 {
    // parse values from query string.
    // if you need to distinguish from default vs. specified, add an appropriate property

 }

This has worked well on 100's of projects. You can use one of the many other parsing solutions to parse values.

No Refunds No Returns
A: 

You can use sort of Maybe monad (though I'd prefer Jay's answer)

public class Maybe<T>
{
    private readonly T _value;

    public Maybe(T value)
    {
        _value = value;
        IsNothing = false;
    }

    public Maybe()
    {
        IsNothing = true;
    }

    public bool IsNothing { get; private set; }

    public T Value
    {
        get
        {
            if (IsNothing)
            {
                throw new InvalidOperationException("Value doesn't exist");
            }
            return _value;
        }
    }

    public override bool Equals(object other)
    {
        if (IsNothing)
        {
            return (other == null);
        }
        if (other == null)
        {
            return false;
        }
        return _value.Equals(other);
    }

    public override int GetHashCode()
    {
        if (IsNothing)
        {
            return 0;
        }
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        if (IsNothing)
        {
            return "";
        }
        return _value.ToString();
    }

    public static implicit operator Maybe<T>(T value)
    {
        return new Maybe<T>(value);
    }

    public static explicit operator T(Maybe<T> value)
    {
        return value.Value;
    }
}

Your method would look like:

    public static Maybe<T> GetQueryString<T>(string key) where T : IConvertible
    {
        if (String.IsNullOrEmpty(HttpContext.Current.Request.QueryString[key]) == false)
        {
            string value = HttpContext.Current.Request.QueryString[key];

            try
            {
                return (T)Convert.ChangeType(value, typeof(T));
            }
            catch
            {
                //Could not convert.  Pass back default value...
                return new Maybe<T>();
            }
        }

        return new Maybe<T>();
    }
Artem Govorov
+2  A: 

What if you specified the default value to return, instead of using default(T)?

public static T GetQueryString<T>(string key, T defaultValue) {...}

It makes calling it easier too:

var intValue = GetQueryString("intParm", Int32.MinValue);
var strValue = GetQueryString("strParm", "");
var dtmValue = GetQueryString("dtmPatm", DateTime.Now); // eg use today's date if not specified

The downside being you need magic values to denote invalid/missing querystring values.

Will
Yes, this seems more viable than relying on the default value of an integer. I'll keep this in mind. I'm still hoping to get my original function working for all types, although I just may resort to using non-generic functions.
Mike C.