5

I have a generic method and need to guarantee that my method returns nullable<T>(ex. source is List<int> or List<int?> must returns int?) and I want to return null instead of default (ex. default of int is 0. in this case I want to return null). It can be possible to add constraints on type parameter(https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters) but whit this approach I must have 2 methods (one of them with struct constraint and another with class constraints)

my method is:

public static T? ElementOrNull<T>(List<T> source, int? index)
{
    if (source == null || index == null || source.Count <= index)
        return null;
    return source[index.Value];
}

I try Convert.ChangeType() and reflection(Activator.CreateInstance() and Activator.CreateInstance<T?>()) but I can't solve this problem.

Any and all feedback is appreciated. Thank you

Note: I checked all the questions about generics and casting of them in StackOverflow but they didn't help me

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
Mohsen Koorani
  • 241
  • 2
  • 10
  • 7
    What's wrong with two overloads? You won't notice it's two of them when using them, the right one will always be used. – Dejan Janjušević Nov 15 '21 at 08:16
  • The problem is those two methods can not be overload. because the inputs, output and the name of the method are the same – Mohsen Koorani Nov 15 '21 at 08:19
  • They're not the same. One is `T?` which is shorthand for `Nullable` and the other is `T`. Though they can't coexist because the name and input parameters are the same. – ProgrammingLlama Nov 15 '21 at 08:21
  • 3
    You have to use 2 methods because `int?` is actually `System.Nullable`. If `T` is `class` then it would have to return raw `T`, but if `T` is `struct` it would have to return `Nullable` which is wrapper. This cannot be expressed in dotnet's type system – JL0PD Nov 15 '21 at 08:21
  • 4
    If you allow `null` in the `List` (i.e. `List`), the two methods will be valid overloads. – Theraot Nov 15 '21 at 08:26
  • Does this answer your question? [Nullable reference types: How to specify "T?" type without constraining to class or struct](https://stackoverflow.com/questions/55975211/nullable-reference-types-how-to-specify-t-type-without-constraining-to-class) – GSerg Nov 15 '21 at 08:34
  • @GSerg Unfortunately, it didn't help, but "JL0PD" and "Theraot" solution worked for me – Mohsen Koorani Nov 15 '21 at 08:41
  • My C# is probably a little rusty and I haven't worked much with nullable types, but: (1) The title of your question doesn't make sense at all; (2) You don't give us any error message or undesired vs. desired behavior, so I don't even understand what your question is. Please explain better, and fix the title! Edit: Oh, The question mark in the title is part of the type, not part of a question! That makes more sense ;-). – Peter - Reinstate Monica Nov 15 '21 at 09:37

2 Answers2

3

Generics constraints are not part of the method signature, but compiler still needs to know if T is value or reference type to convert null to T and handle ? in T? correctly. But you can help the compiler using optional constrained parameters this way:

public class ReqStruct<T> where T : struct
{
}

public class ReqClass<T> where T : class
{
}

public static T? ElementOrNull<T>(List<T> source, int? index, ReqStruct<T> req = null) where T : struct
{
    if (source == null || index == null || source.Count <= index)
        return null;
    return source[index.Value];
}

public static T? ElementOrNull<T>(List<T> source, int? index, ReqClass<T> req = null) where T : class
{
    if (source == null || index == null || source.Count <= index)
        return null;
    return source[index.Value];
}

And since the parameters are optional the usage will look like:

var intOrNull = MethodHolder.ElementOrNull(new List<int> { 1 }, 5); // var is int?
var objOrNull = MethodHolder.ElementOrNull(new List<object>(), 5); // var is object?
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • I figured about this approach before but I can't change the signature of method Although this change be adding optional parameter – Mohsen Koorani Nov 15 '21 at 08:55
  • About this, see https://stackoverflow.com/questions/15367032 and https://codeblog.jonskeet.uk/2010/11/02/evil-code-overload-resolution-workaround/ – Orace Nov 15 '21 at 09:41
3

My preferred approach to this is to use a custom Option/Maybe type. I.e. a struct with a generic value and a bool to describe if the value is valid or not. I.e. never use null, always use a Maybe for optional parameters/return values:

public static Maybe<T> ElementOrNone<T>(List<T> source, Maybe<int> index)
{
    if (source == null || !index.HasValue || source.Count <= index.Value)
        return Maybe<T>.None;
    return source[index.Value];
}

While Nullable<T> and non-nullable references help to avoid null reference exceptions, I find them lacking when writing generic code, in part due to the problem you are describing.

Using a custom type have the advantage of consistent behavior for both value and reference types, and can allow for various extensions to make working with values easier, potentially even using linq query syntax to combine multiple values. A downside is that it is a custom type, so more to learn.

JonasH
  • 28,608
  • 2
  • 10
  • 23