-
Notifications
You must be signed in to change notification settings - Fork 1k
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
Discussion : Omitting new keyword #350
Comments
My suggestion is omitting new keyword for value type. |
This makes it a lot harder to read code because you can never be sure if you are looking at a method call or a constructor call. I don't think this is worth a few saved keystrokes. |
I've never liked the distinction between constructors and regular methods. It's a bit too late for this change, though. |
@pdelvo |
I believe this was proposed specifically for records as type invocation. |
I have also seen this was proposed. For the sake of compatibility, On the other hand, now target-typed |
I always missing value type construction syntax of C++ Point p(0,0,0); But I would never support omitting |
Look into how Python uses the a = A() # executes __call__ on the class, which then calls __init__ on the instance
a() # executes __call__ on the instance Callable instances are used for attributes, memoization, decoration, etc... It may be useful to have C# callable instances in the future: class C : ICallable
{
public int Call()
{
...
}
public static M()
{
var c = new C();
var d = c(); // executes Call()
}
} for things like In Python, the confusion between class calling and instance calling is a matter of capitalization convention. In C#, this would be less clear and would suggest keeping the Other possible areas of confusion to think about:
string[] array = new string[2]; // creates array of length 2, default values
string[] array = new string[] { "A", "B" }; // creates populated array of length 2
string[] array = { "A" , "B" }; // creates populated array of length 2 (Non-)Interned strings: string s = new String(chars);
string s2 = new StringBuilder().Append("My").Append("Test").ToString(); Boxed value types. |
I would much rather have target typed new, and do: Point p = new;
Point p = new();
Point p = new(0, 42); |
@AustinBryan ICYMI, that's #100. |
Yes, well we both know this is actually the stack allocation semantics and not the "value type" semantics; but I'm just being pedantic about being semantic. 😆 |
Some more suggestions... Currently //Suggested |
How about a using statement syntax, as a complement to the static using statement, where you can explicitly bring in types from a namespace as callable like functions. That way you opt-in to the feature: using CSharpForMarkup;
using static new Xamarin.Forms; // static new syntax, can now call all Xamarin.Form type ctors without new keyword
using static Xamarin.Forms.LayoutOptions;
using static Xamarin.Forms.StackOrientation;
using static MyApp.Localization.Strings;
using static MyApp.Resources.Styles;
// ...
private View CreateSearchBar() =>
StackLayout
{
Orientation = Horizontal,
HorizontalOptions = FillAndExpand,
Padding = Thickness(0, 0, 0, 20),
Margin = Thickness(0),
Children = {
// parenthesis still optional, even without new keyword, using initializer syntax
Entry {
Placeholder = PartNumberPlaceholderText,
HorizontalOptions = FillAndExpand,
}.Bind(Entry.TextProperty, nameof(ViewModel.PartNumber)),
Button {
Style = ConfirmButtonStyle,
Text = GoButtonText,
}.Bind(Button.CommandProperty, nameof(ViewModel.SearchPartHistory))
}
}; I would prefer to be able to import an entire namespace, as it would get cumbersome to do so one type at a time for large libraries of types, e.g. Xamarin.Forms controls. |
Saving 12 keystrokes doesn't seem like a good benefit for making the language more difficult to read and reason about. |
I would say that I disagree with the premise of that statement. I find the resulting code easier to read and reason about, not harder. The new keyword causes syntactic clutter that is not ultimately useful and draws your attention away from the more important structures of the code. Also C# wouldn't be the first language that lets you skip the new keyword (like Dart) or where there wasn't a syntactic distinction between constructors and other functions to begin with (Python), and it's a positive thing in those languages, in my opinion. |
And clearly specifies what the code does. It's not clutter, it's a necessary operator. Removing
Those languages were designed with that in mind, it wasn't bolted on later. Not that it matters at all what other languages do. Different languages have different syntax. |
There's no fundamental or practical difference between a function that returns an object and a new expression that creates a new object. They're both "functions which return an object", and in this context, they're both "expressions which evaluate to an object instance". Thus, ultimately, the new keyword serves no actual purpose for the programmer in an expression tree. You could argue perhaps (I'm not an expert on the C# grammar or compiler architecture, so I wouldn't know) that the keyword serves an important (and perhaps critical) purpose for the compiler, but I disagree with the idea that the new keyword makes code easier to read or reason about.
Dart wasn't designed with an optional new keyword from the start. It was added later, to make object trees easier to read and less cluttered. It was a very popular change. |
No, one is a function that may return an object. The other is an allocation. It is an important distinction, behaviorally. Constructors don't return objects.
No, I argue that the keyword serves an important purpose for the reader. But it does also serve to disambiguate for the compiler as well given you could have a method and a type where the constructor has the same signature and there's no other way for the compiler to know what you intended. |
To clarify, I am speaking specifically in the context of an expression or as part of a larger expression, where the distinction is mostly irrelevant. A new expression is an expression that evaluates to a value. A function call is also an expression that evaluates to a value. Both of which involve a function being called. We could get into the weeds about allocation, null guarantees, and object identity differences, but in my opinion those are more or less akin to implementation details.
I would argue that allowing the existence of the new keyword to disambiguate between a function and a class of the same name was not a great outcome - I realize we can't change change that rule without breaking code, but it certainly isn't an argument against this feature. And, one of the reasons I've proposed that the developer would need to opt-in using a new kind of using syntax or keyword, is specifically to address this issue. If the developer opts-in, the compiler is then free to enforce identifier ambiguity rules more consistently in this case without breaking any existing code. It makes sense to enforce the same identifier/overload ambiguity rules as two functions named the same thing with the same arguments. For example a local function vs an imported static function with a static using statement, or two static functions from different static classes. If this happens, the programmer easily can use one of many methods to explicitly disambiguate their code: using an explicit namespace, using |
This would create a separate dialect of the language within the language. All because you're prefer to not see |
In languages like F# and Dart the keyword |
Despite word 'new' is quite short, it's still A WORD. Means "something which takes my attention to read, recognize and... discover it's just creation of object". It's annoying - THAT'S WHY "c-like syntax" won over pascal or basic languages. We already have too much (key)words in a program to waste time on simplistic things. What about some 'new-symbol'? Say, '$'. Then you write:
Note on last line how less chars we have to type and how clearer now it looks. |
@WrongBit, you wrote:
Replacing new with It's worth doing a bit of research into the way that reading actually works at a physiological and cognitive level. As a readers eye saccades across a line of text, the eye picks up quite a wide swath of text. For native English speakers, folks with very high reading speeds (say, 800-1200 wpm) will actually only glance at two or three points on each line of text. Those with more common reading speeds (say, 400-800 wpm) are likely to primarily look at longer words of text, relying on peripheral vision to pick up on shorter words. Looking individually at every word or symbol in turn is a regrettably common habit that limits people to reading in the 100-250 wpm range. The research I've read indicates that the effective difference in reading speed (including comprehension) of a sigil (say, "$") verses a short word ("new") is effectively zero. Worse, introducing a |
Eliminating the new keyword requirement everywhere is a non-starter, as it potentially breaks existing code. Introducing a new sigil to optionally replace new is also a non-starter, for reasons @theunrepentantgeek states - it's not an improvement in practice and more than likely worse. The version of this proposal that has potential, in my view, would be something akin to a One of the primary users of this feature would be people using MVU/Redux patterns and frameworks - i.e. immutable types and declarative data structures in C#. What I have to do today to work around this is to build up a library of public static functions which are just wrappers around constructors - then, I can use a using static directive to eliminate the cruft. But it's a lot of extra effort and maintenance: // Static Utility library:
using ThirdParty.UIFramework;
namespace UI.Utils {
public static class UIConstructorWrappers {
public static Button Button(
String text = null,
Action click = null) => new Button(text, click);
// etc...
// since I'm not the "owner" of the UI framework in this example,
// I have to build up my own library to wrap every type in the UI framework.
}
}
// My View Code:
using static UI.Strings;
using static UI.Utils.UIConstructorWrappers;
// What I'd like to do:
// Eliminate most of the library code above (unless the utility actually
// does something useful besides wrap a constructor), and replace with this:
// using new ThirdParty.UIFramework;
public static class MainPage {
public static View Render(MainPageState state, Dispatch dispatch) =>
StackLayout(
Button(text: Button1Label, click: dispatch.Action1),
Button(text: Button2Label, click: dispatch.Action2),
StackLayout(
orientation: Horizontal,
children: ViewList(
Entry(
placeholder: EntryPlaceholder,
text: state.EntryValue,
textChanged: dispatch.TextChangedAction
)
)
) // etc...
);
// etc...
} |
look at flutter code, Drop new keyword, It's awesome and much more clear: class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final wordPair = new WordPair.random();
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(title: Text('Welcome to Flutter')),
body: Center(child: Text(wordPair.asPascalCase)),
),
);
}
} |
@surfsky No it's seriously not clear that |
In Kotlin, method names start with lowerCases, Class names with UpperCases. That's how methods and constructors are differentiated. May be it is same in Dart. In C#, methods and other class members starts with UpperCase. So, it may seem confusing. |
It's not a big problem:
Drop-new-keyword is useful when creating a big-json-like object. |
@surfsky No, it is a problem With your reasoning, we need to read and understand that token before we know that it is the object and it actually create new object, or it is a function and it just return value. With the current approach we don't need to know at the first glance even what the word is, if it has parentheses without new, then it is function, if it is If there is anything that was |
@Thaina I like to think of constructors as a kind of subset of functions which always return a new value, where other functions may return a cached value or no value at all. Technically they are semantically different but in practice I wouldn't say the distinction is very useful. Even if it were, syntax highlighting in an editor would help distinguish the two. That being said, allowing constructors to be called using regular function syntax is a breaking change that requires per-file opt-in. |
I kind of understand and think so. But then again, having Actually I familiar with this kind of concept from javascript, in that universe the class itself is function and derivation came from prototyping. So any function could be used to be But that's the point. |
@Thaina I would argue that most of these primary use cases are ones in which it is not a high priority for the developer to know whether a given expression in the tree will result in a new value being allocated on the heap or not. Thus, although you are correct in pointing out that calling constructors using function call syntax obscures the fact that a new value is being allocated on the heap when the expression is executed, I would argue that for the most common use cases, that information is not relevant. At the end the of the expression evaluation, the result is the same: an object tree. Further, given that this feature MUST be opt-in (I would suggest on a per-file level, enabled with a new |
Couldn't you just define methods taht then return the new (or cached) value you want? i.e.: Scaffold Scaffold(...)
=> new Scaffold(...); Now you can just call |
I would be far more interested in something that supports in line XML or in line Json. That's probably the future I miss the most from VB. Maybe our own object literal syntax would be more appropriate. But just dropping the |
Yes that is the current workaround. Internally, I have a C# code generator and command line tool that takes an assembly and some filtering options and generates a static class with constructor wrappers over every type in the assembly that matches the filtering options. It has all the drawbacks you'd expect from a code-gen tool, however (e.g. maintenance costs, forward compatibility, code bloat, etc...). And I'd argue the need for a code generator to overcome something in the language is maybe a good sign that some streamlined syntax is needed in the language. |
I honestly disagree, and often feel the opposite. If the problem can easily be solved by a generator, why bloat the language? Indeed, that's the direction we're pushing further toward with SourceGenerators. |
Many language proposals are intended to be quality of life improvements and have workarounds. The existance of a workaround isn't disqualifying, and having to write a source code generator is a pretty extreme workaround, to be honest. I think most people wouldn't bother. |
Agreed. I don't expect most people to do this. I expect some people to do this and then provide libraries that "most people" can then reference to get these capabilities. |
As I said, this is not a great workaround for several reasons beyond how easily the code generation integrates into the build system (or the compiler in the case of C# source generators). Using this workaround requires careful management of using statements to avoid name collision with the wrapped types (I use an "N" prefix in the generated static methods, but it's ugly). Additionally, if the runtime JIT or AOT is unable to inline these wrappers, they result in additional function call overhead over plain new expressions. They are also code bloat, which is important on mobile and other constrained platforms. They also introduce a large number of new symbols in the assembly metadata. Depending on the size of the target assembly this can cause a slowdown in the C# editor and the build. Additionally, one important limitation of using a static method generator, particularly relevant in C# 9, is that you are unable to use an initializer. So, for example this workaround does not work for most collections, and in C# 9, init-only properties cannot be initialized. If there is a property which is not initialized by a constructor, then properly initializing an object is impossible without a helper method. |
I don't know what that means. Why would there be name collisions here, but not have name collisions if we implicitly allow you to drop the
Why would tehy be unable to inline the wrapper? It would literally just be a forwarding function.
I don't see why tooling cannot address that.
This should not cause a slowdown in the C# editor. We test out perf to hundreds of thousands of symbols without issue.
Why not? We're designing an entire feature around that concept. And it's more broadly applicable than just dropping 'new'. :) |
If you generate wrappers for constructors, you're introducing a new static method per constructor. With a new syntax, you would not need to generate a wrapper, so no additional symbols are generated.
There are a number of reasons a JIT or AOT engine would choose not to inline a function regardless of its size including various thresholds.
I don't see why a simple opt-in syntax improvement (already adopted by multiple languages outside of C#) that would eliminate the need for third party code generators, additional code shaking, more aggressive inlining, and which works well with object initializers, is not preferable to the status quo. Aside, perhaps, from a general resistance to any and all language proposals regardless of merit? I'm not saying it's as high priority as data classes and init-only properties in C# 9, but it certainly would be a welcome improvement in the same vein as the pattern matching improvements in C# 9 is all I'm saying.
You cannot use the C# object initializer syntax on values returned from a method call and I'm not aware of any language proposals that allow this. Further, I would not support such a feature for init-only properties as it would make init-only properties mutable at call-stack boundaries. |
A static method that does nothing but call another function or constructor is right at the top of the list of functions that can be inlined.
Making anything "opt-in" creates language dialects, and anything that creates a dialect needs to be a massive improvement to the language.
https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-20.md |
|
Other languages have a different threshold for the introduction of different, subtly incompatible, dialects. The C++ world, for example, seems to positively thrive on the idea that the syntax and behaviour of the language can change markedly depending on compiler flags. #halfjoking Python 3 was introduced as a breaking change in 2008 and so far it has taken that ecosystem 12 extremely painful years to migrate. Even though we have now seen the the last release of Python 2, the pain isn't over. The C# team has an extremely high threshold for introduction of language dialects - they have to provide extremely high amounts of value. I think that nullable reference types is the first, and quite possibly will be the last, given that NRT seeks to tackle the so called billion dollar problem. Remember as well that each additional flag doubles the number of dialects of the language - with just a dozen flags for "simple" features like this, you end up with over four thousand dialects of C#.
I don't think this is the case. The folks who hang out and response to proposals in this repo are doing so because they're passionately interested in advancing the C# language. They tend to be smart, articulate, passionate - and they seldom all agree. For any proposal to progress, it has to have significant value - every proposal starts at -100 points. As you'll see if you review the (long!) discussion above, there are major issues with removing the new keyword.
If you have a different solution to the problem, please suggest it. If your idea is good enough to be championed by one of the LDM, then it might even happen. |
I could see some variation of the functor proposal supporting this request (#95). Trying to use the type name as a function would result in a call to a static public class Foo {
public Foo(string value) { }
public static Foo Invoke(string value) => new Foo(value);
}
// you write:
var foo = Foo("Value");
// compiler translates into
var foo = Foo.Invoke("Value"); This is exactly how Scala works: val some = Some("Value")
// is actually
val some = Some.apply("Value") |
@theunrepentantgeek Having experience with this particular feature in other languages, I can say that I really miss not having this in C#. I think it's important and useful enough to consider as a quality of life improvement, especially as declarative expression trees are becoming more mainstream in the dotnet community (e.g. MAUI MVU, Fabulous). I see it as a feature that improves the overall ergonomics and readability of large C# expression trees, such as MVU or other code-based view functions. The Also, would it be considered less of a "dialect" if object initializers were dropped from the proposal? As in, the new So for example, this code already works in today's C#, and it does not result in an ambiguous reference error because static methods and type names have different lookup rules: // Foo.cs
namespace Foo
{
public class FooClass { }
public class FooClass2 { public FooClass2(int arg) {} }
// plus maybe 20-30 other classes in this namespace
public static class FooClassStatic {
public static FooClass FooClass() => new FooClass();
public static FooClass FooClass2(int arg) => new FooClass2(arg);
// plus maybe 20-30 other static methods in this static class
}
}
// Program.cs
using Foo;
using static Foo.FooClassStatic;
var fooList = new List<FooClass> {
FooClass(),
FooClass(),
FooClass2(12)
}; The new using statement would be equivalent to the above, but skips the need to write or generate that static method: namespace Foo
{
public class FooClass { }
public class FooClass2 { public FooClass2(int arg) {} }
// plus maybe 20-30 other classes in this namespace
}
namespace Foo2
{
public class Foo2Class {}
public class NotGenerated {}
}
using Foo;
// "generate" static methods for all public types in Foo with public constructors
using static new Foo;
// only "generate" a static method for a specific type
using static new Foo2.Foo2Class;
// later in a function body:
var fooList = new List<FooClass> { FooClass(), FooClass(), FooClass(), Foo2Class() };
// ERROR because we didn't import the entire Foo2 namespace, just Foo2Class.
// var x = NotGenerated(); That seems like less of a dialect of C# and more like a compiler directive to "generate" static methods of an anonymous static class that is only in scope for that .cs file (even if in reality the compiler ends up just calling the constructor directly instead of invoking an actual generated static method, unless the generated method is used as a delegate/action/etc..). |
I think that omitting the |
Dart is statically typed, yet you can remove the new key word. Just saying 🤷🏽♂️ |
Well, I don't agree with this design choice. And anyway C# made its decision since 2001, I think changing it (or even adding an option) will be confusing for no reason. |
Not sure how that makes any difference in the result or inference of intent? |
Agree with @agrapine, in practice when it comes to distinguishing class constructors and function cals, it doesn't really matter. They are both expressions with a value. Whether a function returns a value or a constructor expression evaluated to a value, a value must be created either way. If you're using the presence of a new keyword to "watch out" for allocations, then you will miss all the allocs hidden behind function calls anyway, and you'll get a lot of false positives from structs - you should be using a static analyzer for that instead. |
Hi guys! I just looked into MAUI and realized that a declarative UI developing is terrible with a readonly State<int> count = 0;
[Body]
View body() => new StackLayout
{
new Label("Welcome to .NET MAUI!"),
new Button(
() => $"You clicked {count} times.",
() => count.Value ++)
)
}; vs readonly State<int> count = 0;
[Body]
View body() => StackLayout
{
Label("Welcome to .NET MAUI!"),
Button(
() => $"You clicked {count} times.",
() => count.Value ++)
)
}; I use Flutter to develop mobile/web apps and it's very good with an optional |
LOL. I misread that and thought you said omitting the new keyword was terrible. Looking at them both, I actually prefer the first because it makes it clear where the new components are. But really, it's a moot point. You can't change the syntax so drastically without breaking backwards compatibility. And the supposed gain is so trivial as to be laughable. Especially when your argument is that the IDE will just display the omitted keyword. |
If you have a static factory class, you can write the kind of syntax you want to write without needing For example, if you add this when using Roslyn: using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; Then you can write code like this, which fits the MAUI / SwiftUI / etc. declarative style: CompilationUnit()
.WithMembers(
SingletonList<MemberDeclarationSyntax>(
GlobalStatement(
ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("Console"),
IdentifierName("WriteLine")))
.WithArgumentList(
ArgumentList(
SingletonSeparatedList<ArgumentSyntax>(
Argument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("Hello, World!"))))))))))
.NormalizeWhitespace() (Courtesy of https://roslynquoter.azurewebsites.net) |
There are lot situations where
new
keyword can be omitted.var p = new Point();
can be
var p = Point();
Keyword
new
will be required to resolve ambiguity, for example if Point() function and Point class are defined and for other such cases.Benefit is saved space, particularly with calling function and passing many new objects as arguments.
The text was updated successfully, but these errors were encountered: