Skip to content

Instantly share code, notes, and snippets.

@VincentH-Net
Last active April 4, 2022 18:47
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VincentH-Net/c7568727517c4ef4f622815a580316d9 to your computer and use it in GitHub Desktop.
Save VincentH-Net/c7568727517c4ef4f622815a580316d9 to your computer and use it in GitHub Desktop.
C# language proposal examples for UI markup #CSharpForMarkup
// 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))
}
};
@VincentH-Net
Copy link
Author

VincentH-Net commented Dec 6, 2019

This is a feeler to see if a C# language proposal would make sense.

Above example is for Xamarin Forms but the features are equally useful for other UI frameworks where C# is not yet a primary citizen, e.g.
MAUI, UNO, WinUI, WPF and UWP. It is also conceivable that Blazor could use declarative C# to create HTML markup, enabling full web development in a single language. It has already been done for F# in https://fsbolero.io/

Google has optimized Dart for markup:
image

Apple has SwiftUI:
image

Making C# more client friendly should attract more (non-web) developers to the C# & .NET ecosystem.
Many of these devs will prefer to learn a single language for UI markup and logic.
Let's compete on a more equal footing with Google and Apple.

Feel free to feedback, thx!

@VincentH-Net
Copy link
Author

VincentH-Net commented Dec 6, 2019

Possible improvements in the C# language to better support declarative markup (note that this would benefit any UI framework in .NET - WinUI, MAUI, UWP/UNO, Blazor, WPF... so a very large developer audience):

  • omit new keywords within a collection initializer
  • omit nameof(); instead
    • use a one-character alternative
      e.g. #vm.Icon would result in "Icon", or
    • specify (with an attribute? or a one-character prefix like #) on a string parameter that it should receive the name instead of the value of what is passed in
      e.g. void Bind([nameof] string propertyName) or void Bind(#string propertyName) when called with Bind(vm.Icon) would result in Bind("Icon")
  • omit default child view collection name (e.g Children = { })
  • omit default child view name (e.g. Content = new ... )
  • shorthand character (SwiftUI uses .) to reference the parameter type when passing in a parameter value. To be used when passing in an enum value or a class static member to a parameter.
    e.g. calling Align(Alignment a) { ... } with Align(.Left) would be equivalent to Align(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 ..

@VincentH-Net
Copy link
Author

Compare side-by-side:
image

@saint4eva
Copy link

Nice one @VincentH-Net. Can't wait to see what you would come up with.

@JeroMiya
Copy link

JeroMiya commented Feb 3, 2020

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 of nameof(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.

@VincentH-Net
Copy link
Author

VincentH-Net commented Jul 12, 2020

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:

image

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 method Label() 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.

@IanRingrose
Copy link

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.

@VincentH-Net
Copy link
Author

Nameof can sort-of be eliminated in C# 10 with the [CallerArgumentExpression] attribute.

@VincentH-Net
Copy link
Author

VincentH-Net commented Apr 4, 2022

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)

  1. Allow to omit new in initializers below the tree root.
    So have new on the root, but not required to repeat new for each nested child or content object.

  2. 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 { } }

  3. 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 .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment