Open gafter opened 7 years ago
var
makes plain text search for a type-name impossible.
Are we still talking about constructor call text search? In all cases you can CTRL+SHIFT+F SampleType(
for all constructor calls (not taking using aliases into account, but the 99% case is covered).
Local declarations:
var x = new SampleType();
or
SampleType x = new SampleType();
Constructor:
public ConstructorSampleType(string filePath)
{
Song = new FileInfo(filePath);
}
Local declarations:
var x = new SampleType();
or
SampleType x = new();
SampleType x = new();
is right where have to do RegEx's for search.
Constructor:
public ConstructorSampleType(string filePath)
{
Song = new(filePath);
}
and making future constructors with a bunch of constructor bodies completely unreadable.
Why is it unreadable?
My horror scenario would be`
public FileInfo SongA { get; }
public FileInfo SongB { get; }
public FileInfo SongC { get; }
public DirectoryInfo AlbumLocation { get; }
[LINESANDLINESANDLINESOFCODE]
public ConstructorSampleType(string A,string B,string C,string d)
{
if(String.IsNullOrEmpty(A))
throw new ArgumentException(nameof(A));
if(String.IsNullOrEmpty(B))
throw new ArgumentException(nameof(B));
if(String.IsNullOrEmpty(C))
throw new ArgumentException(nameof(C));
if(String.IsNullOrEmpty(d))
throw new ArgumentException(nameof(d));
Song = new(A);
SongB = new(B);
SongC = new(C);
AlbumLocation= new(d);
}
By the time AlbumLocation= new(d);
the average class is probably large enough that you don't have the property type on your screen anymore. Making these constructions look painfully similar.
Are we still talking about constructor call text search?
I'm talking about expecting the language to be designed in a way to be pleasant to be regex searched. That's a non-goal, as evidenced by numerous language features that make regex searching harder. As such, this argument doesn't really carry any weight here.
In all cases you can
In all cases, you'd likely be able to shift-f12 to find all references, and actually be able to have the IDE tell you where everything is :)
Again, it's not a goal to make things easy to find through regex searches. If that were the case, we wouldn't have done so many features from version to version that allow for more brevity at the cost of explicitness.
My horror scenario would be
var
) you don't have to use this. Feel free to just write the object creation type out explicitly. It's likely we'll have editorconfig options around this as well. So you can just say "in my projects i don't want to allow people using this construct at all, or i don't want them using the construct if the type isn't apparent". i.e. just like we provide that option for use var or explicit type
.
I know it's possible to restrict usage of these styles. I just don't think brevity for brevity's sake is worth enough that:
I know this is a philosophical difference here, but i have to voice my concern in regards to 'brevity > explicitness'. I do not inquire the LDM to act on it. I just feel we put brevity as argument in lots of issues without ever establishing a standard on what priority brevity actually has. I like brevity when it came to deduplicating the explicitness of TypeX x = new TypeX; in favor of var. I'm not a fan of writing off expliciteness completely. Just wanted to voice an opinion. :/
The local declaration scenario TypeX x = new TypeX();
is not the driving scenario in my mind. As @MeikTranel points out, the type will appear either on the left or on the right.
Target-typed new
allows to omit the type entirely from invocations AddCustomer(new())
and assignments customer = new();
. This is similar to null
or default
, which also get target-typed.
- Cannot search for constructor calls using simple non-regex search
Although I like the brevity, this particular argument of @MeikTranel alone, and the fact that I'd eventually have to search through code-bases littered with Foo x = new(4);
is enough reason for me to object to this proposal.
But I may be too old-school here, meaning I should rely more on IDE functionalities like searching for references than text-search. I still think that's not ideal when we have multiple constructors though.
I just don't think brevity for brevity's sake
It's not "brevity for brevity's sake". It's brevity-to-reduce-redundancy's sake.
code written on Stack Overflow in C# 8.X will be incompatible on a newing-up-an-instance level
I don't know what this means. What would it be incompatible? Thanks! :)
Cannot search for constructor calls using simple non-regex search
You keep coming back to this. But being regex-searchable isn't a goal of the language. If it was, we'd have designed it massively differently. In contrast, we have intentionally made design decisions around making things conducive to an IDE environment. For example, ordering linq clauses differently than standard sql in order to provide a better intellisense experience, etc. etc.
I like brevity when it came to deduplicating the explicitness of TypeX x = new TypeX; in favor of var.
But that's not the var
feature. For example, var
also allows var v = You.Have.No.Idea(what: type[this.is]);
It was always understood that this might hide information that previously had to be explicit in the language. And, the IDE has long understood this may not be desirable for some audiences. That's why people can choose features that enforce a level of explicitness they desire.
I'm not a fan of writing off expliciteness completely.
Do you ever use lambdas without an explicit parameter type? Do you ever use a let
clause in linq? Even if you don't use these, is there an objection for other users from using this? Or, is it understood that it's acceptable for people to use these features if it's conducive to their codebases?
without ever establishing a standard on what priority brevity actually has.
There is no standard. It comes down to what individual LDM members think is an appropriate balance for the language and the userbase. As with all these brevity features, the end control is with the user who can not use the feature when they don't want them (ideally with IDE tools to help contorl their desire for their codebases).
Based on the proposal (https://github.com/dotnet/csharplang/blob/master/proposals/target-typed-new.md) it looks like the argument list parenthesis are required when using an object or collection initializer. Is there a reason you couldn't just write:
Person person = new
{
FirstName = "John",
LastName = "Doe"
};
vs
Person person = new()
{
FirstName = "John",
LastName = "Doe"
};
The updated expression syntax i believe would be:
object_creation_expression
: 'new' type? '(' argument_list? ')' object_or_collection_initializer?
| 'new' type? object_or_collection_initializer
;
@ChristopherHaws
That syntax is already legal and creates an anonymous type. Introducing target typing there could lead to silent source breaking changes and confusion when it comes to overloads as you can currently pass such an expression to any parameter of type object
. The parenthesis disambiguates between anonymous type and target typing.
This works great for class members where you can't use var
but at least to me it seems that this could be covered by relaxing the rules for var
in classes so that they're allowed if you immediately initialize the member. No fancy new syntax and it solves the only scenario where this feature is a heavy winner. Code example:
class Blah
{
var x; // should still be an error, it's also pointless
var y = new Dictionary<string, int>(); // should work
}
Side note, C++ had this syntax: Type variable(param1, param2);
for ages and with recent language updates (since C++11) they're moving towards the C#-ish auto variable = Type{param1, param2};
syntax instead. source
This proposal is closer to C++'s universal initializers.
So:
Type variable = {param1, param2};
This allows the other use cases too:
public List<Error> ValidateInput() => new();
vector<error> validateInput() { return {}; }
By the time AlbumLocation= new(d); the average class is probably large enough that you don't have the property type on your screen anymore. Making these constructions look painfully similar.
Wait a minute. Shouldn't that be:
Song = new(songAFilePath);
SongB = new(songBFilePath);
SongC = new(songCFilePath);
AlbumLocation= new(albumDirectoryPath);
I'm far more concerned about this code having a, b, c, d
as the parameter names than I am about not knowing the variable type. As the caller of this function, I have no idea what I'm supposed to provide.
Once the names are fixed, the types of the fields can be inferred from the names. A, B, and C are something that holds a file path, and AlbumLocation is something that holds a directory path. Then knowing .NET and my application, I can infer that we're probably talking about FileInfo and DirectoryInfo.
Not a big fan of this proposal. It seems a bit too similar to anonymous types and value tuples. I think it would be unintuitive and confusing to new people.
The conflict with anonymous type constructor in the object initializer example posted above by @ChristopherHaws is especially worrying and inconsistent with normal constructors. The requirement for the parenthesis is cluncky IMHO (they appear redundant but are needed for disambiguation purposes only).
I think supporting generic type inference in constructors would result in much cleaner code in a large portion of the places where this new syntax would be used.
It's hard to argue against the use on field initializers though. Unless we supported 'var' fields, there is not much we can do there to remove the duplication. Having said that, again I think var
fields would solve most of the problems there and is the way to go.
What if I have below code in 3 different file.
File1.cs OtherType ot = null; ot = new(); SomeType st = null; st = new();
File2.cs OtherType ot = null; ot = new(); SomeType st = null; st = new();
File3.cs OtherType ot = null; ot = new(); SomeType st = null; st = new();
Let say in future I want to create factory method for initializing SomeType. How would I find where only SomeType is initializing?
Go to the constructor and click on "Find all references".
I want to create factory method for initializing SomeType. How would I find where only SomeType is initializing?
Create the factory method, mark the constructor as private, recompile. 🎉
You could even mark the constructor as public again afterwards.
Relying on grep is a lousy way to find code.
And it's not like it's viable in the current version of C# anyway - there are numerous cases that you can't grep like new T()
in a generic method
I agree with @RUSshy
That does seem like a straightforward addition, when I initially learned about var
I was surprised that you couldn't do var thing = new Thing();
in a class just because.
it'd rather have var/let in members than new()
I agree with you but we have discussed for such a long time that it is really difficult to implement to the point that the complication make it not worth to do. While the new()
are very cost effective
I still don't support new()
approach but just saying that we might not have high hope on this
If you haven't seen it already have a read of #3231 - in it, @gafter states clearly a number of things that have been widely known, but not necessarily widely appreciated.
In particular, he wrote
the language's current Opinion is that elements of APIs between program components should be explicit, intentional choices.
(This dates back to the very genesis of the language. For example, the need to explicitly mark members as virtual to allow them to be overridden, and for overrides to be explicitly marked as override.)
This opinion is why var for type members is a bad idea. Using type inference to define the APIs between program components would make the APIs between program components brittle and easily broken. It would also likely kill forward development of the language because any change to the type inference algorithm would become a breaking change.
Target typed new gives better than than 90% of the benefits of member var, with almost none of the detrimental consequences.
Taking the example from @jnm2, I can see a very common case where this particular feature wouldn't be very useful:
private readonly IDictionary<(Foo foo, Bar bar), IList<Baz>> bazByFooAndBar = new ???
I'm wondering if adding this feature without thinking of simplifying those kinds of cases as well would promote bad practices like exposing concrete classes instead of abstractions (similar to how the current difficulty of writing immutable classes in the language promotes the use of mutable classes everywhere). I wouldn't want to see worse code being pushed in the wild using brevity as an excuse again (we've seen this with read-only properties before, and immutable classes now).
Do we propose any way to help in such scenarios? Would this be acceptable to you?
private readonly IDictionary<(Foo foo, Bar bar), IList<Baz>> bazByFooAndBar = new Dictionary();
This would be a form of constructor type inference that infers the generic types based on the target type, and would be very closely related to this proposal (or could exist as a separate, simpler proposal on its own). From my perspective, most of the verbosity comes from writing long, repetitive generic types, and the above, coupled with parameter-based constructor generic type inference, would cover 99% such cases.
Why would it be beneficial to use IDictionary there instead of Dictionary? It's strictly worse on performance.
Ignore the private
part IMHO, it could be public
and your clients then shouldn't be concerned with how you're implementing a dictionary.
Exposing a public dictionary or idictionary wouldn't be somethign that would be a good idea in hte first place. i would not want other people to be able to mutate my state.
@CyrusNajmabadi you are missing the point there. Imagine it's an IImmutableCollection
in that case, same problem.
The whole point is that APIs shouldn't expose concrete implementations when proper abstractions exist. IList
, ICollection
, IDictionary
, IEnumerable
, you get the idea.
The proposal made here is unable to initialize any of these (it will only work for concrete types) and I worry people will just say "screw it" and expose the concrete versions to save time and boilerplate code. This exact phenomenon has happened with the constructs I mentioned (readonly
properties and immutable classes). It leads people away of the pit of success.
Why would it be beneficial to use IDictionary there instead of Dictionary? It's strictly worse on performance.
I'm not sure if I follow you here. What I'm suggesting is for the compiler to infer the type arguments of Dictionary<TKey, TValue>
when I call new Dictionary()
in that case. Are you suggesting that if I assign that to an IDictionary<TKey, TValue>
I'm somehow losing performance?
I'm not sure if I follow you here.
I mean: it's strictly worse to have this field typed as IDictionary versus dictoinary. Every call will be an interface dispatch. Things like iteration can't use the struct-enumerators that Dictionary exposes.
I don't see any benefit to doing what you're referring to.
The proposal made here is unable to initialize any of these
I wouldn't expect any real API to be exposing the direct (strongly typed) members that make up the internal implementation.
Imagine it's an IImmutableCollection in that case, same problem.
But you don't new
-up the immutable collections. You use the builders, and then you grab the immutable value fromthe builder. So i'm not sure how that's relevant in this case...
I mean: it's strictly worse to have this field typed as IDictionary versus dictoinary. Every call will be an interface dispatch. Things like iteration can't use the struct-enumerators that Dictionary exposes.
I honestly didn't know about those performance differences that you mention. Thanks for clarifying.
I wouldn't expect any real API to be exposing the direct (strongly typed) members that make up the internal implementation.
I don't know... in my experience it is quite common to expose a concrete type as it's interface as part of a public property. Simple mutable DTO objects with get-only ICollection<T>
properties come to mind but there are numerous other examples one could look up.
But you don't new-up the immutable collections. You use the builders, and then you grab the immutable value fromthe builder. So i'm not sure how that's relevant in this case...
Ok ok fair enough... change that to an IReadOnlyCollection
which has a normal constructor, or any custom interface that you want to expose.
I don't see any benefit to doing what you're referring to.
That's because there is none.
The fear of returning concrete types (which itself is a misleading term in the face of inheritance) comes from a misunderstanding of the expression "program to the interface, not the implementation".
People in the Java community confused the then new interface
keyword with the concept of public API or Application Programming Interface.
Programming to the implementation in Java, or C#, would be using reflection to access things that aren't in the public contract. Which is exactly what we end up doing when some (bleep) returns an interface
and we need access to a method on the public
interface (i.e. class API) of the object.
The whole point is that APIs shouldn't expose concrete implementations when proper abstractions exist.
IList
,ICollection
,IDictionary
,IEnumerable
, you get the idea.
That's a myth. Under normal circumstances there's no benefit in doing that. In the rare cases you need to change the implementation, you do so by...
Other than LINQ, there are very few places in the BCL where returning an interface occurs. And even when it does, it's often a mistake. (See GetEnumerator in List
This opinion is why var for type members is a bad idea.
Also, it's not possible.
You would have to rewrite most of the compiler to support type inference at the member level in the generalized case.
F# can do it because it's a fundamentally different design. And even then it has lots of restrictions such as no circular references between classes and file order is meaningful.
I don't see any benefit to doing what you're referring to.
That's because there is none.
The fear of returning concrete types (which itself is a misleading term in the face of inheritance) comes from a misunderstanding of the expression "program to the interface, not the implementation".
I don't want to derail this topic too much, but again, what do you mean by "in the face of inheritance"? The concrete implementation you return could very well be sealed
. Now you want to change the internal implementation to use something else and the only way to do that is with a breaking change to the API. If you were using an interface, you could replace the implementation with another instance, or decorate the original implementation with a decorator, without the caller ever knowing about it. That's a pretty huge benefit to exposing an abstraction.
Programming to the implementation in Java, or C#, would be using reflection to access things that aren't in the public contract.
That's one of the problems, not all. Again, as per above, there are clear benefits to not exposing concrete types.
Which is exactly what we end up doing when some (bleep) returns an
interface
and we need access to a method on thepublic
interface (i.e. class API) of the object.
The only times I personally had to resort to reflection were when the original abstraction itself was poorly conceived. The solution is not to return the concrete type in these cases, but to fix the abstraction.
I'm quite surprised by your responses to this. I never would have imagined such a topic would be controversial at all, but here we are.
The whole point is that APIs shouldn't expose concrete implementations when proper abstractions exist.
IList
,ICollection
,IDictionary
,IEnumerable
, you get the idea.That's a myth. Under normal circumstances there's no benefit in doing that. In the rare cases you need to change the implementation, you do so by...
- Just change the implementation of the class, leaving the public API unchanged.
Not possible when you don't own the concrete implementation.
- Subclass and override the altered behavior
Composition is better than inheritance and leads to a less coupled object graph. I'd prefer decorator or composite implementations to inheritance if I can. Exposing a concrete class doesn't allow that.
- Create a new method that returns the alternate type. (I've seen this in thr BCL when they returned an array when they needed an immutable list.)
That sounds terrible to me and reminds me of some win32 APIs with DoSomething
and DoSomething2
where the first is marked as obsolete. Leads to bad bloated interfaces and I'd only ever use this if there was no other alternative.
The concrete implementation you return could very well be
sealed
. Now you want to change the internal implementation to use something else and the only way to do that is with a breaking change to the API.
That's only an argument for not returning sealed types that you don't own.
And that makes returning an interface rather laughable because its effectively sealed as well. Even if you own it, you can't add methods to it without causing a breaking change.
Composition is better than inheritance and leads to a less coupled object graph.
Interface inheritance is still inheritance.
I never would have imagined such a topic would be controversial at all, but here we are.
It wasn't in .NET, but you're on the wrong side.
Read the .NET Framework Design Guidelines and you'll see they are very much against returning interfaces because of all the problems they saw in COM. Backwards compatibility and the ability to expand a return types API being high on the list.
Returning an interface by default wasn't even viable in the general case before the invention of extension methods. And even still, it's a bad idea from a backwards compatibility standpoint without DIM, which in turn isn't usable if you target .NET Standard 2.0.
Now I'm not saying you should never return an interface. There are places where it is the best option. But blindly making shadow interfaces for every class is just a waste of time.
Hm, I didn't think SomeType st = null; st = new();
would be allowed, only SomeType st = new();
. The proposal however does not mention such limitation, but it is rare to see a code example of this feature where the reader actually cannot tell what the type is.
As for the return/accept argument, I believed the guideline has forever been to accept most generic and return most specific. Many people already said what they thought the guidelines are, but maybe we should check them: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/guidelines-for-collections
❌ DO NOT use ArrayList or List
in public APIs. ❌ DO NOT use Hashtable or Dictionary<TKey,TValue> in public APIs. Public APIs should use IDictionary, IDictionary <TKey, TValue>, or a custom type implementing one or both of the interfaces.
(It does suggest returning Collection<T>
, ReadOnlyCollection<T>
or its subclasses or IEnumerable<T>
if needed). If there are arguments otherwise, maybe the guidelines should be updated. There is no performance discussion.
What you return is irrelevant to what you contain internally and how you instantiate it. If i have some API that needs to return an IDictionary, i'm still (internally) going to write either var v = new Dictionary<...>()
or Dictionary<...> v = new()
. I'll then just return v
which will be fine being returned through an IDictionary.
These are orthogonal concerns.
@RUSshy
Due to the way Roslyn is designed, adding support for var in type members would require a major rearchitecture of the compiler. This is because currently the compiler binds the signatures of all members, and then binds the bodies. Allowing type inference for the signatures would require such checks to be recursive, and avoid infinite loops.
This would a) make this feature extraordinarily expensive to implement. b) severely impact IDE performance.
In other words for a feature as painful to implement as this, the benefits would have to be absolutely overwhelming. You could probably implement all C# 9 features for the same price as just this.
I really struggle to believe this is worth it.
@RUSshy
Saying, "it is complex" or "we would have to rewrite many part of the compilers" is not valid argument
Yes it is. The amount of effort required is most definitely part of the equation in determining if a feature is worth implementing.
As for the return/accept argument, I believed the guideline has forever been to accept most generic and return most specific. Many people already said what they thought the guidelines are, but maybe we should check them:
There's more to it than just that. Remember that's just the summary of the rules, the explanation behind them is only in the printed book version. (Which is a problem in my opinion, but MS doesn't the rights to the commentary.)
You could probably implement all C# 9 features for the same price as just this.
I doubt it would be that cheap unless they made it so limited that it would be incredibly frustrating to use. From everything I've read, this would make nullable reference types look trivial. And that took several major versions to get the partial implementation we have now.
@RUSshy wrote
People say var for members is a bad idea, in the mean time many new and old languages adopted it, and they have 0 issues, people don't complain, it's the opposite, people love it
Citation needed. You say "many new and old languages", so a list of five should be easy.
@RUSshy wrote
People say var for members is a bad idea, in the mean time many new and old languages adopted it, and they have 0 issues, people don't complain, it's the opposite, people love it
Citation needed. You say "many new and old languages", so a list of five should be easy.
Fast think of me is F# JavaScript TypeScript Kotlin and GoLang
Is php count? I think $
itself is also like var
in some sense that we declare some name and it become variable
python in some sense also has invisible var
. Anything other than that need fun
is it count?
@RUSshy
if you can provide multiple arguments against that, i'd be happy to hear
It completely changes how member initialization works and forces the language to have to now deal with circular inferencing potentially across many parts of source in order to figure out those types. Locals don't have this problem because they cannot forward reference, fields can.
i still believe "it's hard" or it's "complex to implement" are not a valid ones
Yes they are. If a feature is difficult to implement then it needs to be that much more worth it to do so.
"It's easier to implement it that way" is how Java got abominations like
someObject.<TypeParameter>genericMethod()
despite their generic type syntax being similar to C# and C++: GenericType<TypeParameter>
. After the dot it was easier to parse that way™. So now their users have to remember when it's backwards and they committed to having this syntax forever. I don't like the idea that in a future C# sometimes the left-hand side is automatic (with var
) and sometimes it's the right-hand side (with new()
). Sometimes you can use one or the other but sometimes not, and never both. I (sarcastically) can't wait for the cheat sheets to be made.
I'd personally be happier if var-in-class only ever supported expressions where the type of RHS can be deduced without recursion (such as = new SomeType(...)
) if that's the price, even if this is more limited than target-typed new. We're already living with no var
in classes, relaxing restrictions would be a welcome change and consistent with how the language already looks and behaves. It would avoid fragmenting how one asks for automatic type deduction (are we now supposed to stop using var
if target-typed new
is available and use Something s = new(...)
for locals as the new best practice for consistency? So many questions).
I can (I hope incorrectly) foresee quite a few cases where target-typed new is unlikely to work but var
does, e.g., new(1,2)+3
so this could become the new USB-A of C# now that $@ and @$ got fixed. Does new()
or var
not work? Try the other one.
is it really where things should go for long term?
Yes. we've explicitly rejected this. It's not just that it's hard. It's that it literally undoes all of the design and architecture of our compiler, which was built to ensure many scenarios could be very very fast. A core part of this design is that top-level items can be declared and understand 100% without needing to know what a single expression means.
I'd personally be happier if var-in-class only ever supported expressions where the type of RHS can be deduced without recursion (such as = new SomeType(...))
That immediately requires recursion. Because to figure out waht SomeType means, we need to exercise the binding engine, which itself needs to understand the shape and structure of the top level symbols, which means that we need to know what the top level symbols are (including their types).
I'm honestly curious how that would even be spec'ed into the language. i.e. The rule for binding new T(arguments)
recursed both in defining how to bind X
and how to bind the arguments
. Each of those rules recurses into many other rules, which themselves recurse more and more.
relaxing restrictions would be a welcome change and consistent with how the language already looks and behaves.
No one is saying that they don't understand why people want this. What is being said is: this is not going to happen because the complexity is staggeringly high, and no one has a clue on how to do it efficiently without regressing pri0 scenarios for this compiler and language platform.
@CyrusNajmabadi Thanks. I unironically hope that if/when target-typed new gets shipped there will be official guidelines and recommendations on when to prefer that vs. var
in situations where both could be used.
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