-
-
Save VincentH-Net/c7568727517c4ef4f622815a580316d9 to your computer and use it in GitHub Desktop.
// C# vNext markup friendly | |
enum Row { Icon, Prompt, Header, Entry } | |
void Build() => Content = new Grid | |
{ | |
RowDefinitions = Rows.Define( | |
(Icon , Auto), | |
(Prompt, Auto), | |
(Header, 50 ), | |
(Entry , Auto) | |
), | |
{ | |
Image { } | |
.Row (Icon) .CenterH () | |
.Bind (vm.Icon), | |
Label { LineBreakMode = WordWrap } | |
.Row (Prompt) .TextCenterH () | |
.Bind (vm.RegistrationPrompt), | |
Label { Text = "CODE" } | |
.Row (Header) .Bottom (), | |
Entry { Placeholder = "123456", Keyboard = Numeric } | |
.Row (Entry) .Margins (left: 10) | |
.Bind (vm.RegistrationCode) | |
} | |
}; | |
// C# 8 | |
void Build() => Content = new Grid | |
{ | |
RowDefinitions = Rows.Define( | |
(Row.Icon , Auto), | |
(Row.Prompt, Auto), | |
(Row.Header, 50 ), | |
(Row.Entry , Auto) | |
), | |
Children = { | |
new Image { } | |
.Row (Row.Icon) .CenterH () | |
.Bind (nameof(vm.Icon)), | |
new Label { LineBreakMode = LineBreakMode.WordWrap } | |
.Row (Row.Prompt) .TextCenterH () | |
.Bind (nameof(vm.RegistrationPrompt)), | |
new Label { Text = "CODE" } | |
.Row (Row.Header) .Bottom (), | |
new Entry { Placeholder = "123456", Keyboard = Keyboard.Numeric } | |
.Row (Row.Entry) .Margins (left: 10) | |
.Bind (nameof(vm.RegistrationCode)) | |
} | |
}; |
Nice one @VincentH-Net. Can't wait to see what you would come up with.
This is a good list. My comments:
- You can already omit the enum typename with a using static directive.
- There's already a proposal for eliminating the need for the new keyword but there was heavy pushback and no movement on it. Limiting it to collection initializers might have more luck though.
- Not sure about the nameof elimination - seems a little ambiguous to me especially if the property is a string type. Maybe a sigil to use instead of nameof? e.g.
#vm.Icon
instead ofnameof(vm.Icon)
? - Your default child view collection name is interesting, but doesn't seem likely to be accepted.
EDIT: I was unaware that C# already allows an initialization syntax for readonly collection properties. If you omit the new
keyword and start with an opening curly brace after the =
operator in the object initializer, it will call Add
for each item in the initializer list. In this way you can set the Children
property of Panel
or Layout
and other similar controls with a Children
collection.
Codegen versus Language Support
Some of the proposed improvements can be approximated by generating fluent + factory functions.
See these example functions and how they can be used in markup:
But the disadvantages of this codegen workaround are:
- The overhead. A UI framework needs to include a generated function for each view type and each view property. Runtime the fluent API is not free either when compared to an object initializer expression.
- Reduced readability - you lose syntax coloring because everything is a function. So e.g. functions for classes and properties are all one color - see the above image.
- Reduced structure readability and navigation - putting lists of child views in
( )
instead of in{ }
causes loss of vertical lines between opening and closing character, and also of collapse/expand functionality in the editor. - Name conflicts because of using using static to include factory methods that have the same name as the type.
E.g. using static a factory class that contains the factory methodLabel()
causes an error when you attempt to reference a static member of that class, e.g.Label.TextColorProperty
.
So C# support would not just reduce the development effort and overhead of .NET UI frameworks, but even more importantly it would improve the development experience of everyone using those frameworks with markup in C#. With MAUI, UNO, WinUI and Blazor that is a LOT of developers who could benefit, even if only part of them choose to use C# for markup instead of XAML or HTML.
Automatic nameof surport has many risks, maybe new type is needed that can pass details of a property that does not have automatic conversion to string.
Nameof can sort-of be eliminated in C# 10 with the [CallerArgumentExpression]
attribute.
Could we reduce repetition in nested object initializers?
(e.g. in a declarative UI markup expression, but useful when new-ing up any tree of objects)
-
Allow to omit
new
in initializers below the tree root.
So havenew
on the root, but not required to repeatnew
for each nested child or content object. -
Allow to indicate a default initialization property for a type (collection or single "content property"),
so that property name can be omitted in an initialization expression. E.g.new Grid { Children = { new Label { }, new Button { } } }
could be like:
new Grid { { new Label { }, new Button { } } }
or even:
new Grid { new Label { }, new Button { } }
and when combined with 1):
new Grid { Label { }, Button { } }
-
Allow to omit the parameter type when passing in a parameter value (SwiftUI uses
.
).
To be used when passing in an enum value or a class static member to a parameter.
(also useful outside expression trees) e.g.Alignment = Alignment.Left
could be:
Alignment = .Left
where
Alignment.Left
is either an enum value or a static class member.
When the . is entered, Intellisense starts just as if the full type name was entered before the .
Compare side-by-side:
