在单个C#泛型方法中返回nullable和null?
在C#泛型方法中是否可以返回对象类型或可为空的类型 例如,如果我有一个用于在单个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) {
列表的安全索引访问器
,并且我想返回一个值,我可以稍后使用==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;
}
}