在C#中“推广”泛型类型为Nullable?

时间:2012-09-28 07:46:09

标签: c# generics metaprogramming nullable typetraits

在C#中给定泛型类型T,我想知道如何获取类型Q,对于不可为空的T?等于T,并且{{1已经可以为空的T

问题来自真实的代码。我想统一访问我的ASP.NET应用程序中通过查询字符串传递的参数。我想指定相同类型的默认值,但确保T可以作为默认值传递。

null

目前我被迫有两个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)); } 重载 - 一个没有默认值,一个有它:

FetchValue

它工作正常,但我想知道是否可以合并这两个函数。

在C ++中,我会使用类型特征,例如public static T FetchValue<T>(string name); public static T FetchValue<T>(string name, T default_value); ,对于可空和非可空类型都有两个PromoteNullable<T>::type特化。但是C#呢?

6 个答案:

答案 0 :(得分: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_valueT?而不是T,则上述代码无效。即使你做演员:

return (T)default_value;

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

在C#Generics中,编译器必须为该方法创建一个IL。无法插入可能访问Value的可选代码。

答案 1 :(得分:1)

由于您的返回类型为T,而T属于值类型,因此不能为空。 所以你总是要传递一个可以为空的类型,因为你想要一个null,对吗?

试试这个,它允许你传递一个可以为空的值类型(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类型的句子。但这是必需的,因为只有object才能为空。

答案 2 :(得分: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);
    }
}

答案 3 :(得分: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);
}

Nullable类型必须是值类型,因此您必须将约束设置为struct。您将类型T声明为Nullable并在有值的情况下转换类型,否则返回类型为default的{​​{1}},在这种情况下为空。调用此方法时,还必须明确设置类型:

T

返回字符串

由于int? val = FetchValue<int>("name"); 是可以为空的引用类型,因此您可以正确地说它不能与此函数一起使用。在这种情况下,我会创建一个新的string,而字符串则使用它。

struct

像这样调用这个函数。

struct NullableString
{
    public string Value;
}

答案 4 :(得分:0)

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

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?当有必要给来电者时,无论如何这很好:他可能知道的更好!

答案 5 :(得分: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 { }