在单个C#泛型方法中返回nullable和null?

在单个C#泛型方法中返回nullable和null?,c#,generics,nullable,C#,Generics,Nullable,在C#泛型方法中是否可以返回对象类型或可为空的类型 例如,如果我有一个用于列表的安全索引访问器,并且我想返回一个值,我可以稍后使用==null或.HasValue()进行检查 我目前有以下两种方法: static T? SafeGet<T>(List<T> list, int index) where T : struct { if (list == null || index < 0 || index >= list.Count) {

在C#泛型方法中是否可以返回对象类型或可为空的类型

例如,如果我有一个用于
列表的安全索引访问器
,并且我想返回一个值,我可以稍后使用
==null
.HasValue()
进行检查

我目前有以下两种方法:

static T? SafeGet<T>(List<T> list, int index) where T : struct 
{
    if (list == null || index < 0 || index >= list.Count)
    {
        return null;
    }

    return list[index];
}

static T SafeGetObj<T>(List<T> list, int index) where T : class
{
    if (list == null || index < 0 || index >= list.Count)
    {
        return null;
    }

    return list[index];
}
静态T?SafeGet(列表,int索引),其中T:struct
{
if(list==null | | index<0 | | index>=list.Count)
{
返回null;
}
返回列表[索引];
}
静态T SafeGetObj(列表,int索引),其中T:class
{
if(list==null | | index<0 | | index>=list.Count)
{
返回null;
}
返回列表[索引];
}
如果我尝试将这些方法组合成一个方法

static T SafeGetTest<T>(List<T> list, int index)
{
    if (list == null || index < 0 || index >= list.Count)
    {
        return null;
    }

    return list[index];
}
static T SafeGetTest(列表,int索引)
{
if(list==null | | index<0 | | index>=list.Count)
{
返回null;
}
返回列表[索引];
}
我得到一个编译错误:

无法将null转换为类型参数“T”,因为它可能是不可为null的值类型。考虑使用“默认(t)”代替.< /P> 但是我不想使用
default(t)
,因为在原语的情况下,
0
,这是
int
的默认值,是一个可能的实值,我需要将其与不可用的值区分开来

这些方法是否可以合并为一种方法


(作为记录,我正在使用.NET 3.0,虽然我对更现代的C#能做什么感兴趣,但我个人只能使用在3.0中工作的答案)

不完全是您想要的,但可能的解决方法是返回元组(或其他包装类):


你可以做一些相似但不同的事情。结果几乎相同。这依赖于重载和方法解析规则

private static T? SafeGetStruct<T>(IList<T> list, int index) where T : struct 
{
    if (list == null || index < 0 || index >= list.Count)
    {
        return null;
    }

    return list[index];
}

public static T SafeGet<T>(IList<T> list, int index) where T : class
{
    if (list == null || index < 0 || index >= list.Count)
    {
        return null;
    }

    return list[index];
}

public static int? SafeGet(IList<int> list, int index)
{
    return SafeGetStruct(list, index);
}
public static long? SafeGet(IList<long> list, int index)
{
    return SafeGetStruct(list, index);
}

etc...
私有静态T?SafeGetStruct(IList列表,int索引),其中T:struct
{
if(list==null | | index<0 | | index>=list.Count)
{
返回null;
}
返回列表[索引];
}
公共静态T SafeGet(IList列表,int索引),其中T:class
{
if(list==null | | index<0 | | index>=list.Count)
{
返回null;
}
返回列表[索引];
}
公共静态int?安全获取(IList列表,int索引)
{
返回SafeGetStruct(列表、索引);
}
公共静态长?安全获取(IList列表,int索引)
{
返回SafeGetStruct(列表、索引);
}
等
不太对吧?但它是有效的

然后,我会将整个过程封装在T4模板中,以减少代码编写量


编辑:我的强迫症让我使用IList而不是List。

简短的回答是否定的,这是不可能的。我能想到的最大原因是,如果你能说“我想在这个泛型方法中使用A或B”,那么编译就会变得复杂得多。编译器如何知道A和B可以以相同的方式使用?你所拥有的和它将得到的一样好


作为参考,请参见。

我认为您的需求的一个问题是,您试图使
T
同时成为一个结构体和该结构体的相应
可为空的
类型。因此,我将向类型签名添加第二个类型参数,使其:

static TResult SafeGet<TItem, TResult>(List<TItem> list, int index)
static TResult SafeGet(列表,int索引)
但仍然存在一个问题:对于要在源类型和结果类型之间表示的关系,没有通用约束,因此必须执行一些运行时检查

如果你愿意做上述工作,那么你可以做如下事情:

    static TResult SafeGet<TItem, TResult>(List<TItem> list, int index)
    {
        var typeArgumentsAreValid =
            // Either both are reference types and the same type 
            (!typeof (TItem).IsValueType && typeof (TItem) == typeof (TResult))
            // or one is a value type, the other is a generic type, in which case it must be
            || (typeof (TItem).IsValueType && typeof (TResult).IsGenericType
                // from the Nullable generic type definition
                && typeof (TResult).GetGenericTypeDefinition() == typeof (Nullable<>)
                // with the desired type argument.
                && typeof (TResult).GetGenericArguments()[0] == typeof(TItem)); 

        if (!typeArgumentsAreValid)
        {
            throw new InvalidOperationException();
        }

        var argumentsAreInvalid = list == null || index < 0 || index >= list.Count;

        if (typeof (TItem).IsValueType)
        {
            var nullableType = typeof (Nullable<>).MakeGenericType(typeof (TItem));

            if (argumentsAreInvalid)
            {
                return (TResult) Activator.CreateInstance(nullableType);
            }
            else
            {
                return (TResult) Activator.CreateInstance(nullableType, list[index]);
            }
        }
        else
        {
            if (argumentsAreInvalid)
            {
                return default(TResult);
            }
            else
            {
                return (TResult)(object) list[index];
            }
        }
    }
int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
    //Error handling here
}
else
{
    //value is a valid value here
}
static TResult SafeGet(列表,int索引)
{
var TypeArgumentsRevalid=
//两者都是引用类型且类型相同
(!typeof(TItem).IsValueType&&typeof(TItem)=typeof(TResult))
//或者一个是值类型,另一个是泛型类型,在这种情况下,它必须是
||(typeof(TItem).IsValueType&&typeof(TResult).IsGenericType
//从可为空的泛型类型定义
&&typeof(TResult).GetGenericTypeDefinition()==typeof(可为空)
//使用所需的类型参数。
&&typeof(TResult).GetGenericArguments()[0]==typeof(TItem));
如果(!TypeArgumentsRevalid)
{
抛出新的InvalidOperationException();
}
var argumentsAreInvalid=list==null | | index<0 | | index>=list.Count;
if(typeof(TItem).IsValueType)
{
var nullableType=typeof(Nullable).MakeGenericType(typeof(TItem));
if(参数REINVALID)
{
return(TResult)Activator.CreateInstance(nullableType);
}
其他的
{
return(TResult)Activator.CreateInstance(nullableType,list[index]);
}
}
其他的
{
if(参数REINVALID)
{
返回默认值(TResult);
}
其他的
{
返回(TResult)(对象)列表[索引];
}
}
}

这里还有一个选项您可能没有考虑过

public static bool TrySafeGet<T>(IList<T> list, int index, out T value)
{
    value = default(T);

    if (list == null || index < 0 || index >= list.Count)
    {    
        return false;
    }

    value = list[index];
    return true;
}

在顶部,它与许多其他类型集合的
TryXXX
兼容,甚至与转换/解析API兼容。从方法的名称也可以很明显地看出函数是什么,它“尝试”获取值,如果不能,则返回false。

我的解决方案是编写一个更好的
可为null的
。它没有那么优雅,主要是您不能使用
int?
,但它允许在不知道是类还是结构的情况下使用可空值

public sealed class MyNullable<T> {
   T mValue;
   bool mHasValue;

   public bool HasValue { get { return mHasValue; } }

   public MyNullable() {
   }

   public MyNullable(T pValue) {
      SetValue(pValue);
   }

   public void SetValue(T pValue) {
      mValue = pValue;
      mHasValue = true;
   }

   public T GetValueOrThrow() {
      if (!mHasValue)
         throw new InvalidOperationException("No value.");

      return mValue;
   }

   public void ClearValue() {
      mHasValue = false;
   }
}
公共密封类MyNullable{
T值;
布尔值;
公共bool HasValue{get{return mHasValue;}}
公共MyNullable(){
}
公共MyNullable(T pValue){
设置值(pValue);
}
公共无效设置值(T)
int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
    //Error handling here
}
else
{
    //value is a valid value here
}
public sealed class MyNullable<T> {
   T mValue;
   bool mHasValue;

   public bool HasValue { get { return mHasValue; } }

   public MyNullable() {
   }

   public MyNullable(T pValue) {
      SetValue(pValue);
   }

   public void SetValue(T pValue) {
      mValue = pValue;
      mHasValue = true;
   }

   public T GetValueOrThrow() {
      if (!mHasValue)
         throw new InvalidOperationException("No value.");

      return mValue;
   }

   public void ClearValue() {
      mHasValue = false;
   }
}