Open gafter opened 7 years ago
@HaloFour the issue is where none of initializer elements have a type, like a lambda or a method group.
@HaloFour That is not target-typing though. What @alrz said; see also new[] { }
, new[0]
and new { null }
.
@alrz I'm on the fence about #414. I'm not asking to drop the new
keyword. I would just like to see the type inferred from the target regardless of whether we're talking about new()
, new { }
or new[]
.
I know the latter results in a different type, but I want the syntactic symmetry. Why wouldn't we go for that?
@jnm2
If I understand correctly, I think your proposal is similar to https://github.com/dotnet/csharplang/issues/877 in a sense that whenever we fail to infer a "best type" we should consider the target-type for the whole expression. So both of these could be valid:
class C1 {
void M(C2 a, C3 b, bool c) {
C1 x = c ? a : b;
C1[] z = new[] { a, b };
}
}
class C2 : C1 {}
class C3 : C1 {}
That is different from new()
because here we look at target-type constructors to infer the type, while in those cases we should rely on a failure in best-type algorithm. but I do think that makes sense to have both.
I scream for this feature every time I have to instantiate a Dictionary<,>.
@asyncawaitmvvm Amen, brother!
https://github.com/dotnet/csharplang/issues/877 also helps with new()
,
Size screenSize = isJurassic ? new(800, 600) : new(3840, 2160);
Otherwise, you just cannot use new()
as both operands of a ternary.
@Thaina But you can't use var
in certain situations. Here's a few:
// Format: List<(Dictionary<string book name, string author>, int copies)>
// Before:
public List<(Dictionary<string, string>, int)> MyBooks => myBooks ?? myBooks =
new List<(Dictionary<string, string>, int)>();
// After:
public List<(Dictionary<string, string>, int)> MyBooks => myBooks ?? myBooks = new();
// Before:
PrintBooks(new List<(Dictionary<string, string>, int)>());
// After:
PrintBooks(new);
// Before:
public List<(Dictionary<string, string>, int)> MyBooks = new List<(Dictionary<string, string>, int)>();
// After:
public List<(Dictionary<string, string>, int)> MyBooks = new();
@AustinBryan There was a proposal to let we use var
on class level. It was argued that it a lot harder to implement. But I think it worth to have consistent syntax
@Thaina
There was a proposal to let we use
var
on class level. It was argued that it a lot harder to implement. But I think it worth to have consistent syntax
You are welcome to your opinion, although said proposals are of the very few that seem to get immediately shot down and closed by the language team, e.g. https://github.com/dotnet/csharplang/issues/1484
@Thaina I doubt that will ever happen. It seems to be much harder to do, while new
would just be yet another compile trick that it can do before runtime. And even still, that would only clean up the last case. var
could only be used to declare variables, but the first two ways aren't declaring variables.
// var:
public var MyBooks => myBooks ?? myBooks = new List<(Dictionary<string, string>, int)>();
// new:
public List<(Dictionary<string, string>, int)> MyBooks => myBooks ?? myBooks = new();
That would be a personal preference, meanwhile, this:
// Before:
PrintBooks(new List<(Dictionary<string, string>, int)>());
// Var:
PrintBooks(var);
// New:
PrintBooks(new());
PrintBooks(new);
Doesn't even remotely make sense, so new()
will always be needed in that case, and it should be there to mirror PrintBooks(default)
and make the language more consistent.
If the object doesn't have parameters, should? new
without ()
work, instead of new()
? I would prefer that because it would better match default
.
If the object doesn't have parameters, should? new without () work, instead of new()? I would prefer that because it would better match default.
It would also match VB where invocation parentheses are optional where there's no arguments.
If new
is equivalent to new()
, it implies that new { A = 1 }
is equivalent to new() { A = 1 }
.
default
doesn't call anything, unlike new()
. Parentheses often imply that a piece of code will be executed.
@AustinBryan LDM is holding back on "naked" new
(without ()
) for now. That will be in the 2018-06-25 notes (sorry, not yet published).
Naked new
I think would create ambiguity. The following is already legal:
public class Foo {
public int A { get; set; }
public string B { get; set; }
}
public class C {
public void M() {
M(new { A = 123, B = "abc" });
}
public int M(Foo o) => 1;
public int M(object o) => 2;
}
If new
would then be considered to create Foo
instead of an anonymous type that would result in the wrong overload being selected.
This feature will save me so much typing that I don't care one iota about having to insert an extra two parentheses. And I definitely agree that (regardless of the possible ambiguity) the parentheses make it clear that this is an invocation rather than a simple assignment, as noted by @Ghost4Man .
M(new);
is not already legal, I think it can be considered for this feature. M(new { A = 2 });
is already legal and should keep working as it is: an anonymous object. I understand if the team decides not to make the former legal for symmetry reasons
From the design meeting notes:
Nullable value types
Without special treatment, they would only allow the constructors of nullable itself. Not very useful. Should they instead drive constructors of the underlying type?
S? s = new (){}
Conclusion
Yes
Does this mean that S? s = new();
and S? s = new S?();
would produce different results? That sounds confusing.
I actually prefer that inconsistency.
We already have "var" that enough for variables in methods. Isn't it better to add "var" into fields, properties?
public var IntValue => 5; public var ClassValue => ClassValue.Default; private readonly _structValue = new StructValue();
@NoofSaeidh Wrong issue? This is about implicitly-typed new
, not var
(for the latter see #1484).
@NoofSaeidh
void M(Dictionary<string, int> x) { }
M(new { { "a", 1 }, { "b", 2 } });
@ufcpp I hope the empty parenthesis are obligatory here.
M(new () { { "a", 1 }, { "b", 2 } });
I second @quinmars (and prefer new()
with no space, like other method calls).
@quinmars @jnm2
Per LDM notes the "naked" new is being held off.
https://github.com/dotnet/csharplang/issues/100#issuecomment-402862850
Given the ambiguity with anonymous types I doubt that will be reconsidered.
S? s = new (){}
Probably not the best example. With special treatment of nullable value types, that would never work - at least, not until https://github.com/dotnet/csharplang/issues/99 lands) because we filter out struct default constructors.
@svick
Considering that, assuming S
have another constructor S(int)
, the following:
S? s = new(43);
would call the S(int)
, otherwise, you'll need to wrap yet another new
to account for the nullable type.
S? s = new(new(43));
So here we reduce the noise by targeting the underlying type in case of a nullable type.
@SymmetryKeeper-V The same argument can be made about var
and other features, people can abuse the feature proposed here but they can also use it to improve the readability of their code.
I don't think that you can call the code you posted an alternative, it's expensive to maintain, inferior if you care about readability and I'm not even speaking about allocations and performance.
Saw the target typed new expressions in the .NET blog article here. In the example it seems that 'new' itself is redundant:
Point[] ps = { new (1, 4), new (3,-2), new (9, 5) }; // all Points
Why not like this:
Point[] ps = { (1, 4), (3,-2), (9, 5) }; // all Points
@Barsonax Because that would mean a new Tuple. This is confusing, and furthermore ambiguous if Tuple implicitly converts to Point.
Besides new is a useful indicator that a new object is being created (possibly on the heap). Whilst less relevant for structs, that's extremely important information for classes. Is 3 characters really too long?
Because that would mean a new Tuple. This is confusing, and furthermore ambiguous if Tuple implicitly converts to Point.
The type is known to be Point
because the variable is of that type. Nothing is ambiguous about that. Even if there is a implicit conversion from Tuple to Point it should use the constructor. If you think its confusing its because implicit conversions themselves can be confusing (there many ways to abuse those). Besides even with a implicit conversion from Tuple
to Point
it won't convert the array from a Tuple
array to a Point
array. It would simply result in a compile error.
Besides new is a useful indicator that a new object is being created (possibly on the heap). Whilst less relevant for structs, that's extremely important information for classes. Is 3 characters really too long?
new
in itself has nothing to do with the heap as that has to do with is it a struct or a class. If you write (1,4)
you are actually writing new ValueTuple<int,int>(1,4)
so there already cases where new
is omitted. Also because of the space its actually 4 character saved per element and as you can see in the example its significantly shorter.
If 'hidden' heap allocation is a concern consider enabling this syntax only for structs. Most of the use cases would be structs anyway.
On the contrary, if there is an implicit conversion from tuple to point this is currently a valid expression, so you will be breaking existing code.
So the only option is to specify that when an implicit conversion exists, the creation of a tuple wins. In which case it's impossible to tell locally the type of the object being created. You have to know whether an implicit conversion from tuple exists.
On the contrary, if there is an implicit conversion from tuple to point this is currently a valid expression, so you will be breaking existing code.
Foo[] fooarray = new[] { (1, 2) };
Won't compile even if Foo can be implicitly converted from
ValueTuple<int,int>
toFoo
. It can convert a single element but not a array of these elements.
Requiring the new
keyword to represent "executing a constructor" feels more right to me and easier to understand.
Seems it doesn't handle it consistently atm:
public class Point
{
public static implicit operator Point((int a, int b) tuple) => new Point();
public void array()
{
var foo1 = new Point[] { (1, 2), (3, -5) }; //Compiles
Point[] foo2 = { (1, 2), (3, -5) }; //Compiles
Point[] foo3 = new[] { (1, 2), (3, -5) }; //Error
}
}
So actually even today there ways to abuse the system and omit the new.
@Barsonax
Of course you could allow new [] which is not ambiguous, but not allow new Point[] which is. However that's even more confusing, and of course doesn't help with any other collection types.
@Barsonax There's no abusing the system going on. There's an implicit operator being used, and you are creating a new ValueTuple, which is converted to a new point.
I makes more sense if it would pick the constructor of Point instead of going through the implicit conversion:
public class Point
{
public static implicit operator Point((int a, int b) tuple) => new Point(tuple.a, tuple.b);
public Point(int p1, int p2)
{
}
public void array()
{
Point[] foo2 = { (1, 2), (3, -5) }; //Works but picks the implicit conversion..
}
}
That's a hidden performance drain which is possible in todays code.
@Barsonax But you can't change the behaviour of currently working code, so it's irrelevant what the ideal behaviour should have been
One could doubt if this would break anything in a real application though. Well unless you are trying to find a situation on purpose.
@Barsonax Perhaps. But to take the risk, the benefits must be great enough that this becomes worth it. And I personally think that the word new is a good indicator that a constructor is being used. As such I personally think this is not only not worth it, but actively damaging.
It also means that every single tuple declaration must be analyzed by the compiler to see if it's actually a new expression, and vice versa. This makes the compiler significantly more complex, as this can't be decided in the parsing stage. It would have to be disambiguated afterwards.
Well it might not be too bad I just hope the JIT recognizes this correctly and optimizes it. If it manages to optimize the method call and the tuple creation away and simply directly calls the constructor it should result in the exact same machine code. Usually the implicit converter body is very small and thus candidate to inlining which should enable this.
For complex cases it probably cannot optimize this but I suspect these to be very rare.
EDIT: well turns out it does not optimize this so just have to be wary when I see these kind of constructs in code.
It's basically the same, but you can avoid the pointer usage with Unsafe.As<TFrom, TTo>
:
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe implicit operator Vector3(ValueTuple<float, float, float> value)
{
return Unsafe.As<ValueTuple<float, float, float>, Vector3>(ref value);
}
It doesn't really matter, but the Jit emits an extra mov
instruction for your pointer version for whatever reason.
This will make plain text code search for constructor calls impossible without complex RegEx queries... Just saying.
@MeikTranel I don't find myself taking advantage of plain text searches for constructors today. It's too easy to Ctrl+T to the constructor of the type I want and then press Shift+F12 to find all references.
Ideally this should be the case yes, but for very large multi-solution workspaces this simply is not possible. I personally just don't see any benefit to this proposal that would be worth any kind of downside. I mean essentially we're talking about moving a type from the right to the left side... and making future constructors with a bunch of constructor bodies completely unreadable.
EDIT: It feels to me like the solution here solves the problem to generally - "newing up fields in a class should be made less verbose" seems to be the requirement. Changing all that is holy about C# newing is the proposed solution.
This will make plain text code search for constructor calls impossible without complex RegEx queries... Just saying.
var
makes plain text search for a type-name impossible. C'est la vie. Brevity is naturally at odds with explicitness. If that's a problem for your style of development, don't use the features that allow you to be briefer :)
and making future constructors with a bunch of constructor bodies completely unreadable.
Why is it unreadable?
Changing all that is holy about C# newing is the proposed solution.
Given the LDM doesn't think this is the case, perhaps you are imposing your own view on what is "holy" here...
Summary: Allow
Point p = new (x, y);
Shipped in preview in 16.7p1.
See also https://github.com/dotnet/roslyn/issues/35
Test plan: https://github.com/dotnet/roslyn/issues/28489