Making C# code a bit more DSL like

March 10, 2010 at 9:17 pm | Posted in C#, GIMP, Programming | 12 Comments

Recently I was cleaning up some of my GIMP# code. One of the constructions that is often used is something like this:

var image = new Image(width, height, ImageBaseType.Rgb);
image.AddLayer(new Layer(image, "layer", ImageType.Rgb), 0);

The code above creates a new RGB image with a certain width and height and adds a new layer to that image. As you can see to just add the layer I need a reference to image twice. The GIMP API dictates that you can only create a layer if you know the image ID. What I was looking for was something with a bit more DSL (Domain Specific Language) like syntax. Something like in the next code fragment:

var image = new Image(width, height, ImageBaseType.Rgb) {
   {new Layer("layer", ImageType.Rgb), 0}};

This code uses the C# collection initializer that was introduced in version 3.0. The first step to make this work is to make Image implement the IEnumerable interface. The slightly weird thing is that this interface doesn’t even have to be functional:

public IEnumerator GetEnumerator()
{
   throw new NotImplementedException();
}

The above code is already sufficient. Instead of the AddLayer we now have to supply an Add method with the following signature:

public void Add(Layer layer, int position)

Now a problem becomes visible: in the original code we first construct image and use this variable as the first paramter in the layer constructor. However, when we use a collection initializer, we don’t have this variable available. And constructing a layer without an image is not possible in the GIMP API. To solve this problem I introduced a delayed constructor and do the actual construction in the Image.Add method, where the image is available as the this parameter. The Layer class looks like this:

public class Layer
{
   readonly Func<Image, Layer> _delay;

   public Layer(string name, ImageType type)
   {
      _delay = (image) => {return new Layer(image, name, type);};
   }

   // internal since it is only used by the Image class
   internal Layer DelayedConstruct(Image image)
   {
      return _delay(image);
   }
}

The code for Image.Add now becomes straightforward:

public void Add(Layer layer, int position)
{
   AddLayer(layer.DelayedConstruct(this), position);
}

This delayed constructor trick works quite nice. In the same way I can add GIMP channels and vectors. It is even possible to add channels, layers and vectors in one collection initializer since the Add methods all have different signatures.

So, where is the catch? There is a (minor) one: since I now have added a layer constructor without the image parameter, it is possible to call the constructor outside the collection initializer context:

var layer = new Layer("layer", ImageType.Rgb);
// layer is unusable: all methods will fail!
image.Add(layer, 0);
layer = image.Layers["layer"];
// only now can you use layer
Console.WriteLine("Layer width: " + layer.Width);

Personally I think the benefits of this approach outweigh this small disadvantage. I’m interested to hear alternative implementations.

12 Comments »

RSS feed for comments on this post. TrackBack URI

  1. You could introduce another class, LayerPrototype, that has no actual Layer functionality except your delayed constructor. Added bonus: that prototype instance could be used in more than one Image, or even more than once per image.

    e.g.

    class LayerPrototype {
    // .. your delayed layer construction code
    }

    class Image {
    public void Add(LayerPrototype proto, int position)
    {
    AddLayer(proto.DelayedConstruct(this), position);
    }
    }

    • Thanks for the feedback! I have thought about that solution as well. It is perfectly workable although I slightly dislike that it is now very visible that you are not constructing a ‘real’ layer. Maybe all code should use a LayerFactory class that creates Layers based on a LayerPrototype. In that case one would never be able to construct Layers directly.

  2. […] Making C# code a bit more DSL like « Maurits thinks aloud […]

  3. Hi Maurits,

    I like the kind of things your doing now… I should get out of my ‘C world’ more often….

    One question though: I don’t get the last code. If you create this ‘unusable’ layer. And then do image.Add(layer, 0), doesn’t that then create a NEW layer (in the delayed constructor), which is added to the Image? And therefore, won’t that unusable layer still be unusable?

    Best regards,

    Arnaud

    P.S. Didn’t have the time to code some small example code to test this myself…

    • Hey Arnoud,
      Oops, you are right. I will correct this! Thanks for the sharp remark.

  4. You can apply classic (dotted) DSL:
    var image = new Image(width, height, ImageBaseType.Rgb)
    .WithLayer("layer1", ImageType.Rgb)
    .WithLayer("layer2", ImageType.Rgb);

    Where WithLayer is extension method that calls regular Layer constructor and returns image.

    • Hi Andrey,

      Great solution! I have seen this before, but somehow didn’t think about using it. I like the syntax of the collection initializers solution a bit more, but your method is safer.

      I am curious why you propose to use extension methods here and not just regular methods in the Image class?

      • Yes, you can use regular method. I just think this is some kind of syntax sugar that we are adding on top of existing API. So it is ad hoc feature and it should be located in separate place. I feel we are not going to change public contract of Image class, so we should not change the set of its public methods. This is just my feeling.

      • Do I understand correctly, that this method will look something like the following?

        public Image WithLayer(string name, ImageType type)
        {
        this.addLayer(new Layer(name, type));
        return this;
        }

      • Almost yes.
        public static Image WithLayer(this Image @this, string name, ImageType type, int index)
        {
            this.AddLayer(new Layer(@this, name, type), index);
            return @this;
        }

  5. Why can’t the layer class just be lazy about calling gimp_layer_new until after the layer has been added to an image?

    System.Windows.Forms doesn’t create the window handles immediately upon construction.

    • That surely would be possible. However that would also mean that the GIMP# API would behave differently from the GIMP API, because in the latter case it is possible to call methods on a layer without it being attached to an image.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.
Entries and comments feeds.

%d bloggers like this: