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)

James McMahon
  • 48,506
  • 64
  • 207
  • 283
  • 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? – D Stanley Aug 31 '15 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(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`... – Ron Beyer Aug 31 '15 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. – James McMahon Aug 31 '15 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](https://en.wikipedia.org/wiki/Option_type)) but that doesn't seem to be an option in C#. – James McMahon Aug 31 '15 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?`. – D Stanley Aug 31 '15 at 16:59
  • @JamesMcMahon although `Option` is not part of the BCL, there are some good open source implementations out there. Have you considered using one of those? I too agree that this is the ideal course of action, the code just screams for it. – dcastro Aug 31 '15 at 17:31
  • This may be what you're looking for: http://stackoverflow.com/a/5738847/2100227 . – Eris Aug 31 '15 at 19:52
  • That's the kind of situation I miss from Rust: Non-nullable by default, and "null" is only emulated by more specific/meaningful types. I believe this is the case for other more recently designed languages too, but no personal experience on that. [Read more about how null can be evil](http://programmers.stackexchange.com/questions/12777/are-null-references-really-a-bad-thing) – This company is turning evil. Sep 01 '15 at 03:38

6 Answers6

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.

Ron Beyer
  • 11,003
  • 1
  • 19
  • 37
  • 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. – James McMahon Aug 31 '15 at 19:07
  • 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 Aug 31 '15 at 20:21
  • @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. – Ron Beyer Aug 31 '15 at 20:30
  • @RonBeyer: That still wouldn't fix the covariance issue, which can't be resolved with syntactic sugar. – supercat Aug 31 '15 at 20:32
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]);
    }
Me.Name
  • 12,259
  • 3
  • 31
  • 48
  • 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. – James McMahon Aug 31 '15 at 16:19
  • 1
    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#. – James McMahon Aug 31 '15 at 16:52
  • 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)` – Me.Name Aug 31 '15 at 17:08
  • Thanks for the edit. There is also this option (no pun intended) https://github.com/tejacques/Option. – James McMahon Aug 31 '15 at 17:13
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.

Aron
  • 15,464
  • 3
  • 31
  • 64
  • 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 Aug 31 '15 at 16:10
  • 1
    @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. – Ondrej Svejdar Aug 31 '15 at 16:12
  • 1
    @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. – Aron Aug 31 '15 at 16:12
  • Ugly, not sure of the long term benefit of such an approach but it does work and answer the question. – Greg Aug 31 '15 at 16:14
  • 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` :) – James McMahon Aug 31 '15 at 16:46
  • @JamesMcMahon made an edit. Hope it makes you happier. Application of the Façade pattern to the rescue! (STILL FUGLY THOUGH) – Aron Aug 31 '15 at 16:48
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.

Community
  • 1
  • 1
Becuzz
  • 6,846
  • 26
  • 39
  • 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. – James McMahon Aug 31 '15 at 16:21
  • 1
    @JamesMcMahon If you look at the [documentation for Nullable](https://msdn.microsoft.com/en-us/library/b3h38hb0%28v=vs.110%29.aspx) 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 Aug 31 '15 at 16:29
  • @Becuzz: I think the decision was a consequence of the (IMHO unfortunate) decision that a `Nullable` should box as either a `T` or a null, rather than boxing as a `Nullable`. Such a rule breaks down if it is possible to have a `Nullable` where `HasValue` is true but the held value is itself null. – supercat Aug 31 '15 at 20:27
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];
            }
        }
    }
Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104
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.

Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80