Reputation: 49629
Is it possible in a C# generic method to return either an object type or a Nullable type?
For instance, if I have a safe index accessor for a List
and I want to return a value that I can check later with either == null
or .HasValue()
.
I currently have the following two methods:
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];
}
If I try to combine the methods into a single method.
static T SafeGetTest<T>(List<T> list, int index)
{
if (list == null || index < 0 || index >= list.Count)
{
return null;
}
return list[index];
}
I get a compile error:
Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
But I don't want to use default(T)
because in the case of primitives, 0
, which is the default for an int
, is a possible real value that I need to distinguish from a not available value.
Is it possible for these methods be combined into a single method?
(For the record I am using .NET 3.0 and while I interested in what more modern C# can do I will personally only be able to use answers that work in 3.0)
Upvotes: 11
Views: 3467
Reputation: 13148
My solution to this is to write a better Nullable<T>
. It's not as elegant, mainly you can't use int?
, but it allows the use of a nullable value without knowing if it's a class or a struct.
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;
}
}
It may be tempting to write a GetValueOrDefault
method, but that is where you will hit the problem. To use this you will always have to check if the value is there or not first.
Upvotes: 0
Reputation: 29213
I think that one problem with your requirement is that you are trying to make T
be both a struct and the respective Nullable<>
type of that struct. So I would add second type parameter to the type signature, making it:
static TResult SafeGet<TItem, TResult>(List<TItem> list, int index)
But there's still one problem: there are no generic constraints for the relationships you want to express between the source type and the result type, so you'll have to perform some runtime checks.
If you're willing to do the above, then you could go for something like this:
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];
}
}
}
Upvotes: 2
Reputation: 11273
There is one more option here you may not have considered...
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;
}
Which lets you do things like this:
int value = 0;
if (!TrySafeGet(myIntList, 0, out value))
{
//Error handling here
}
else
{
//value is a valid value here
}
And on the top side, its compatible with the TryXXX
of many other type collections, and even the conversion/parsing API's. Its also very apparent of what the function is from the name of the method, it "tries" to get the value, and if it can't, it returns false.
Upvotes: 6
Reputation: 12544
Not precisely what you want, but a possible workaround would be to return a Tuple (or other wrapper 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 would always mean that no value could be obtained, the single tuple itself would mean a value (even if the value itself can be null).
In vs2015 you could use the ?.
notation when calling: var val = SafeGetObj(somedoublelist, 0)?.Item1;
Of course instead of a Tuple, you could create your own generic wrapper.
As stated, not exactly optimal, but it would be a workable work around, and have the added benefit of being able to see the difference between not a valid selection and a null element.
Example of a custom wrapper implementation:
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]);
}
Upvotes: 5
Reputation: 15772
You can do something similar, but different. The result is almost the same. This relies on overloading and method resolution rules.
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...
Not pretty right? But it works.
I would then wrap the whole thing up in a T4 template to reduce the amount of code writing.
EDIT: My OCD has made me use IList instead of List.
Upvotes: 5
Reputation: 6866
The short answer is no, it is not possible. The reason biggest reason I can think of being that if you were able to say "I want an A or a B in this generic method" it would become far more complicated to compile. How would the compiler know that A and B can both be used in the same way? What you have is about as good as it is going to get.
For reference, see this SO question and answer.
Upvotes: 3