DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Bracket Patterns in C# - P.1
Tea
Tea

Posted on

Bracket Patterns in C# - P.1

Perhaps a month ago, I teased a coming post about bracket/delimiter/begin-end patterns in C#...

What is a bracket pattern?

If you have used OpenGL, you may remember code looking like:

glBegin(GL_QUADS);
glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(-0.5f, -0.5f);    
// ...
glEnd();
Enter fullscreen mode Exit fullscreen mode

These patterns are not uncommon and they are, well - error prone. Ever accidentally left your front door open? That is how goes with pairing commands as above.

Bracket patterns in C-sharp

In C# you may leverage using and IDisposable. However this is on the heavy side, primarily for safely managing resources.

I review lightweight alternatives - looking at increasingly more powerful variants.

The basics

In the simplest case, we want a pattern equivalent to...

Begin();
DoSomething();
End();
Enter fullscreen mode Exit fullscreen mode

A basic bracket pattern, then, is implemented as follows:

class Service{

    public Func<T> With{ get{
        Start();
        return End;
    }}

    void Start(){ ... }

    void End(T arg){ ... }

}
Enter fullscreen mode Exit fullscreen mode

Enabling the following use:

With( DoSomething() );
Enter fullscreen mode Exit fullscreen mode

Returning the With function as a property allows running code both before and after running the enclosed command.

One use case is with functional graphic APIs (such as OpenGL, and others...); Unity, for instance, provides the following API(*):

(*) In places they now use IDisposable

BeginHorizontalGroup();
Label("Hello");
EndHorizontalGroup();
Enter fullscreen mode Exit fullscreen mode

Which then becomes:

HorizontalGroup( Label("Hello") );
Enter fullscreen mode Exit fullscreen mode

And the pattern implementation:

public static class GUI{

    public static Func<object> With{ get{
        BeginHorizontalGroup();
        return End;
    }}

    void End(object arg) => EndHorizontalGroup();

}
Enter fullscreen mode Exit fullscreen mode

As you see we are not doing any with the argument - our purpose here is only 'framing' the Label() command. Which brings us to the one constraint these patterns are sharing: Label() (and likewise framed commands) cannot return void.

Although I did use a static class this is not required; so, assuming the target API does not use globals either, thread safety may be preserved.

A common use case for bracket patterns is when building hierarchies and this may help designing articulate alternatives to the builder pattern.

In its most simple form the bracket pattern only supports one argument. What if (as makes sense with a horizontal group) we wanted to combine several commands? Then, use a delegate:

public class Service{

    public EndFunc With{ get{
        Start();
        return End;
    }}

    void Start(){ ... }

    delegate void EndFunc(params T[] args);

    void End(params T[] args){ ... }

}
Enter fullscreen mode Exit fullscreen mode

The reason we need a delegate here is var-args (...) are not supported by any variant of System.Func<...>.

Back to the 'horizontal group' example, the above then enables the following syntax:

HorizontalGroup( 
    Label("Hello"), 
    Button("Tap to enter"),
);
Enter fullscreen mode Exit fullscreen mode

If you go back to the OpenGL example, note how the glBegin(GL_QUADS) function allowed arguments. In the next session I'll be looking at a handy variant which cleanly separates arguments to the begin function from the enclosed items.

Var-arg forms allocate memory; often the impact is negligible; when it's not there is an alternative, and I'll also review this in my next posting.

Happy Coding ~

Photo by Dan-Cristian Pădureț on Unsplash

Top comments (0)

🌱 DEV runs on 100% open source code known as Forem.

Β 
Contribute to the codebase or learn how to host your own.