11

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)

8
  • 2
    So... you're returning null when an index is out-of-bounds rather than letting the runtime throw an exception? Seems like that would hide many logic errors. What's the purpose of the method? Commented Aug 31, 2015 at 16:07
  • 2
    I don't think so, because if for example T is an int, T? would not be convertible to T. Even your first method, even though it compiles, has issues, if you tried writing something like int i = SafeGet<int>(myIntList, 0), you would get an error "Cannot implicitly convert type 'int?' to 'int'...". Maybe in your test you are using the var keyword, which infers int? instead of int... Commented Aug 31, 2015 at 16:08
  • @RonBeyer, yeah that is a good point and probably partially why you can't do this. For the record I would be using var for type inference. Commented Aug 31, 2015 at 16:24
  • 1
    @DStanley, I want to avoid using exceptions as part of standard control flow. I have nested arrays that I need to access and be able to chain access together in a reasonable way. Not having an element is expected and rather than having Count checks everywhere I wanted to encapsulate my checks in a single method. Ideally I'd be able to use Nullable to wrap reference types as well as value types (much like Option Types) but that doesn't seem to be an option in C#. Commented Aug 31, 2015 at 16:56
  • @JamesMcMahon Using var doesn't change anything - if the return type is int (because that's what T is) the compiler will infer that type, and you can't return an int?. Commented Aug 31, 2015 at 16:59

6 Answers 6

6

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Yeah I had thought of that, but decided it was a pain to juggle two variables to represent the state that a single Nullable variable can.
Better IMHO would be T TryGetValue(..., out isValid), since the latter pattern works with both type inference and covariance, while out T works with neither. Unfortunately, MS decided upon the bool TryGetValue(... out T value) pattern before either of those considerations became relevant.
@supercat C#6 almost had a good feature, called "Declaration Expressions", which would have let you write something like if (TrySafeGet(myIntList, 0, out var value)) which gets rid of the extra variable, but the feature was removed.
@RonBeyer: That still wouldn't fix the covariance issue, which can't be resolved with syntactic sugar.
5

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

4 Comments

I like this idea, unfortunately I am using Unity so I am stuck with .NET 3.0. Sorry I forgot to mention that, I'll add it to the question.
While I can't personally test it, I think your answer is the best solution for people not stuck in a medieval version of C#.
haha thanks, feel for you there. Hopefully you still get to use vs2015 none the less, you can compile to .net 3.0 while still using handy features such as ?. and $"" For the record added an example custom wrapper too. instead of == null, that would work with something like if(res.Success)
Thanks for the edit. There is also this option (no pun intended) github.com/tejacques/Option.
5

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.

6 Comments

I think this is actually more methods than the OP currently has. He has 2 working, and would like it to be 1 method.
@Katana314 - this overload is lengthy, but you don't have to be aware if it is struct or class when you're calling the method.
@Katana314 He said he wants to "combine" it into a single method. His pattern requires two different methods names. Mine has more methods, but a SINGLE method name through overloading. As I said in my opening , not what he wants, but give the same result.
Ugly, not sure of the long term benefit of such an approach but it does work and answer the question.
This is interesting. Being able to use the same method name is a definite plus but ultimate the code duplicate is what I dislike most about my current solution. You are totally right about IList :)
|
3

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.

3 Comments

That's kind of what I expected given my understand of how generics function in C#. What's strange to me is that you can't wrap a C# object in Nullable, only objects. Seems like a really strange design choice because I expected Nullable to work similarly to Option types in newer languages.
@JamesMcMahon If you look at the documentation for Nullable<T> you notice how T is constrained to be a struct (something that normally can't be null). I suspect this choice has something to do with using the stack vs. the heap for variables and garbage collection. It is also reminiscent of how C++ did things a long time ago when I last used it.
@Becuzz: I think the decision was a consequence of the (IMHO unfortunate) decision that a Nullable<T> should box as either a T or a null, rather than boxing as a Nullable<T>. Such a rule breaks down if it is possible to have a Nullable<T> where HasValue is true but the held value is itself null.
2

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

1 Comment

Really interesting answer. Ultimately I think this is too complex for my use case.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.