alx9r
alx9r

Reputation: 4263

Does the C# spec prohibit type inference based on default parameters?

In the following code I am trying to accomplish default type deduction in a factory pattern. I would like to call the factory method with an arbitrary combination of arguments and have the generic type parameters inferred from the default parameters for the omitted arguments. This attempt at that, however, results in error CS1750. CS1750 is mentioned in a handful of places in the roslyn repo, but reviewing those didn't reveal much about the cause in this case.

The full text of the error is as follows:

(parameter) S a = new S()

A value of type 'S' cannot be used as a default parameter because there are no standard conversions to type 'S' [TypeInference]csharp(CS1750)

public class T {}
public class T0  : T {}
public class T1  : T {}
// T2{} .. T9{}
public class T10 : T {}
public struct S<T> {}
public class X {} // X is a complex object strongly-typed with T1..T10
public static class C {
    public static X Factory<A,B/*,C..J*/>(
        S<T0> s = new S<T0>(), // this is fine
        S<A>  a = new S<T0>(), // ERROR: no standard conversions to type...
        S<B>  b = new S<T0>()  // ...'S<A>' [TypeInference]csharp(CS1750)
        //S<C> c,
        //...
        //S<J> j
    )
    where A : T
    where B : T
    =>throw new System.NotImplementedException();

    static void usage1() {
        /* I'd like to be able to omit an arbitrary portion
           of the factory arguments and have type inference use
           the default values.*/
        Factory(b : new S<T1>());
    }

    // simplified example
    public static void Foo<A>(A a)    {}
    public static void Bar<A>(A a=42) {} // ERROR: CS1750

    static void usage2() {
        Foo(42); // this is fine
    }
}

I have been trying to understand why CS1750 is raised. According to the 5th edition of the spec

15.6.2 Method Parameters...

The expression in a default-argument shall be one of the following: ...

  • an expression of the form new S() where S is a value type

new S<T0>() seem to meet this criterion. Indeed, the parameter declaration S<T0> s = new S<T0>() doesn't seem to raise an error.

Reading (admittedly without completely absorbing) the spec's type inference section it wasn't obvious why default values wouldn't be considered during type inference. The language in that section even seems to be careful to distinguish optional parameters with and without corresponding arguments. For example, in this sentence a missing optional argument is excluded as cause for inference failure:

12.6.3 Type Inference...

If ... there is a non-optional parameter with no corresponding argument, then inference immediately fails.

Rather than rule out default parameters, this seems more like a weak suggestion that type inference could be based on them.

  1. Why is the compiler trying to convert S<T0> to S<A> instead of inferring A to be T0?
  2. Does the spec prohibit type inference based on default parameters?

Upvotes: 3

Views: 182

Answers (1)

Rich N
Rich N

Reputation: 9485

The answer to your question 1, I think, is 'the compiler doesn't work that way'. The compiler expects, as I did in my comment, that the programmer provides a valid type for a default parameter based on the current type constraints. The compiler won't infer a generic parameter for you based on the type of a default parameter. Here the only constraints are that both A and T0 descend from T, so in general we can't expect S<A> a = new S<T0>() to work. I'm pretty sure that's what the error means.

Your question 2 is really 'could it work that way?', I think. I think it could theoretically, but there are some problems. Consider what happens if, in your example, someone makes the call C.Factory<T1, T1>(), thus explicitly setting A to be of type T1 without providing any parameters. Now we've got an error case when we try to assign new S<T0>() to our parameter a of type S<A> = S<T1>, since S<T0> is not assignable to S<T1>. How should the compiler handle that? Since A is only constrained to be a descendant of T, and Factory has an overload that takes no parameters, it looks like the call is valid. So any exception is going to be confusing at best, and probably arguably wrong. But we don't want to infer A to be T0 after the programmer has explicitly asked for a different type.

Upvotes: 1

Related Questions