Creating a generic method in C#

C#Generics

C# Problem Overview


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;
}

C# Solutions


Solution 1 - C#

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.

Solution 2 - C#

I know, I know, but...

public static bool TryGetQueryString<T>(string key, out T queryString)

Solution 3 - C#

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;
        }

Solution 4 - C#

Convert.ChangeType() doesn't correctly handle nullable types or enumerations in .NET 2.0 BCL (I think it's fixed for BCL 4.0 though). Rather than make the outer implementation more complex, make the converter do more work for you. Here's an implementation I use:

public static class Converter
{
public static T ConvertTo<T>(object value)
{
return ConvertTo(value, default(T));
}




public static T ConvertTo<T>(object value, T defaultValue)
{
if (value == DBNull.Value)
{
return defaultValue;
}
return (T) ChangeType(value, typeof(T));
}




public static object ChangeType(object value, Type conversionType)
{
if (conversionType == null)
{
throw new ArgumentNullException("conversionType");
}



// if it's not a nullable type, just pass through the parameters to Convert.ChangeType
if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable&lt;>)))
{
  // null input returns null output regardless of base type
  if (value == null)
  {
    return null;
  }

  // it's a nullable type, and not null, which means it can be converted to its underlying type,
  // so overwrite the passed-in conversion type with this underlying type
  conversionType = Nullable.GetUnderlyingType(conversionType);
}
else if (conversionType.IsEnum)
{
  // strings require Parse method
  if (value is string)
  {
    return Enum.Parse(conversionType, (string) value);			
  }
  // primitive types can be instantiated using ToObject
  else if (value is int || value is uint || value is short || value is ushort || 
       value is byte || value is sbyte || value is long || value is ulong)
  {
    return Enum.ToObject(conversionType, value);
  }
  else
  {
    throw new ArgumentException(String.Format("Value cannot be converted to {0} - current type is " +
                          "not supported for enum conversions.", conversionType.FullName));
  }
}

return Convert.ChangeType(value, conversionType);




}
}

} }

Then your implementation of GetQueryString<T> can be:

public static T GetQueryString<T>(string key)
{
T result = default(T);
string value = HttpContext.Current.Request.QueryString[key];



if (!String.IsNullOrEmpty(value))
{
    try
    {
        result = Converter.ConvertTo&lt;T>(value);  
    }
    catch
    {
        //Could not convert.  Pass back default value...
        result = default(T);
    }
}

return result;




}

}

Solution 5 - C#

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>();
    }

Solution 6 - C#

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike ColeView Question on Stackoverflow
Solution 1 - C#WillView Answer on Stackoverflow
Solution 2 - C#JayView Answer on Stackoverflow
Solution 3 - C#GravitonView Answer on Stackoverflow
Solution 4 - C#SamView Answer on Stackoverflow
Solution 5 - C#Artem GovorovView Answer on Stackoverflow
Solution 6 - C#No Refunds No ReturnsView Answer on Stackoverflow