Discussion : Omitting new keyword #8719
Replies: 54 comments
-
My suggestion is omitting new keyword for value type. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
I've never liked the distinction between constructors and regular methods. It's a bit too late for this change, though. |
Beta Was this translation helpful? Give feedback.
-
@pdelvo |
Beta Was this translation helpful? Give feedback.
-
I believe this was proposed specifically for records as type invocation. |
Beta Was this translation helpful? Give feedback.
-
I have also seen this was proposed. For the sake of compatibility, On the other hand, now target-typed |
Beta Was this translation helpful? Give feedback.
-
I always missing value type construction syntax of C++ Point p(0,0,0); But I would never support omitting |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
I would much rather have target typed new, and do: Point p = new;
Point p = new();
Point p = new(0, 42); |
Beta Was this translation helpful? Give feedback.
-
@AustinBryan ICYMI, that's #100. |
Beta Was this translation helpful? Give feedback.
-
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. 😆 |
Beta Was this translation helpful? Give feedback.
-
Some more suggestions... Currently //Suggested |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Saving 12 keystrokes doesn't seem like a good benefit for making the language more difficult to read and reason about. |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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'. :) |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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") |
Beta Was this translation helpful? Give feedback.
-
@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..). |
Beta Was this translation helpful? Give feedback.
-
I think that omitting the |
Beta Was this translation helpful? Give feedback.
-
Dart is statically typed, yet you can remove the new key word. Just saying 🤷🏽♂️ |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
Not sure how that makes any difference in the result or inference of intent? |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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 |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
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) |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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.
Beta Was this translation helpful? Give feedback.
All reactions