Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not require type specification for constructors when the type is known #35

Closed
paulomorgado opened this issue Jan 19, 2015 · 24 comments
Closed

Comments

@paulomorgado
Copy link

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.

@FlorianRappl
Copy link

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?

  • Overload resolution
  • Readability (especially in combination with method calls)
  • Clash with anonymous objects

@thomaslevesque
Copy link
Member

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)?

@dsaf
Copy link

dsaf commented Jan 19, 2015

It seems that people want (this and other inference discussions) elements of Hindley-Milner, but not the total way it was implemented in F#.

@paulomorgado
Copy link
Author

@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.

@svick
Copy link
Contributor

svick commented Jan 19, 2015

@paulomorgado

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.

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.

@thomaslevesque
Copy link
Member

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.

@theoy theoy added this to the Unknown milestone Jan 19, 2015
@paulomorgado
Copy link
Author

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.

@AdamSpeight2008
Copy link
Contributor

Wouldn't Anders Hejlsberg answer also apply constructors?

@alrz
Copy link
Member

alrz commented Feb 10, 2016

VOTE UP.

@neofuji
Copy link

neofuji commented Mar 6, 2016

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)

@gafter
Copy link
Member

gafter commented Mar 6, 2016

@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).

@neofuji
Copy link

neofuji commented Mar 23, 2016

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);

@neofuji
Copy link

neofuji commented Mar 23, 2016

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);

@AdamSpeight2008
Copy link
Contributor

Isn't the type inference being inferred from the wrong direction? Eg Inference based on target type?

@neofuji
Copy link

neofuji commented Mar 23, 2016

It is expected behavior on this issue.
Of course, it is incompatible with var-statements (var foo = new(); is impossible).

@AdamSpeight2008
Copy link
Contributor

That's axiom I was pointing out and mentioned by Anders.

Every expression has a type.

@alrz
Copy link
Member

alrz commented Mar 23, 2016

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.

@neofuji
Copy link

neofuji commented Mar 24, 2016

@AdamSpeight2008 Now, lambdas are inferred from target type.

Array.TrueForAll(new[] { 1, 2, 3 }, a => a % 2 == 0);

@alrz
Copy link
Member

alrz commented May 16, 2016

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

@gafter gafter modified the milestones: 2.0 (Preview 3), 2.0 (RC) Jul 14, 2016
@jaredpar jaredpar modified the milestones: 2.0 (RC), 2.0 (RTM) Jul 18, 2016
@jcouv jcouv removed this from the 2.0 (RTM) milestone Aug 9, 2016
@MadsTorgersen
Copy link
Contributor

This is attractive as a counterpart to deconstruction, and we are considering it for a future version of C#.

@neofuji
Copy link

neofuji commented Aug 21, 2016

I think it is useful for array initialization.

public int[] indices = new[256];

@gafter
Copy link
Member

gafter commented Mar 20, 2017

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 dotnet/csharplang#100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests