Description
I want to propose a name resolution for generic types similar to F#'s, plus a relocation extension which I will discuss further.
Usually a separate non-generic class is solely exists to provide static factory methods for a generic type. With this feature there would be no need for that anymore and we can use the same class to define static factory methods and use it while we take advantage of type inference.
Consider this class:
class A<T> {
public static void M() { }
public static void M(T arg) { }
public static void M<U>() { }
public static void M<U>(U arg) { }
public static void M<U>(T arg) { }
public static void M<U>(T arg, U arg1) { }
public static void M<U, V>() { }
public static U F<U>(T arg) { }
public static A<U> G<U>(T arg) { }
}
To clear up when we choose a generic type with a simple name, here's the procedure:
- If a non-generic type with the name
A
exists in the scope we choose that. OK. - If multiple generic types with the name
A
exist in the scope we need diamond syntax to disambiguate. Fail. - If a single generic type with the name
A
exists in the scope we choose that. OK.
Note: Using diamond syntax to disambiguate generic types is discussed over at #2319.
This is current syntax:
A<int>.M(); // 1st
A<int>.M(1); // 2nd
A<int>.M<int>(); // 3rd
A<int>.M(1.0); // 4th
A<int>.M<double>(1); // 5th
A<int>.M(1, 2); // 6th
A<int>.M<int,int>(); // 7th
A<int>.M<int>(1); // ambiguous between 4th and 5th
And proposed one:
A.M<int>(); // 1st -- relocation
A.M(1); // 2nd
A.M<int;int>(); // 3rd -- relocation
A.M<int>(1.0); // 4th -- relocation
A.M<double>(1); // 5th
A.M(1, 2); // 6th
A.M<int;int,int>(); // 7th -- relocation
A.M<int>(1); // ambiguous between 4th and 5th
Relocation means that we specify the type's type arguments in method type arguments location, delimited with a semicolon. When we go down to nested types, we will also use semicolon to separate each type's type arguments.
This is specifically useful together with #952, #6739 and type invocation (#206):
enum class Option<T> {
Some(T),
None
}
var option = Some(5); // type invocation
Option<int> option = None(); // by target
var option = None<int>(); // relocation
This feature can be used with others like #6207 and #5429:
var result = A<int>.F<int>(5); // current syntax
var result = A.F<U:int>(1); // selective
int result = A.F<int>(1); // relocation, by target
A<int> result = A.G(5); // by target
One other use case is in using
directives. Currently we can define type aliases for any non-generic type like using Callback = System.Action;
and with #3993 also for generics using Cache<T> = Dictionary<string, T>;
. But we can't use using
for concrete types like:
using System.Action;
And if we could, there was no way to express generic types. Java doesn't have this problem because of type erasure.
So assuming Option<T>
type above we can write:
using My.Namespace.Option;
To simply bring that single type into the scope. Obviously, if there was a non-generic type named Option
either we can keep it or disambiguate it with diamond syntax.
Insignificant type parameters: If the type has no default generic type parameter (#6248) we can take advantage of this feature in methods e.g.
void F(A a) { }
// translates to
void F<T>(A<T> a) { }
Related: #1470