dotnet / csharplang

The official repo for the design of the C# programming language
11.61k stars 1.03k forks source link

[Proposal]: Compound assignment in object initializer and `with` expression #5176

Closed CyrusNajmabadi closed 1 week ago

CyrusNajmabadi commented 3 years ago

Compound assignment in object initializer and with expression

Summary

Allow compound assignments like so in an object initializer:

var timer = new DispatcherTimer {
    Interval = TimeSpan.FromSeconds(1d),
    Tick += (_, _) => { /*actual work*/ },
};

Or a with expression:

var newCounter = counter with {
    Value -= 1,
};

Motivation

It's not uncommon, especially in UI frameworks, to create objects that both have values assigned and need events hooked up as part of initialization. While object initializers addressed the first part with a nice shorthand syntax, the latter still requires additional statements to be made. This makes it impossible to simply create these sorts of objects as a simple declaration expression, negating their use from things like expression-bodied members, switch expressions, as well as just making things more verbose for such a simple concept.

The applies to more than just events though as objects created (esp. based off another object with with) may want their initialized values to be relative to a prior or default state.

Detailed design - Object initializer

The existing https://github.com/dotnet/csharplang/blob/main/spec/expressions.md#object-initializers will be updated to state:

member_initializer
-    : initializer_target '=' initializer_value
+    : initializer_target assignment_operator initializer_value
    ;

The spec language will be changed to:

If an initializer_target is followed by an equals ('=') sign, it can be followed by either an expression, an object initializer or a collection initializer. If it is followed by any other assignment operator it can only be followed by an expression.

If an initializer_target is followed by an equals ('=') sign it not possible for expressions within the object initializer to refer to the newly created object it is initializing. If it is followed by any other assignment operator, the new value will be created by reading the value from the new created object and then writing back into it.

A member initializer that specifies an expression after the assignment_operator is processed in the same way as an assignment to the target.

Detailed design - with expression

The existing with expression spec will be updated to state:

member_initializer
-    : identifier '=' expression
+    : identifier assignment_operator expression
    ;

The spec language will be changed to:

First, receiver's "clone" method (specified above) is invoked and its result is converted to the receiver's type. Then, each member_initializer is processed the same way as a corresponding assignment operation assignment to a field or property access of the result of the conversion. Assignments are processed in lexical order.

Design Questions/Notes/Meetings

Note: there is no concern that new X() { a += b } has meaning today (for example, as a collection initializer). That's because the spec mandates that a collection initializer's element_initializer is:

element_initializer
    : non_assignment_expression
    | '{' expression_list '}'
    ;

By requiring that all collection elements are non_assignment_expression, a += b is already disallowed as that is an assignment_expression.

--

There is an open question if this is needed. For example, users could support some of these scenarios doing something like so:

var timer = new DispatcherTimer {
    Interval = TimeSpan.FromSeconds(1d),
}.Init(t => t.Tick += (_, _) => { /*actual work*/ }),

That said, this would only work for non-init members, which seems unfortunate.

LDM Discussions

https://github.com/dotnet/csharplang/blob/main/meetings/2021/LDM-2021-09-20.md#object-initializer-event-hookup

wrexbe commented 3 years ago

I would use this feature. I want to know how it would handle property changes that trigger an event. What if something like this

var myText= new MyText{
    Text = "Hello"
}
myText.PropertyChanged += delegate {};

got refactored to

var myText = new MyText{
    PropertyChanged += delegate {},
    Text = "Hello"
}

before the event would not trigger, and after it would get triggered. Does the order matter? Would swapping them make the event not trigger?

var myText = new MyText{
    Text = "Hello",
    PropertyChanged += delegate {}
}

Should the language enforce the events get added last, or should auto refactoring tools try to add the events last, to avoid triggering them on accident?

jnm2 commented 3 years ago

That's an existing question that applies to assignments of properties. I would be very confused if the syntax did not desugar to a series of statements in the order that the members are initialized in syntax.

What if someone wants to rely on PropertyChanged getting called when Text is set, and blocking them doing it in their preferred order is just annoying them?

wrexbe commented 3 years ago

Yeah that makes sense. Just worried about someone moving them around to be ABC order. I would probably keep the event initializers at the bottom most of the time.

bartdesmet commented 3 years ago

Should we allow this for with { ... } expressions? I'm not sure.

While my scenario isn't about events, I recently encountered a case where the lack of compound assignment in with expressions sent me down an "expression-body-to-statement avalanche". The case was updating some deep record structure to increment a TotalRuntime accounting property of type TimeSpan with the Elapsed time obtained from some Stopwatch:

var newStats = oldStats with { /* update a bunch of properties here ... */ Elapsed += sw.Elapsed, /* ... and more here */ };
CyrusNajmabadi commented 3 years ago

Thanks @bartdesmet I'll definitely bring up the question if we want to support this in with as well as supporting compound assignment for more than just events.

jnm2 commented 3 years ago

While my scenario isn't about events, I recently encountered a case where the lack of compound assignment in with expressions sent me down an "expression-body-to-statement avalanche".

This has happened to me as well.

bernd5 commented 3 years ago

For me especially operator ??= would be very useful in with expressions.

TahirAhmadov commented 3 years ago

This would be very useful when constructing a "tree" of objects, like UI controls, but also anything else, where children are added inline:

this.Controls.Add(new Panel
{
  Controls = new List<Control>
  {
    new Button
    {
      Text = "Submit",
      Click += this.btnSubmit_Click,
    },
    new Button
    {
      Text = "Cancel",
      Click += this.btnCancel_Click,
    },
  }
});
TahirAhmadov commented 3 years ago

Question: the same field/property cannot be assigned twice, but should it be possible to attach 2+ handlers to the same event? Would it be by repeating the event name, or separating the handlers with commas or something like that?

var btn = new Button
{
  Click += this.btn_Click,
  Click += this.anyControl_Click,
// or
  Click += [this.btn_Click, this.anyControl_Click],
};
jnm2 commented 3 years ago

Click += new EventHandler(this.btn_Click) + new EventHandler(this.anyControl_Click) would probably fall out automatically.

najak3d commented 2 years ago

Is there a good reason to not simply allow you to call ANY member method of the newly created object (and treat it the same as if you called it after construction)?? After all, property setters are really just calling the "Property_Set(value)" method; so why not simply extend this capability to allow calling ANY class method for the instance being constructed?

So for the event handler registration example, the answer is clear -- just have two entries:

var btn = new Button()
{
    Clicked += EventHandler1,
    Clicked += EventHandler2,
    AnyClassMethod(args),
    AnyExtensionMethod(args)   
}

It simply means that every statement inside the brackets ({ }) is treated as though it were prefixed by "btn.".

This is the Simplest solution/rule, and offers the maximum benefit.

=== NOTE: This also helps those who don't want to be forced to make all Property Setters Public. For example, I prefer, for many instances, to only make Get() public, and for the Set, create a different method with "reason" tag:

public int MyProp { get; private set; } // Setter is PRIVATE

public void SetMyProp(int val, string reason)  // USE THIS METHOD TO SET the property
{
     // Here I can enable Logging, which will now include the "reason" without doing expensive reflection techniques.
}

This makes it very easy to find out "why the value was set" later on, simply by looking at Log output, and also makes it easier to set Breakpoints for specific "reason" values.

Using this technique, currently disables our ability to use the Object initializer, because my properties don't have public setters.

What I'd prefer to do is:

new MyClass()
{
   SetMyProp(value, "Construct")
}

Currently, we cannot use Object Initializers for this mode of API.

IMO, simply allow Object Initializer blocks to call ANY method on the instance, not just Property Setters.

CyrusNajmabadi commented 2 years ago

Is there a good reason to not simply allow you to call ANY member method of the newly created object

Yes. Methods imply state mutation as opposed to declarative construction. So I feel that it's much more natural for them to be after the instance read constructed.

Also, that syntax is already allowed and is used for collection initialization. So it would need to be a syntax that would not be ambiguous with that.

acaly commented 2 years ago

Methods imply state mutation as opposed to declarative construction.

This doesn't make sense to me. I agree that methods imply state mutation, but state mutaion is NOT opposed to declarative construction. Construction is really a kind of mutation in C#, because C# currently doesn't have a good mechanism to distinguish the two, until we have required init property and init-only methods. Also, although event registration should be seen as construction, a more general compound assignment is more like a state mutation. In any case, you shouldn't say something like "we don't want to add this because it is not initialization/construction".

CyrusNajmabadi commented 2 years ago

Construction is really a kind of mutation in C#, because C# currently doesn't have a good mechanism to distinguish the two

I disagree. I think we do, and part of that is not blurring the lines more by using methods in these scenarios.

najak3d commented 2 years ago

The long-term trend has been that programmers want simpler (more terse) syntax. Less typing accomplishes more (and thus less reading too).

For UI construction, some controls are written where the Children list is "Get-only" -- you can modify the contents of the list, but not "set the list" -- e.g. you can call AddChildren, or AddChild, but not "Children = new List()".

So UI composition that SHOULD work without writing a full suite of hackish extension methods is the following:

new Grid()
{
   Prop1 = propVal,
   Prop2 = propVal2,
   AddChildren(
      new Button()
     {
        Clicked += EventHandler,
        BindTo(binding),
        ///... more 
    },
    new Button()
    {
       // compose Button2 here
    }
   )  // end of AddChildren(..)
} 

If a developer thinks "methods don't belong in initialization", then that programmer can decide to not-call-methods in the intializer. But for the context where it is deemed grossly useful (e.g. UI composition) - allow it. Why not?

theunrepentantgeek commented 2 years ago

Why not?

Wrong question. Language features are tremendously expensive. The question needs to be Why?

And it needs a compelling answer, far more than Why not?

CyrusNajmabadi commented 2 years ago

The long-term trend has been that programmers want simpler (more terse) syntax.

This is not the long term trend at all. And you can see very popular and very verbose languages that show that.

Less typing accomplishes more (and thus less reading too).

This is not a pro. Clarity is what matters, not terseness.

For UI construction, some controls are written where the Children list is "Get-only" -- you can modify the contents of the list, but not "set the list" -- e.g. you can call AddChildren, or AddChild, but not "Children = new List()".

This already works in C# today. Just do this:

new Whatever
{
    Children = { ... }
}

This will add to the children list. No need to new it up.

If a developer thinks "methods don't belong in initialization", then that programmer can decide to not-call-methods in the intializer.

Or we can just not allow it at all if it's not going to be a good thing :)

--

note @najak3d please use normal github markdown markers around your code. e.g. ```c#. I've edited your posts to use them properly :)

CyrusNajmabadi commented 2 years ago

So UI composition that SHOULD work without writing a full suite of hackish extension methods is the following:

You can already do this in C# today with:

new Grid()
{
   Prop1 = propVal,
   Prop2 = propVal2,
   Children =
   {
      new Button()
      {
          Clicked += EventHandler, // will be supported by this proposal.
      },
      new Button()
      {
         // compose Button2 here
      }
   }  // end of Children
} 
theunrepentantgeek commented 2 years ago

Less typing accomplishes more (and thus less reading too).

The two aren't strongly correlated. Shorter means more opportunity for confusion (does the code you're reading do what you think it does).

It's so easy to go past concise to cryptic, particularly when you're familiar with something and your readers may not be.

Real world example:

What does SGTM mean?

Sounds Good To Me or Silently Giggling To Myself?

Assuming the wrong one once caused me some minor embarrassment.

najak3d commented 2 years ago

I am very impressed with the community here. This was my first day visiting these forums. I'm a 51 year old programmer of C# since 2003, and want to see this language "rule all" -- but it appears to me that C# has lost ground in the last 10 years, mainly due to Microsoft doing a piss-poor job of making Xamarin Forms competitive/good (they work worse than WPF). Thus we have competitors like Flutter taking market share from what would have normally been done in C#.

One area where C# is hobbled is in the area of UI construction, which gave birth to XAML, which generally sucks.

It's unfortunate that C# has required hackish full-suites of extension methods to achieve a notation that competes with Flutter. I think this functionality shouldn't require this much work.

Yes, I agree that too-terse/overloaded statements is bad programming. I tend to go the verbose route, not combining things into single lines (e.g. method(anotherMethod(args)); IMO is normally bad).

However, for UI composition, this domain/context really deserves better native support from the C# language itself.

It doesn't seem to be much of a stretch to simply say "any method can be called from the Object Initializer block" -- and for safety, an order could be enforced such that "Init-Only Properties must always be called first", then after that, anything goes.

This would be: (a) Terse, (b) Clear, and (c) enable UI composition naturally.

It simply works similar to how VB "with" statements used to work, in that all operations are done on the main source object. They of course all operate In-Order. It should be the equivalent of making all the same calls using a local variable. In the Object Initialization block, it would just work like a "with" statement. Very simple, concise, clear, and extremely useful for certain contexts.

For contexts where it's not appropriate, just don't use it. But in short, even if you use it "inappropriately", what's the harm? It works the same as if you just made all the same calls (in the same order) using a local variable assigned to the new object. So it just amounts so a bit of very useful syntactic sugar, that makes UI composition syntax "natural/built-in", rather than an obtuse hack.

I think you are all awesome. Thank you for your attention, and feedback. This has been very encouraging to witness this energy , genius, and tone of the discussions. Kudos to the whole group of you.

acaly commented 2 years ago

I disagree. I think we do

Then what is the reason to allow general compound assignment, instead of just subscribing events, in object initializer? As I said, I agree that event subscription is important in object initializer, but I don't think allowing it for fields and property is anywhere better than calling methods, at least based on your initialization (or construction) vs mutation logic.

If I understand correctly,

var item = new Item()
{
  Value += 1,
};

does not distinguish whether the modification of Value is inialization or mutation. If you think you do, then the new syntax should only support init-only properties, not all set properties or fields. I know those are for back-compatibility. But that is exactly the problem here. Because of the back-compatibility, C# cannot have a clear difference between initialization and mutation from the callee side, or it will break old callers from modifying fields that is a mutation, not initialization.

Another reason I can think of why you say you do is that you think any modification of a field using compound assignment in object initializer can be seen as initialization instead of mutation. If that is the case, you are defining what is initialization/mutation by where the code is written, and that also means whatever we add to object initializer (e.g. method calls) will also be considered as initialization, which should be fine according to your initialization/mutation standard.

najak3d commented 2 years ago

It seems to me that "immutability" is currently enforced only by "readonly" modifiers or using "init" instead of "set" for properties.

Restriction: Readonly method - cannot use Init block -- all values must be set using a coded constructor.

Too Loose: Init methods - currently Order-of-ops in Init Block do NOT enforce "init methods must be called first". This seems unfortunate, because in a small way, this does not really enforce immutability, at least during the Init block.

As it currently stands though, the Init Block simply runs everything "in order" as you wrote it. That's pretty simple. So if we want to enforce "immutability" via Init Blocks, then we need to either:

  1. Invent new notation (e.g. "InitFirst") to operate as Property Setter, which enforces order within Init blocks.
  2. OR: Treat it as compiler error where an "Init only" setter is called AFTER anything except non-Init Properties.
  3. OR: Simply make sure "init only" setters are called before any Non-Property Setter. So method calls must come after calls to "Init Only" property setters. (I'd vote for # 3, so that we have no backward compatibility issues.)

So in this simple/sensible fashion, C#11 Init-Blocks can offer better "immutability" support, while also adding very useful syntax for nested composite construction (e.g. UI's). Adds magnificent benefit, without confusion or downside.

CyrusNajmabadi commented 2 years ago

It's unfortunate that C# has required hackish full-suites of extension methods to achieve

this domain/context really deserves better native support from the C# language itself.

You are being completely arbitrary. You reject one 'native' c# feature for being 'hackish', but then want some other features to do the same thing.

Extension methods are native. They're part of the language. Avoiding features that have been around for 15 years and are widespread and fully embraced by the ecosystem just because you don't like them is not going to motivate is to create something new.

najak3d commented 2 years ago

You are being completely arbitrary. You reject one 'native' c# feature for being 'hackish', but then want some other features to do the same thing.

Extension methods are native. They're part of the language. Avoiding features that have been around for 15 years and are widespread and fully embraced by the ecosystem just because you don't like them is not going to motivate is to create something new.

The NATURE of these extensions is hackish. For example, Button already has a "Text" property, but because it does not "return Button", we cannot use it! Therefore we have to create a NEW "Text()" method extension to use in it's place... to do the same thing, only it returns Button."

So it forces 100% replacement of all existing methods/properties, to support this syntax. But if the Object-Init syntax were simply a bit more functional -- then we wouldn't need to use all of these hacked extension methods.

Extension methods to add functionality is a good idea. And I'm glad they are available for scenarios like we are doing now -- because they allow us to "hack C# language" to essentially create a notation not currently supported by C# (but should be supported, IMO).

Extensions that are awesome are ones that extend collections to give you a "Count()" method for an IEnumerable, or the various other Linq extensions. Those are all nice, but are ADDING functionality.

In our case, our methods are 90% NOT adding functionality, but are simply "redoing existing properties/methods" to make up for an inadequacy of the C# language itself (which is supported by Flutter/Dart and others). That's why it's a hack, vs. the many other uses of Extensions, where it's not a hack.

IMO, in a way, though, most usages of Extensions are a bit hackish in nature. In short, remove the "using ExtensionsNamespace" and your code suddenly breaks. So it's a bit hackish/weird, and in many cases have been overused by some. The best we can say about Extensions is that they are VERY helpful in overcoming awkwardness that would otherwise result without them, and so they are "good/useful" and we're glad they exist.

If you can write code that relies upon 1000 Extension methods, vs. writing code that needs almost ZERO extension methods, using nearly the same syntax -- it's preferred, by far, to have the code non-reliant upon these extensions.

In short, I'm not calling the "syntax for UI composition" hackish; but I am calling the current method of using 1000's of extensions to make this syntax possible, "hackish", because it is. But I'm glad this hack works, because at least we do enjoy the benefits of C#-markup-composition syntax. It's just unfortunate that this syntax requires so many awkward extensions to make it possible.

HaloFour commented 2 years ago

@najak3d

That's not a case of extension methods being hackish, that's a case of using extension methods to hack C# to smell like some other language because you want the style of that other language. That is the hack, not extension methods.

There is no goal to make C# the language to "rule them all", especially not by blindly copying the syntax and idioms of other languages. C# aims to be a good tool and to offer solutions to problems (note, not necessarily the solution that you'd prefer). If popularity and a user base comes with that, then great. C# has a solid position on the TiOBE index and the 2022 rankings indicate an uptick in usage over the previous year.

CyrusNajmabadi commented 2 years ago

The NATURE of these extensions is hackish. For example, Button already has a "Text" property, but because it does not "return Button", we cannot use it!

I don't understand. Why don't you just set the Text property directly (either during construction or after?). It's already a mutable property. What do you need a method for?

najak3d commented 2 years ago

@HaloFour :

That's not a case of extension methods being hackish, that's a case of using extension methods to hack C# to smell like some other language because you want the style of that other language. That is the hack, not extension methods.

There is no goal to make C# the language to "rule them all", especially not by blindly copying the syntax and idioms of other languages. C# aims to be a good tool and to offer solutions to problems (note, not necessarily the solution that you'd prefer). If popularity and a user base comes with that, then great. C# has a solid position on the TiOBE index and the 2022 rankings indicate an uptick in usage over the previous year.

I'm glad we agree that the current methods used by MANY UI's is "hackish" (even Comet, I think is employing such a hack). So to make C# capable of "composing UI's with simple notation", it requires this hack. Extension methods ALLOW the hack. I agree that Extensions themselves aren't innately "a hack", but are often used to create hacks, especially of this nature.

I do think .NET should rule-them-all. The fact that it supports Extensions, has allowed C# to remain competitive against others like Flutter/Dart -- because this "hack" is mostly hidden from those using it. Some 3rdparty generally "provides the extensions" and users don't have to do this for themselves.

For situations where C# is good, i.e. Client apps where you "write it once, and runs everywhere" - C# should rule. But because Microsoft dropped-the-ball for over 10 years, it created a gap now being filled by Flutter/Dart. Flutter/Dart is filling the gap and stealing market share in this domain (e.g. 400,000 new Flutter apps since 2018). MAUI is trying to steal it back, but appears mismanaged or understaffed, and has QA issues and is very late -- AND doesn't even provide a pixel-perfect UI solution (where UI operates/looks the same everywhere, as many apps prefer - i.e. one-user-manual).

So for client-apps, that need to run everywhere, C# should rule, without question (around 2006-2010 time frame, most new apps of this nature were written in C#; but now Xamarin-Forms has almost become a desert wasteland, in comparison).

Client-App development has been grossly trending towards MVU and UI's created in Code, not XAML. C# requires a hack to do this, while many other languages do not. IMO, this has become a clear C# deficiency, in the context where C# should rule.

HaloFour commented 2 years ago

@najak3d

I'm glad we agree that the current methods used by MANY UI's is "hackish" (even Comet, I think is employing such a hack).

No, I don't agree with this. The hack is that the frameworks are intentionally avoiding the idiomatic approach for initializing types that has been provided in .NET and C# for 20+ years.

Simply put, stop trying to force the idioms of one language into another and you won't run into the friction. That applies for any two languages or frameworks.

HaloFour commented 2 years ago

@najak3d

Flutter/Dart is filling the gap and stealing market share in this domain (e.g. 400,000 new Flutter apps since 2018)

Dart barely registers on the TiOBE index despite being around for over a decade. COBOL and VBScript rank higher. Even if language design was a popularity contest, which it is not, C# certainly has nothing to fear in this space. C# is not going to turn into Dart to compete with Dart.

CyrusNajmabadi commented 2 years ago

@najak3d you still haven't provided any good examples of use cases these new language features are needed for. You've just claimed over and over again that they're needed. This is not an effective argument.

I get that you think flutter is great and that MS needs to do more to complete there. But that's not an argument that means that the language needs to change. We could potentially do everything you've mentioned here in the language and not move the needle one iota.

We need solid data (and real use cases) that demonstrate that it is the language side which is the problem here and that this is the right actually solution to that problem.

Simply claiming it is by Fiat, and ignoring the counterarguments about how your existing use cases can already be solved is not helping your case.

Please show real world cases that need this.

najak3d commented 2 years ago

@CyrusNajmabadi wrote:

The NATURE of these extensions is hackish. For example, Button already has a "Text" property, but because it does not "return Button", we cannot use it!

I don't understand. Why don't you just set the Text property directly (either during construction or after?). It's already a mutable property. What do you need a method for?

I've written this in other responses, and will reiterate it here. There are several reasons that methods are needed for initialization:

  1. To call Extension Methods created for simpler syntax (e.g. Pos = new Point(1,2) vs Pos(1,2) extension)
  2. To call Extension Methods that do not exist on the target object (such as setting Grid Col/Row).
  3. If API of class only provides a "SetMethod" rather than property setter (e.g. "SetPosition(point, reason)" vs. just "Position = point"

To name the main ones that I encounter.

Object-Init block does not allow for ANY extension methods to work (other than for Collection initialization maybe?).

And so the hacked solution now being employed widely is to create Extensions for EVERYTHING, required to make it work.

CyrusNajmabadi commented 2 years ago

Extension methods ALLOW the hack. I agree that Extensions themselves aren't innately "a hack", but are often used to create hacks, especially of this nature.

I'm going to be clear here as a mod. This argument is played out. You cannot simply claim this native approach to be a hack as a fact. You may not like it, but that's ultimately you just subjectively picking or choosing the existing, well established, language features that your are willing to use.

C# is a pragmatic language. If there is a totally reasonable, very effective, existing solution, then we're not going to disregard that because you just are just adamant it's a hack.

Such an argument is not convincing and does nothing to actually support your case. If you want this language change you have to show real use cases so we can establish 1. if those cases are important, 2. If they can be solved today, 3. If not, of your proposal is the right solution for them.

Think about it this way. How would we even validate of our language change solves a problem if we don't have the problem in front of us to validate? We could ship something and it could totally fail the moment you tried to use it for your domain.

HaloFour commented 2 years ago

@najak3d

I've written this in other responses, and will reiterate it here. There are several reasons that methods are needed for initialization:

  1. To call Extension Methods created for simpler syntax (e.g. Pos = new Point(1,2) vs Pos(1,2) extension)
  2. To call Extension Methods that do not exist on the target object (such as setting Grid Col/Row). If API of class only provides a "SetMethod" rather than property setter (e.g. "SetPosition(point, reason)" vs. just "Position = point"
  3. To name the main ones that I encounter.

Object-Init block does not allow for ANY extension methods to work (other than for Collection initialization maybe?).

And so the hacked solution now being employed widely is to create Extensions for EVERYTHING, required to make it work.

Of those three the only one I can kind of see is point 2, in that extension properties don't exist.

Beyond that the argument seems to be that you are working with frameworks that are going out of their way to work against the idioms and standards long established in .NET and C# and you are surprised/frustrated that by doing so that they have created unnecessary friction. I would take those points to the designers of said framework as by following the proper conventions these issues almost entirely go away. C# isn't going to shift its idioms because someone prefers to establish their own.

najak3d commented 2 years ago

@HaloFour :

Dart barely registers on the TiOBE index despite being around for over a decade. COBOL and VBScript rank higher. Even if language design was a popularity contest, which it is not, C# certainly has nothing to fear in this space. C# is not going to turn into Dart to compete with Dart.

In a world where small apps are becoming more and more numerous, and devs look for the "right platform" to meet their needs, currently Flutter/Dart tops the list for that context.

  1. Write once, runs everywhere.
  2. UI is pixel-perfect -- test it once, and it runs the same everywhere. (Xamarin Forms uses native controls, so it looks and runs differently everywhere).
  3. Snappy/responsive. (Xamarin-Forms is laggy.)
  4. Allows Code-Based UI declaration, vs. XAML! (Xamarin requires hack using 1000's of extensions for this)
  5. Hot-Reload concept works faster and better. (Xamarin Hot reload is bugged; often have to turn it off to prevent crashing)

So I'm speaking from the Xamarin perspective, having lived in this arena solely for the last 12 years. Xamarin (and thus .NET/C#) has lost much market share to Flutter/Dart, and a few others.

I find myself hating the fact that "they've got it better than me" as I'm still trying to stick with Xamarin.Forms, soon MAUI. C#/.NET used to RULE in this context; now they don't.

We're now turning to Avalonia, another project that sprang up to fill this unfortunate gap left by Microsoft, to provide a pixel-perfect UI that runs everywhere. Yet Avalonia has to use Extension Hacks to compete against Dart, and it seems silly to me, as I do not see the inherent dangers in allowing methods to be called from Object init blocks, which is the only barrier to enable the elimination of these hacked Extension methods.

HaloFour commented 2 years ago

@najak3d

So take that up with Xamarin and MAUI. C# doesn't know nor care about those UI frameworks, or any UI frameworks. What C# does provide is a standard syntax for object initialization and those idioms work just fine for UI frameworks as long as they follow the appropriate conventions.

CyrusNajmabadi commented 2 years ago

Write once, runs everywhere.

This is not solved by a language change.

UI is pixel-perfect -- test it once, and it runs the same everywhere. (Xamarin Forms uses native controls, so it looks and runs differently everywhere).

This is not solved bya language change,

And this is true for all 5 of your points. :)

Your entire post appears to complaints about xamarin. None of your relates to the c# language afaict.

najak3d commented 2 years ago

@HaloFour :

I'm glad we agree that the current methods used by MANY UI's is "hackish" (even Comet, I think is employing such a hack).

No, I don't agree with this. The hack is that the frameworks are intentionally avoiding the idiomatic approach for initializing types that has been provided in .NET and C# for 20+ years.

Simply put, stop trying to force the idioms of one language into another and you won't run into the friction. That applies for any two languages or frameworks.

The "friction" doesn't bother me. What bothers me most is that Flutter/Dart currently provides a "better platform for writing Client-apps that are written once, and run everywhere", and are stealing Xamarin market share in mass. If Microsoft hadn't dropped-the-ball on Xamarin Forms -- this wouldn't be an issue.

Flutter/Dart are becoming more capable quickly, and are playing leapfrog with C# (similar to how C# played leapfrog with Java initially). Similarly Java folks were mad when they lost so much market share to C# 20 years ago. Now Flutter/Dart is doing the same thing now, to C# (mostly because Xamarin.Forms suck, as does XAML).

Turns out... most people seem to prefer C# markup to XAML. (similar to what Dart/others now allow)

CyrusNajmabadi commented 2 years ago

What bothers me most is that Flutter/Dart currently provides a "better platform for writing Client-apps that are written once, and run everywhere", and are stealing Xamarin market share in mass.

@najak3d that may be true, but that doesn't motivate changes in the language just because. We'd need a strong argument as to why these changes to the language would move the needle in the slightest.

CyrusNajmabadi commented 2 years ago

as I do not see the inherent dangers in allowing methods to be called from Object init blocks, which is the only barrier to enable the elimination of these hacked Extension methods.

We don't do things just because. We have literal finite resources for language change. So every language change has to provide immense value to be worth it. So this would have to solve actual real problems to warrant the time and effort involved to do all of this.

najak3d commented 2 years ago

@CyrusNajmabadi :

And this is true for all 5 of your points. :) Your entire post appears to complaints about xamarin. None of your relates to the c# language afaict.

I am speaking from a Xamarin perspective - whose main focus has been on Client-Side apps. Since Xamarin is "losing this battle", then in turn C# is losing that same battle.

Part of winning the battle is providing C# markup. Many UI's have solved it via a HACK and extra code. Problem is "solved with a HACK".

So it begs the question of "why does this require a hack?", especially when this hack would not be needed if we simply allowed method calls from inside the Object-Init block, which IMO, has no real downside, but rather only an upside.

CyrusNajmabadi commented 2 years ago

To call Extension Methods created for simpler syntax (e.g. Pos = new Point(1,2) vs Pos(1,2) extension)

You don't need this. Just write:

Position = { X = 1, Y = 2 }

It's already terse, easy to read, idiomatic and well supported everywhere :-)

HaloFour commented 2 years ago

@najak3d

What bothers me most is that Flutter/Dart currently provides a "better platform for writing Client-apps that are written once, and run everywhere", and are stealing Xamarin market share in mass. If Microsoft hadn't dropped-the-ball on Xamarin Forms -- this wouldn't be an issue.

This has nothing to do with C# as a language.

Turns out... most people seem to prefer C# markup to XAML. (similar to what Dart/others now allow)

I've yet to meet a UI framework in .NET that did not allow writing code instead of markup.

Part of winning the battle is providing C# markup. Many UI's have solved it via a HACK and extra code. Problem is "solved with a HACK".

You not liking it does not make it a hack. If you have to write extension methods to use a particular UI framework take that up with said UI framework.

najak3d commented 2 years ago

@CyrusNajmabadi :

What bothers me most is that Flutter/Dart currently provides a "better platform for writing Client-apps that are written once, and run everywhere", and are stealing Xamarin market share in mass.

@najak3d that may be true, but that doesn't motivate changes in the language just because. We'd need a strong argument as to why these changes to the language would move the needle in the slightest.

Your proposal is for "compound assignment"... here again, this can be done outside of the initializer block too. So where is the problem? What problem are you aiming to solve that isn't already being solved by less convenient notation?

Most of the C# changes have been for "convenience" and "simpler notation"... not for "making new things possible". We mostly are just making "common things that were difficult, easier to do".

I don't see a reason to "restrict what is called inside Object Init block". The change I'm proposing is quite simple - it's mostly just to "remove existing restrictions" and make it more permissive. Alleviate the restrictions, and make it more flexible. The resulting code will not be confusing, as it runs the exact same as code would if you assigned a variable and then called the same methods directly after the object-Init block. Treat Object Init Block mostly as a VB "With" block, with no (or few) exceptions.

najak3d commented 2 years ago

@HaloFour - I thought you were the one who agreed that the 1000's of Extensions to effectively modify the C# language to make it support Dart-like notation was in fact "a hack". Now it seems that you are saying "it's not a hack". Which is it? I think it's blatantly a hack, and I thought you agreed.

HaloFour commented 2 years ago

@najak3d

I don't see a reason to "restrict what is called inside Object Init block". The change I'm proposing is quite simple - it's mostly just to "remove existing restrictions" and make it more permissive. Alleviate the restrictions, and make it more flexible. The resulting code will not be confusing, as it runs the exact same as code would if you assigned a variable and then called the same methods directly after the object-Init block. Treat Object Init Block mostly as a VB "With" block, with no (or few) exceptions.

I disagree. The notion of calling arbitrary methods seems entirely out of place for an object initialization block. It's very much not intended to be like VBs With block (which has been roundly rejected for C#).

There needs to be significantly more reason to consider such a language change. Trying to smell like Dart is simply not good enough.

I thought you were the one who agreed that the 1000's of Extensions to effectively modify the C# language to make it support Dart-like notation was in fact "a hack".

The desire to make a .NET framework model that of a Dart framework is the hack. How you achieve that is irrelevant. Stop trying to force the idioms of one framework into another and you won't experience this friction.

najak3d commented 2 years ago

Thank you for all of your attention and time.

@CyrusNajmabadi - I see you are listed near the top on Github as a "Contributor" and so I imagine you have sufficient sway in the decisions made for the C# language. I see about 120 Contributors in all on the list, and the vast majority of those Contributors who have seen my posts, do not seem to be supportive of my proposal.

So I think I've gotten my answer -- the answer appears to be a flat "no". So I can stop my ranting and save us all some time.

I just find it a shame that C# has to be hacked to accommodate the preferences of perhaps a million client-app Devs, and as a result, watching C# lose out to rising alternatives such as Flutter/Dart.

Since Extensions have allowed us to hack C# in the desired manner, at least we are still able to accommodate, making my proposal "non-essential". However, it should be noted that MANY of C# changes were also "non-essential", but were done mostly for purposes of simplification of notation, or other "non-essential" reasons. So I would suppose that if enough people, like me, came to this forum to request this feature -- then it might gain momentum.

Currently I seem to stand mostly alone here, and thus there is no point to me continuing these rants.

I thank the whole team here. I love, love, love C#, and want to see it regain the market share lost to Flutter/Dart (and others). That is a part of my overall mission here.

CyrusNajmabadi commented 2 years ago

Your proposal is for "compound assignment"... here again, this can be done outside of the initializer block too. So where is the problem?

Declarative initialization of events is not possible today. That's a core thing we aim to solve. Similarly, declarative delta initialization from another object is not possible either.

CyrusNajmabadi commented 2 years ago

The resulting code will not be confusing,

As I've already mentioned, this syntax is legal today and means something else :-)

So I do think that would be confusing.

CyrusNajmabadi commented 2 years ago

So I think I've gotten my answer -- the answer appears to be a flat "no". So I can stop my ranting and save us all some time.

@najak3d this is not the case. I've spelled out explicitly what we would need from you to progress this argument further.

Right now it is going in circles. Genuine use cases with real world apis need to be shown that demonstrate the strong need for this.

Continuing to lament about flutter is not helpful. You haven't shown at all that this proposal of yours moves the needle in that discussion.

Indeed, all your complaints seem to have nothing to do with the language and everything with the tooling. So your argument comes across as nonsensical. In other words, it's a solution in search of a problem.

CyrusNajmabadi commented 2 years ago

I thank the whole team here. I love, love, love C#, and want to see it regain the market share lost to Flutter/Dart (and others). That is a part of my overall mission here.

That's fine. But consider it from our perspective. Sometime says they want .net UI stacks to complete better against flutter. Then they say something like: and thus GC is a hack, use ref counting instead.

There's a huge disconnect between what is being proposed and the problem space they claim they want to see improved.

That's the issue here. You've mentioned many issues with tooling (things like hot reload or pixel perfect), but then say we need arbitrary object invocation in initialization.

There's a disconnect between the problems you are calling out and the solutions you are proposing