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

时间:2015-08-31 15:46:40

标签: c# generics nullable

在C#泛型方法中是否可以返回对象类型或Nullable类型?

例如,如果我有List的安全索引访问者,并且我想返回一个值,我可以稍后使用== 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];
}

如果我尝试将这些方法组合成一个方法。

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

    return list[index];
}

我收到编译错误:

  

无法将null转换为类型参数&#39; T&#39;因为它可能是一个不可为空的值类型。考虑使用&#39;默认(T)&#39;代替。

但我不想使用default(T),因为在基元的情况下,0int的默认值,是我可能的真正价值需要区分不可用的值。

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

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

6 个答案:

答案 0 :(得分:6)

此处还有一个选项,您可能没有考虑过......

int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
    //Error handling here
}
else
{
    //value is a valid value here
}

这可以让你做到这样的事情:

TryXXX

在顶部,它与许多其他类型集合的$(function(){ var $window = $(window); var toggleImage = function toggleImage($el, src){ $el.attr('src',src); }; $window.on('resize',function(){ if ($window.width() < 768){ //mobile toggleImage($('#two_size_img'),'img@1x.png'); } else { //landscape tablet/desktop toggleImage($('#two_size_img'),'img@2x.png'); } }); });兼容,甚至与转换/解析API兼容。它的方法名称也很明显,它“尝试”获取值,如果不能,则返回false。

答案 1 :(得分:5)

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

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

        return Tuple.Create(list[index]);
    }

Null总是意味着不能获得任何值,单个元组本身就意味着一个值(即使值本身可以为null)。

在vs2015中,您可以在致电时使用?.表示法:var val = SafeGetObj(somedoublelist, 0)?.Item1; 当然,您可以创建自己的通用包装器而不是Tuple。

如上所述,并不完全是最优的,但它是一个可行的工作,并且具有能够看到不是有效选择和空元素之间的区别的额外好处。

自定义包装器实现的示例:

    struct IndexValue<T>
    {
        T value;
        public bool Succes;
        public T Value
        {
            get
            {
                if (Succes) return value;
                throw new Exception("Value could not be obtained");
            }
        }

        public IndexValue(T Value)
        {
            Succes = true;
            value = Value;
        }

        public static implicit operator T(IndexValue<T> v) { return v.Value; }
    }

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

        return new IndexValue<T>(list[index]);
    }

答案 2 :(得分:5)

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

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...

不太对劲?但它确实有效。

然后我会在T4模板中包装整个内容以减少代码编写量。

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

答案 3 :(得分:3)

简短的回答是否定的,这是不可能的。我能想到的最大原因是,如果你能说'#34;我想在这种通用方法中使用A或B&#34;编译会变得复杂得多。编译器如何知道A和B都可以以相同的方式使用?你所拥有的就是它将要获得的好处。

供参考,请参阅this SO question and answer

答案 4 :(得分:2)

我认为您的要求的一个问题是您正在尝试使T既是结构又是该结构的Nullable<>类型。所以我会在类型签名中添加第二个类型参数,使其成为:

static TResult SafeGet<TItem, TResult>(List<TItem> list, int index)

但仍然存在一个问题:对于要在源类型和结果类型之间表达的关系没有通用约束,因此您必须执行一些运行时检查。

如果你愿意做上述事情,那么你可以选择这样的事情:

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

答案 5 :(得分:0)

我的解决方案是写一个更好的Nullable<T>。它并不优雅,主要是你不能使用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;
   }
}

编写GetValueOrDefault方法可能很诱人,但这就是你遇到问题的地方。要使用它,您必须首先检查值是否存在。