11

The following pair of functions attempt to replicate the null conditional operator available in C# 6.0:

public static TResult Bind<T, TResult>(this T obj, Func<T, TResult> func)
    where T : class
{
    return obj == null ? default(TResult) : func(obj);
}

public static TResult Bind<T, TResult>(this Nullable<T> obj, Func<T, TResult> func)
    where T : struct
{
    return obj.HasValue ? func(obj.Value) : default(TResult);
}

The first function is constrained to classes and for a String s allows me to write something like:

var x = s.Bind(a => a.Substring(1));

The second function is where I am running into trouble. For example, given a int? number I would like to write:

var y = number.Bind(a => a + 1);

However, this gives me the following error:

The call is ambiguous between the following methods or properties: 'BindingExtensions.Bind<T, TResult>(T, Func<T, TResult>)' and 'BindingExtensions.Bind<T, TResult>(T?, Func<T, TResult>)'

I'm guessing that this has something to do with the interplay between the type inference of the anonymous function and the method overload resolution. If I specify the type of a as int than it compiles just fine.

var y = number.Bind((int a) => a + 1);

However, this is clearly less than desirable. Can anyone tell me why the compiler thinks the above call to bind is ambiguous and/or offer a way to fix this? I know I could simply name the two functions differently, but what fun is that?

Micah Hahn
  • 123
  • 5
  • You could remove the constraint on the first one and you wouldn't need the second. Of course then the result of `number.Bind(a=>a+1)` for a `null` value would be `null` instead of 0. But that's the result I would expect anyway. – juharr Jul 27 '15 at 17:46
  • @juharr That would kind of defeat the purpose though. I expect the anonymous function to have an input that can never be null, which is why the second function operates on a Nullable but only passes a T to the anonymous function. – Micah Hahn Jul 27 '15 at 17:50
  • 1
    Your function wouldn't be passed a `null` because `obj == null` would be true for a `Nullable` with a `HasValue` of false. Of course that means your function has to expect a `Nullable` instead of an `int`, but that's what I would expect anyway. – juharr Jul 27 '15 at 17:54
  • @juharr I disagree, I would expect an `int` instead of a `int?` precisely because it can never be null. My inspiration for this comes from the functional idea of monads (which is why I named the function `Bind`). I view `Bind` as lifting the variable out of its nullable context if it can and operating on it. – Micah Hahn Jul 27 '15 at 17:58
  • Why, when the first one expects a reference type that could be null? – juharr Jul 27 '15 at 17:59
  • 4
    Type constraints are [ignored during overload resolution](http://stackoverflow.com/a/26039211/11683). – GSerg Jul 27 '15 at 18:00
  • You can fool the compiler by adding a third parameter to the first method: `, T dummyForTypeInference = null)`. Other than that there is no way since type constraints are not considered to be part of the method signature and thus aren't used during the part of the overload resolution that figures out that you have multiple matches. They are considered later, but by then it is too late. – Lasse V. Karlsen Jul 27 '15 at 18:01
  • By the way, you may be interested in using an optional type like I've created in the [CallMeMaybe](https://bitbucket.org/j2jensen/callmemaybe) library. It doesn't suffer from this problem because `Maybe<>` isn't constrained to `struct`s, so unlike with `Nullable<>`, a `Maybe<>` signature is considered by the compiler to be more specific than the other method signature. Also, `Maybe<>` solves the problem you're solving all by itself: `var y = number.Maybe().Select(i => i + 1).Nullable();` – StriplingWarrior Jul 27 '15 at 18:25

2 Answers2

2

Overloaded functions cannot be disambiguated by type constraints (see "Generic constraints, where T : struct and where T : class"). Any nullable type N satisfies N : T and N : Nullable<T>, required by the former and latter Bind definitions respectively. I am guessing that number is of type Nullable<int> or similar.

var x = s.Bind(a => a.Substring(1));

This is unambiguous because s is of type string and for all T not string : Nullable<T>, so only the first overload is acceptable.

var y = number.Bind(a => a + 1);

This is ambiguous because the type of a => a + 1 may be inferred as either Func<int?,int?> or Func<int,int>. If inferred as Func<int?,int?> the first overload applies, and if inferred as Func<int,int> the second overload applies.

var y = number.Bind((int a) => a + 1);

This is unambiguous if number is of type Nullable<int>, for example. For the first overload for all T not T : Nullable<int> and T : int, so it does not apply. For the second overload you just need T : int which is easily satisfied by T = int.

Community
  • 1
  • 1
erisco
  • 14,154
  • 2
  • 40
  • 45
  • 3
    `Nullable` is itself a struct isn't it? [link](https://msdn.microsoft.com/en-us/library/b3h38hb0(v=vs.110).aspx) So how could `int?` satisfy the class constraint? – Micah Hahn Jul 27 '15 at 17:52
  • @Micah Thank-you for pointing out this mistake. I have revised my explanation of the problem. – erisco Jul 27 '15 at 18:14
0

Try this:

    public static TResult Bind<T, TResult>(this T? obj, Func<T?, TResult> func)
    where T : struct
    {
        return obj.HasValue ? func(obj.Value) : default(TResult);
    }
lukiller
  • 1,107
  • 9
  • 12
  • 2
    this fix the issue, you need to explain why i think – Fredou Jul 27 '15 at 18:00
  • This actually works, but I'd really like to see the explanation. Thought I don't think the OP will be happy with passing a `Nullable` to the function. – juharr Jul 27 '15 at 18:00
  • 1
    Right, that would work, but see the comments I made on the question. The argument to func should be a `T` not a `T?` because it will never be null. – Micah Hahn Jul 27 '15 at 18:01
  • @MicahHahn If the argument will never be null then why odes it matter if the parameter is a `T` or a `T?` – D Stanley Jul 27 '15 at 18:07
  • The Func(T) delegate expects "the same" obj type T. And yes, it can be a null value, if "int? number = null", you can still use the Bind function, since obj.HasValue will return false. – lukiller Jul 27 '15 at 18:10
  • @DStanley With the example I gave for the anonymous function it might not matter ( `a => a + 1` ) but there would be plenty of cases that might require casting down from `T?` to `T` which seems unnecessary. But thanks for finding that duplicate, apparently I didn't dig hard enough. – Micah Hahn Jul 27 '15 at 18:13
  • @MicahHahn The compiler already lifts many functions from `T?` to `T` so casting _may_ not be necessary as much as you think. – D Stanley Jul 27 '15 at 18:48