Do not require type specification for constructors when the type is known.
Imagine this case:
Dictionary<string, List<int>> d = new Dictionary<string, List<int>>();
becomes:
Dictionary<string, List<int>> d = new();
Obviously, constructor arguments and object initialization would be allowed.
This might be a simple optimization given the above example, but there are longer type specifications out there.
But when applied to this:
XmlReader.Create(reader, new XmlReaderSettings{ IgnoreWhitespace = true });
it would become this:
XmlReader.Create(reader, new{ IgnoreWhitespace = true });
which is a lot more readable. In cases like this, the type is not really important, its properties are.
How is this readable? I see people complaining that
var d = new Dictionary<string, List<int>>();
is not readable (which I can't understand at all, as the full information is on the line -- it just avoids duplication). So the provided one wouldn't satisfy these guys, I guess.
But okay, now to your second point: If I just omit the possibility of having overloads (and how that should be handled) then I don't see how this is more readable at all. First it looks like an anonymous class instance, which is a false friend (and how this ambiguity should be handled is also unclear). Second, even if some constructor is used, we need to know the available method signatures for knowing which constructor to call. Therefore no one can read the code just from that one line.
So what are my doubts?
I don't like the fact that it clashes with anonymous objects. In the case you show with XmlReaderSettings
, there is no ambiguity, but what about this?
void Foo(Bar bar);
void Foo<T>(T arg);
class Bar { public int X { get; set; } }
If I write Foo(new { X = 42 })
, am I calling Foo(Bar)
or Foo<T>(T)
?
It seems that people want (this and other inference discussions) elements of Hindley-Milner, but not the total way it was implemented in F#.
@FlorianRappl,
This is not intended to please the ones that don't like var d = new Dictionary<string, List<int>>();
. They'll probably like this even less:
Dictionary<string, List<int>> d;
...
d = new();
I don't expect the compiler to read the developer's mind or beyond that.
This would only be possible if the type of the variable/parameter/field has a publicly accessible constructor with that signature.
This wouldn't be possible:
IDictionary<string, List<int>> d = new();
Overload resolution would be a bit harder but not impossible.
As for readability, I don't think it's worth than other cases we already have.
@thomaslevesque,
How would that work for Foo(new Bar{ X = 42 })
? It would be a call to void Foo(Bar bar)
, right?
T
in void Foo<T>(T arg)
is not a type, it's a type parameter. There are two implementation decisions that can be made here. Either the compiler doesn't try to bind to void Foo<T>(T arg)
, because it's missing information to do so, and just tries to bind to void Foo(Bar bar)
; or it doesn't try at all.
@dsaf, I don't know. It would be inferring the type of the parameter from the method definition. Not inferring the return type of anything.
@paulomorgado
T
in voidFoo<T>(T arg)
is not a type, it's a type parameter. There are two implementation decisions that can be made here. Either the compiler doesn't try to bind to voidFoo<T>(T arg)
, because it's missing information to do so, and just tries to bind to voidFoo(Bar bar);
or it doesn't try at all.
So you're saying that Foo(new { X = 42 })
would either call the Bar
overload or it wouldn't compile? Because both would be a breaking change, the code currently compiles and calls the T
overload.
How would that work for
Foo(new Bar{ X = 42 })
? It would be a call to void Foo(Bar bar), right?
Yes, but in this case you explicitly specify the type, so it's obvious you want to call the one that takes a Bar
. But if you use Foo(new { X = 42 })
, with your proposal both methods are valid candidates, when currently only the generic one is valid. This adds another case where the compiler must either fail with an ambiguous match error, or make a decision that might not be obvious to the developer.
T in void Foo
(T arg) is not a type, it's a type parameter. There are two implementation decisions that can be made here. Either the compiler doesn't try to bind to void Foo (T arg), because it's missing information to do so, and just tries to bind to void Foo(Bar bar); or it doesn't try at all.
But currently the compiler _does_ bind to Foo<T>(T arg)
if you pass an anonymous type... If you change that, you break a _lot_ of existing code.
Oops! Missed that one. I don' think we can get around that one.
Even if we could come up with an alternative to anonymous types (new ?{ X = 42 }
) that would still be a breaking change.
Something like new !{ X = 42 }
to indicate a non anonymous type would look ridiculous.
Wouldn't Anders Hejlsberg answer also apply constructors?
VOTE UP.
I think the problem can be solved by explicit ()
.
Because anonymous types cannot be constructed with new()
.
Examples:
void Foo(Bar bar);
void Foo<T>(T arg);
class Bar { public int X { get; set; } }
Foo(new { X = 1 }); // Foo<T>(T)
Foo(new() { X = 1 }); // Foo(Bar)
@AdamSpeight2008 Not really, as this is a new proposed syntax form (assuming we do what @neofuji suggests). It has no effect on overload resolution for existing code. However, the specification and implementation would likely be nearly as complex as the spec for the type of a lambda (where we have to essentially try every possibility to see what works).
I think it is also useful for setting values to properties.
Examples:
class Foo { public System.TimeSpan Time { get; set; } = new(1, 0, 0); }
var foo = new Foo { Time = new(1, 30, 0) };
foo.Time = new(2, 0, 0);
And return statements...
System.TimeSpan Foo()
{
return new(1, 0, 0);
}
System.TimeSpan Bar() => new(1, 30, 0);
System.TimeSpan Baz => new(2, 0, 0);
System.TimeSpan Hoge[int x] => new(x, 0, 0);
Isn't the type inference being inferred from the wrong direction? Eg Inference based on target type?
It is expected behavior on this issue.
Of course, it is incompatible with var-statements (var foo = new();
is impossible).
That's axiom I was pointing out and mentioned by Anders.
Every expression has a type.
This is not possible either,
T F<T>() { ... }
var res = F();
Same would be true for new()
: if the type is not inferable you will need to specify it.
@AdamSpeight2008 Now, lambdas are inferred from target type.
Array.TrueForAll(new[] { 1, 2, 3 }, a => a % 2 == 0);
@neofuji You sure? Func<Int, Bool>
implicitly casting to Predicate<Int>
?
@AdamSpeight2008 Yes, that code really works.
Lambdas do not specialize in Action<>
or Func<>
.
(Of course, Func<int, bool>
cannot cast to Predicate<int>
...)
Func<int, bool> x = a => a % 2 == 0;
Array.TrueForAll(new[] { 1, 2, 3 }, x); // CS1503
Do we consider the argument list when we are inferring the type?
void F(Foo foo) {}
void F(Bar bar) {}
class Foo { }
class Bar { public Bar(int a) {} }
F( new () ); // OK?
class Foo { public Foo(double a) {} }
class Bar { public Bar(int a) {} }
F( new (1.0) ); // OK?
Do we consider the object initializer when we are inferring the type?
class Foo { public int P { get; set; } }
class Bar { }
F( new () { P = 1 } ); // OK?
class Foo { public double P { get; set; } }
class Bar { public int P { get; set; } }
F( new () { P = 1.0 } ); // OK?
class Foo { public double P { set {} } }
class Bar { public double P { get; } }
F( new () { P = 1.0 } ); // OK?
Do we consider the collection initializer when we are inferring the type?
void F(Foo foo) {}
void F(List<int> list) {}
F( new () { 1, 2, 3 } ); // OK?
What about type parameters?
void F<T>(List<T> list) {}
F( new () { 1, 2, 3 } ); // OK?
In the above example, is there any mechanism to infer T
? rel. #1419, #1470, #2319
This is attractive as a counterpart to deconstruction, and we are considering it for a future version of C#.
I think it is useful for array initialization.
public int[] indices = new[256];
We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.
This corresponds to https://github.com/dotnet/csharplang/issues/100
Most helpful comment
This is attractive as a counterpart to deconstruction, and we are considering it for a future version of C#.