8

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#?

4

6 回答 6

3

没有直接回答提出的问题,但我会写这个:

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

所以大部分代码只存在一次——如果它选择的话,你甚至现在实际上可以选择调用代码null作为默认值。


即使您可以创建所需的参数声明,这行仍然是一个问题:

return default_value;

如果结果default_value是 aT?而不是 a T,那么上面的代码就不起作用了。即使您进行演员表:

return (T)default_value;

还有一个问题——要转换 from T?to T,编译器实际上必须插入一个调用来获取Value可为空的属性。default_value但是,如果类型只是,那么该调用将无效T

在 C# 泛型中,编译器必须为方法创建一段 IL。没有办法插入一段可以访问的可选代码Value

于 2012-09-28T08:22:11.167 回答
1

有我的例子:

//Page extension
static class PageExtensions
{
    private static T FetchValue<T>(this Page page, string name, object defaultValue)
    {
        string str = page.Request.QueryString[name];
        if (string.IsNullOrEmpty(str))
        {
            if (defaultValue != null)
                return (T)defaultValue;

            throw new HttpRequestValidationException("A " + name + " must be specified.");
        }

        //not the best way
        return (T)Convert.ChangeType(str, typeof(T));
    }

    public static T FetchValueFromCurrentPage<T>(string name, T defaultValue)
    { 
      var page = HttpContext.Current.Handler as Page;
      if(page == null)
        throw new InvalidOperationException("Current handler is not Page");
      return page.FetchValue<T>(name, defaultValue);
    }

    public static T FetchValueFromCurrentPage<T>(string name) where T : class
    {   
        return FetchValueFromCurrentPage(name, (T)null);
    }
}
于 2012-09-28T12:14:07.480 回答
1

由于您的返回类型是TT并且是值类型,因此它不能为空。所以你总是必须传递一个可为空的类型,因为你想要一个空返回,对吧?

试试这个,它允许你传递一个可以为空的值类型(i)和一个普通的引用类型(o):

static void Main(string[] args)
{
    int? i = 5;
    object x = new object();

    object o = FetchValue("x", i);
    o = FetchValue("x", x);
}

private static T? FetchValue<T>(string name, T? p) where T : struct
{
    T? result = (T?)FetchValue(name, (object)p);
    return result;
}

private static T FetchValue<T>(string name,
    T default_value = default (T)) // default(T) where T is a reference type will always be null!
    where T : class
{
    // do whatever you want
    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));
}

请记住,这都是 foo 语法,因为object无论如何你最终都会得到 T 的类型。然而,这是必需的,因为只有object's 可以为空。

于 2012-09-28T08:47:43.190 回答
0

您可以指定约束以确保调用者传入引用类型(可以为空):

public static T FetchValue<T>(
   string name, 
   T default_value = null) where T : class //!
{
  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));
}

在这种情况下,您将转换 T 的责任委托给 T?必要时给呼叫者,这无论如何都很好:他可能知道得更好!

于 2012-09-28T08:27:50.727 回答
0

尝试以下操作:

private static T FetchValue<T>(string name, Nullable<T> default_value = null) where T : struct
{
    var page = HttpContext.Current.Handler as Page;

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

    if (str == null)
    {
        if (default_value == null)
        {
            throw new Exception("A " + name + " must be specified.");
        }
        else
        {
            return (T)Convert.ChangeType(default_value, typeof(T));
        }
    }

    return default(T);
}

可空类型必须是值类型,因此您必须将约束设置为struct。如果有一个值,则您将类型声明T为并转换类型,否则您返回的类型在这种情况下为空。调用此方法时,您还必须像这样显式设置类型:NullabledefaultT

int? val = FetchValue<int>("name");

返回一个字符串

由于 astring是可为空的引用类型,因此您正确地说它不能与此函数一起使用。在那种情况下,我会创建一个新的struct,并且对于字符串使用它来代替。

struct NullableString
{
    public string Value;
}

像这样调用函数。

var val = FetchValue<NullableString>("blah", new NullableString() { Value = "default" });
于 2012-09-28T08:16:10.173 回答
0

你可以用这种简单的方式做你想做的事:

public static T FetchValue<T>(string name, T defaultValue = null) where T : class { }
public static T? FetchValue<T>(string name, T? defaultValue = null) where T : struct { }
于 2012-09-28T08:49:03.860 回答