Open gafter opened 7 years ago
there will be official guidelines and recommendations on when to prefer that vs. var in situations where both could be used.
The official guideline will be the same as var
: use whichever you think is healthiest for your codebase. If you are a person taht prefers var
and finds the repetition of the types that can be inferred redundant, use it. If you're a person that strongly feels they need to know the exact types of all their variables then don't use var
. if you're a it depends
person then use either as you see fit.
That's the guidance :)
--
To give you an idea: Roslyn-Compiler believes all variables should be typed, and so dissuades people from using var
in that part of the code. Roslyn-IDE believes the opposite. Types feel extremely redundant, and so so var
is encouraged.
The same will likely hold true for new(...)
.
@RUSshy The only issue I see with your examples is readonly ConcurrentDictionary<string, MySuperLongClass<string, int>> myDictionary = new ();
specifically the generic type arguments so instead of changing the world (or trying to) which isn't likely to ever happen the request should be about addressing pain points as opposed to inconsistencies within the language (even though I think it's important to do whenever feasible and needed) so imo a feature like typedef
can clean some issues around generics, tuples and such... it wouldn't give us type inference (at least not the kind you want/expect to have) on fields but on the bright side types would make more sense within our codebases and we may write more succinct code and repeat ourselves a lot less.
So instead of expressing it like this:
class MyProgram
{
readonly ConcurrentDictionary<string, MySuperLongClass<string, int>> myDictionary = new ();
readonly string myString = "myString";
readonly MyOtherLongClass myLongClass;
readonly TinyData myData;
}
With typedef
we might be able to express it like this:
// MyTypedef.cs
typedef ConcurrentDictionary<string, MySuperLongClass<string, int>> MyConcurrentDictionary;
// MyProgram.cs
class MyProgram
{
MyConcurrentDictionary myDictionary = new ();
string myString = "myString";
MyOtherLongClass myLongClass;
TinyData myData;
}
@eyalsk typedef
is proposed in #1300
@lcsondes I know but thanks.
@RUSshy So you want a feature where the IDE would have the ability to format fields like so? or at least a variation of this?
class MyProgram
{
ConcurrentDictionary<string, MySuperLongClass<string, int>> myDictionary = new ();
string myString = "myString";
MyOtherLongClass myLongClass;
TinyData myData;
}
I mean, to me, this would be quite frustrating to read.
p.s. it's offtopic so if this is the case you might want to create a suggestion at the Roslyn repo or through Visual Studio.
@RUSshy Rider use adornments which add visual annotations to the IDE much like CodeLens work in VS so yeah it's very different and as you said it wouldn't work because an adornment is only a visual effect which can't be used to transform the source.
Would it be allowed to omit the parentheses when there's an initializer, just like collection and object initializers today? Would it make it less readable because it's too similar to anonymous type construction?
Would it be allowed to omit the parentheses when there's an initializer,
No, that is explicitly not allowed. An argument-list must be provided to use target-typed-new.
Instead of
new() { 1, 2, 3 }
couldn‘t you allow just
{ 1, 2, 3 }
?
@daveyostcom Because using { }
without anything else is already legal: it introduces a lexical scope.
Also, C# is never going to remove the requirement of new
to create objects. It makes it clear to any reader that it creates something new, and it also disambiguates between calling a type's constructor vs. a member that shares the same name as a type in scope.
Would it be allowed to omit the parentheses when there's an initializer,
No, that is explicitly not allowed. An argument-list must be provided to use target-typed-new.
I found this unintuitive when I was playing with this feature. I expected this to work. But, if you're going to stick with this, please change the error message from something like "can't cast anonymous type to class Foo" to something like "targeted type new requires parentheses, even with an initializer" Just one anecdotal data point.
@mkane91301
please change the error message from something like "can't cast anonymous type to class Foo" to something like "targeted type new requires parentheses, even with an initializer"
I'm not sure that's possible (alrz may well correct me though). The problem is that the syntax new { ... }
specifically creates an instance of an anonymous type; the compiler has no way of knowing in which cases it's actually a malformed target-typed new expression.
I believe you're correct @Richiban. At least, that's my understanding as well 🙂.
@Richiban But maybe the compiler can detect the fields, and if match the declared fields of the target, emit an error like "Type mismatch: cannot assign an anonymous type to X. Did you mean to use a target-typed new (requires parentheses)?".
@ChayimFriedman2
That would be confusing/ambiguous given that the following is already legal today:
public class Person {
public string Name { get; set; }
}
public class C {
public void M(object o) { }
public void M(Person p) { }
public void M() {
M(new { Name = "Foo" });
}
}
@HaloFour
Legal, and doesn't match my criteria:
But maybe the compiler can detect the fields, and if match the declared fields of the target, emit an error like "Type mismatch: cannot assign an anonymous type to X. Did you mean to use a target-typed new (requires parentheses)?".
The target is object
, and as far as I know there isn't such thing object.Name
.
Type mismatch: cannot assign an anonymous type to X. Did you mean to use a target-typed new (requires parentheses)?
It seems like an analyzer and code fix would be a better suited tool for this type of warning. If there is ambiguity in which overload would be used or there is no overload and you are using an anonymous type, there could be an analyzer rule to warn you that you should be explicit with your types and possibly a code fix to add the type name for you.
I think @ChristopherHaws is right, but it will be nice to have this analyzer from Microsoft.
No it is not, there's just collision between it and anonymous type in syntax. You're invited to suggest better one.
No it is not, there's just collision between it and anonymous type in syntax. You're invited to suggest better one.
Maybe there's something I'm missing (likely there's something I'm missing) but if we take a step back, this might be simpler than it appears up close.
Remember, this is a case where the compiler is being asked to infer a developer's intent through context. So, whether the goal is to create a friendlier error message or to not require the parentheses, it's still ultimately about inferring whether the developer intends this as target-typed new or an anonymous class.
(Of course, it is a viable choice to punt and require the parentheses and give a confusing error message, but we're trying to do better here.)
Now let's review the fundamental difference between these two features. With an anonymous class, the type is INFERRED from the initialization expression. With target-typed new, the initialization expressed is simplified because we KNOW the type from the target.
So we know that target-typed new CANNOT work if the target is:
And we know that anonymous type initialization can ONLY work if the target it:
So there is no case where an initialization expression can be either target-typed or anonymous.
Or is there? Am I missing something?
Or is there? Am I missing something?
So we know that target-typed new CANNOT work if the target is:
- object
Or is there? Am I missing something?
So we know that target-typed new CANNOT work if the target is:
- object
Let me clarify: I am only talking about scenarios where there are no parentheses, which is the only case where it is claimed there is ambiguity between the two syntaxes. I'm proposing that there is no ambiguity between the existing use of new { ... } and the possible target-typed use of new { ... } without the parentheses. (Obviously, there is no ambiguity between new { ... } and new() { ... }.)
Or is there? Am I missing something?
The return type of a lamda expression.
DoSomething(() => new { X = 1 });
Even if you find rules to teach the compiler what to do in that case, for me as a human reader it'd be mucher easier to understand that line if there is only one possible explanation.
Or is there? Am I missing something?
The return type of a lamda expression.
DoSomething(() => new { X = 1 });
Even if you find rules to teach the compiler what to do in that case, for me as a human reader it'd be mucher easier to understand that line if there is only one possible explanation.
How is it possible for the return type of that lambda to be an anonymous type? What would the type of the parameter to DoSomething be?
private void DoSomething(Func<????> factory)
{
...
}
@mkane91301
private void DoSomething<T>(Func<T> factory)
{
...
}
@mkane91301
private void DoSomething<T>(Func<T> factory) { ... }
Yes, I need to add generic parameters to targets that target-typed new cannot work with. So, in this case it has to be an anonymous type.
T t=new T(); is valid though if T:new()
T t=new T(); is valid though if T:new()
There's no ambiguity in T t = new();
as an anonymous type can never satisfy the new
constraint and an anonymous type initializer is always new { ... }
and never new()
, so this is not an issue.
Yes, I need to add generic parameters to targets that target-typed new cannot work with. So, in this case it has to be an anonymous type.
That'd be to simple. Take for example:
void DoSomething<T>(T a, Func<T> func) {}
DoSomething(new Point1D(), () => new () { X = 2 });
DoSomething(new { X = 2 }, () => new { X = 2 });
There is both possible. I'm sure the LDT could find a formulation to cover all those cases. The most compelling argument against the naked new
was made by @HaloFour https://github.com/dotnet/csharplang/issues/100#issuecomment-402869715: It creates ambiguity in the overload resolution.
The most compelling argument against the naked
new
was made by @HaloFour #100 (comment): It creates ambiguity in the overload resolution.
And my argument is I would prefer the naked new and don't allow target-typed new AT ALL for arguments to overloaded constructors or methods, even when the compiler can resolve the call with no ambiguity. I feel that when there are overloads, using a target-typed new makes the code LESS readable because it's even more confusing to the reader than to the compiler as to which overload is intended.
@mkane91301
And my argument is I would prefer the naked new and don't allow target-typed new AT ALL for arguments to overloaded constructors or methods, even when the compiler can resolve the call with no ambiguity. I feel that when there are overloads, using a target-typed new makes the code LESS readable because it's even more confusing to the reader than to the compiler as to which overload is intended.
Therein lies the problem, calling an overloaded method with an anonymous type is already perfectly legal. That's why there must be syntax that clearly distinguishes a target-type new expression from an anonymous type expression.
@mkane91301
And my argument is I would prefer the naked new and don't allow target-typed new AT ALL for arguments to overloaded constructors or methods, even when the compiler can resolve the call with no ambiguity. I feel that when there are overloads, using a target-typed new makes the code LESS readable because it's even more confusing to the reader than to the compiler as to which overload is intended.
Therein lies the problem, calling an overloaded method with an anonymous type is already perfectly legal. That's why there must be syntax that clearly distinguishes a target-type new expression from an anonymous type expression.
And if target-typed new was not allowed for overloaded and generic arguments, simply because they make the code less readable, then there’s no ambiguity between a construct that’s forbidden and one that already exists so there’s no reason to require parentheses.
@mkane91301
Even if it's not ambiguous to the compiler it's still ambiguous and confusing to the reader. I would find it to be very strange if a target-typed new expression suddenly turned into an anonymous type expression by introducing an overload.
@mkane91301
Even if it's not ambiguous to the compiler it's still ambiguous and confusing to the reader. I would find it to be very strange if a target-typed new expression suddenly turned into an anonymous type expression by introducing an overload.
I agree with that. Personally, I would be fine if target-typed new was only supported for initializing fields and properties, which is the only case where you type the same type name twice in a row. I’d be fine with requiring the parentheses. But I think I’ve shown that it’s a small edge case where the compiler could not tell that leaving out the parentheses was a grammatical error for target-typed new and not an illegal cast from an anonymous type. I think it’s reasonable to ask for an improved error message, which was my original point.
I think it’s reasonable to ask for an improved error message, which was my original point
Definitely. However, improved error messages should be requested at dotnet/Roslyn, not csharplang. Thanks!
Playing with the preview, I've really enjoyed this feature, thank you.
However, I've missed it when initializing arrays. Are there plans to support target typed new for array types?
I realize I can use Implicitly Typed Array syntax, but this won't work when initializing an array with no initial elements, even if there is an explicit declaration of the target variable type.
It would be useful to have the ability to initialize them as such:
public (float, int, string)[] foo = new [2];
Are there plans to support target typed new for array types?
It does not appear that there is, from the proposed spec:
- The following kinds of types are not permitted as targets of the conversion
- [...]
- Array types: arrays need a special syntax to provide the length.
Arrays are also listed in the 2018-06-25 LDM meeting notes as an unconstructible type.
I agree it seems like arrays should be supported. Based on the wording from the spec, I think it'd be considered to be its own feature separate from C# 9's target-typed new.
It does not appear that there is, from the proposed spec:
Ah, I missed that document. Thank you for providing the info. I suppose that's that. Hopefully in the future 🤞
We discussed target-typing for array creation, but in many useful cases it is not trivial to figure out the target element type. For instance IEnumerable<short> array = new[] { 1 };
.
Here's the championed proposal: https://github.com/dotnet/csharplang/issues/2701
@jcouv If I understand that correctly that's for having new()
within new[]{/*here*/}
. This on the other hand is KnownType[] arr = new[5];
, is that covered?
@jcouv, understood, looks like there's some design decisions that need to be settled first. Thanks for the example and the link to the proposal.
If I understand that correctly that's for having new() within new[]{/here/}
@lcsondes it looks like both are covered in the linked proposal:
For the sake of completeness, I'd suggest to also make new[] a target-typed expression,
IEnumerable<KeyValuePair<string, string>> Headers = new[] { new("Foo", foo), new("Bar", bar), }
What's the plan for new[1]
? It doesn't seem to have shipped.
What's the plan for
new[1]
? It doesn't seem to have shipped.
What's the plan for
new[1]
? It doesn't seem to have shipped.See #2701.
Thank you, but that only seems to talk about target-typed expressions between the {}. Right now I think the compiler is expecting a ,
or ]
after new[
which would not need a change to cover the examples in #2701 but would need to change to cover new[123]
. (see also https://github.com/dotnet/csharplang/issues/100#issuecomment-666310166)
Is this secretly covered? It would be nice if we could get an explicit yes/no.
I'm not sure what you mean by 'secretly covered'
That I do not see a number within square brackets in any of the proposed target-typed new array examples.
That is the proposal I just linked.
All I'm trying to get is an explicit yes/no on whether that one covers new[1]
specifically, because all it talks about is new[]{new(1)}
.
That is the proposal I just linked.
I think it's something LDM could naturally include alongside that proposal, but unless I'm blind nothing in the #2701 suggests that it would enable something like int[] myArray = new[10];
as an equivalent alternative to to int[] myArray = new int[10];
(Which I believe is what @lcsondes is asking about.)
We can't give an explicit yes/no since it hasn't been designed yet.
I can say that it would definitely be part of the discussion. However, there's no guarantees that can be given for a future feature that hasn't even been brought to ldm to do.
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