Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Promote" generic type to Nullable in C#?

Given a generic type T in C#, I wonder how to acquire type Q, which is equal to T? for non-nullable T, and T for already nullable T.

The question arose from real code. I want to unify access to parameters passed through query string in my ASP.NET application. And I want to specify a default value of the same type, but ensure null can be passed as a default value.

public static T FetchValue<T>(
   string name, 
   <T? for non-nullable, T otherwise> default_value = null)  // How to write this?
{
  var page = HttpContext.Current.Handler as Page;

  string str = page.Request.QueryString[name];

  if (str == null)
  {
    if (default_value == null)
    {
      throw new HttpRequestValidationException("A " + name + " must be specified.");
    }
    else
    {
      return default_value;
    }
  }

  return (T)Convert.ChangeType(str, typeof(T));
}

Currently I'm forced having two overloads of the FetchValue - one without default value, and one with it:

public static T FetchValue<T>(string name);
public static T FetchValue<T>(string name, T default_value);

It works fine, but I wonder whether it is possible to merge both functions like this.

In C++ I would use type-traits, like PromoteNullable<T>::type with two specializations of PromoteNullable for both nullable and non-nullable types. But what about C#?

like image 311
Mikhail Avatar asked Sep 28 '12 07:09

Mikhail


1 Answers

Doesn't directly answer the question as posed, but I'd write this:

    public static T FetchValue<T>(string name)
    {
        T value;
        if (TryFetchValue(name, out value))
            return value;
        throw new HttpRequestValidationException("A " + name + " must be specified.");
    }

    public static T FetchValue<T>(string name, T default_value)
    {
        T value;
        if (TryFetchValue(name, out value))
            return value;
        return default_value;
    }

    private static bool TryFetchValue<T>(
         string name,
         out T value)
    {
        var page = HttpContext.Current.Handler as Page;

        string str = page.Request.QueryString[name];

        if (str == null)
        {
            value = default(T);
            return false;
        }

        value = (T)Convert.ChangeType(str, typeof(T));
        return true;
    }

So the bulk of the code exists only once - and you can even now actually have the calling code choose to have null as a default value, if it so chooses.


Even if you could create the parameter declaration you wanted, this line would still be an issue:

return default_value;

If it turned out that default_value was a T? rather than a T, then the above code doesn't work. Even if you do a cast:

return (T)default_value;

there's still an issue - that to cast from T? to T, the compiler actually has to insert a call to obtain the Value property of the nullable. But that call wouldn't be valid if the type of default_value was just T.

In C# Generics, the compiler has to create one piece of IL for the method. There's no way to insert an optional piece of code that may access Value.

like image 71
Damien_The_Unbeliever Avatar answered Oct 19 '22 15:10

Damien_The_Unbeliever