在单个 C# 泛型方法中返回 nullable 和 null?
Returning nullable and null in single C# generic method?
是否可以在 C# 泛型方法中 return 对象类型或 Nullable 类型?
例如,如果我有一个 List
的安全索引访问器,并且我想 return 一个我可以稍后使用 == null
或 [=14= 检查的值].
我目前有以下两种方法:
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];
}
我遇到编译错误:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
但我不想使用 default(T)
,因为在基元的情况下,0
,这是 int
的默认值,是我可能的真实值需要与不可用值区分开来。
是否可以将这些方法合并为一个方法?
(郑重声明,我使用的是 .NET 3.0,虽然我对更现代的 C# 能做什么很感兴趣,但我个人只能使用在 3.0 中有效的答案)
不完全是你想要的,但一个可能的解决方法是 return 元组(或其他包装器 class):
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;
当然,您可以创建自己的通用包装器而不是元组。
如前所述,这不是最佳选择,但它是一个可行的变通办法,并且还有一个额外的好处,即能够看到无效选择和空元素之间的区别。
自定义包装器实现示例:
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]);
}
您可以做类似但不同的事情。结果几乎相同。这依赖于重载和方法解析规则。
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。
简短的回答是否定的,这是不可能的。我能想到的最大原因是,如果您能够说 "I want an A or a B in this generic method" ,编译起来会变得更加复杂。编译器如何知道 A 和 B 都可以以相同的方式使用?您所拥有的与即将获得的一样好。
参考 this SO question and answer。
我认为您的要求存在一个问题,即您试图使 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];
}
}
}
这里还有一个你可能没有考虑过的选项...
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;
}
它可以让你做这样的事情:
int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
//Error handling here
}
else
{
//value is a valid value here
}
最重要的是,它与许多其他类型集合的 TryXXX
兼容,甚至与 conversion/parsing API 兼容。从方法的名称中也可以很明显地看出函数是什么,它 "tries" 获取值,如果不能,则 returns false。
我的解决办法是写一个更好的Nullable<T>
。它不是那么优雅,主要是你不能使用 int?
,但它允许在不知道它是 class 还是结构的情况下使用可为 null 的值。
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
方法可能很诱人,但这就是您会遇到问题的地方。要使用它,您总是必须首先检查该值是否存在。
是否可以在 C# 泛型方法中 return 对象类型或 Nullable 类型?
例如,如果我有一个 List
的安全索引访问器,并且我想 return 一个我可以稍后使用 == null
或 [=14= 检查的值].
我目前有以下两种方法:
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];
}
我遇到编译错误:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
但我不想使用 default(T)
,因为在基元的情况下,0
,这是 int
的默认值,是我可能的真实值需要与不可用值区分开来。
是否可以将这些方法合并为一个方法?
(郑重声明,我使用的是 .NET 3.0,虽然我对更现代的 C# 能做什么很感兴趣,但我个人只能使用在 3.0 中有效的答案)
不完全是你想要的,但一个可能的解决方法是 return 元组(或其他包装器 class):
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;
当然,您可以创建自己的通用包装器而不是元组。
如前所述,这不是最佳选择,但它是一个可行的变通办法,并且还有一个额外的好处,即能够看到无效选择和空元素之间的区别。
自定义包装器实现示例:
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]);
}
您可以做类似但不同的事情。结果几乎相同。这依赖于重载和方法解析规则。
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。
简短的回答是否定的,这是不可能的。我能想到的最大原因是,如果您能够说 "I want an A or a B in this generic method" ,编译起来会变得更加复杂。编译器如何知道 A 和 B 都可以以相同的方式使用?您所拥有的与即将获得的一样好。
参考 this SO question and answer。
我认为您的要求存在一个问题,即您试图使 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];
}
}
}
这里还有一个你可能没有考虑过的选项...
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;
}
它可以让你做这样的事情:
int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
//Error handling here
}
else
{
//value is a valid value here
}
最重要的是,它与许多其他类型集合的 TryXXX
兼容,甚至与 conversion/parsing API 兼容。从方法的名称中也可以很明显地看出函数是什么,它 "tries" 获取值,如果不能,则 returns false。
我的解决办法是写一个更好的Nullable<T>
。它不是那么优雅,主要是你不能使用 int?
,但它允许在不知道它是 class 还是结构的情况下使用可为 null 的值。
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
方法可能很诱人,但这就是您会遇到问题的地方。要使用它,您总是必须首先检查该值是否存在。