Skip to content

Zenhackers

  • Home
January 29, 2014January 29, 2014 Rouan van DalenSoftware Development

Lenses in C#

csharp

I first read about lenses in my adventures with Haskell.

A lens is an accessor for getting/setting a field in a data structure.

There are good reasons for lenses to exist in a pure, functional programming environment like Haskell. I thought lenses was a neat idea and it would be useful to have them in C#. So I came up with the following definition:

class Lens<TObject, TProperty>
{
    public Func<TObject, TProperty> Get {get; private set;}
    public Action<TObject, TProperty> Set {get; private set;}
    
    public Lens(Func<TObject, TProperty> getter, Action<TObject, TProperty> setter)
    {
        Get = getter;
        Set = setter;
    }
}

The Lens<> class is a combination of:

  • a getter function – allowing us to get the value of a property.
  • a setter function – allowing us to set the value of a property.

This is the essence of a lens. Lenses allow us to treat object properties as first-class values, so they can be stored in variables, and passed into and out of functions.

What about using the Lens<> class? It looks like this (LINQPad-friendly):

// a simple class for which we are going to create lenses for the properties
class Person
{
    public string Name {get;set;}
    public int Age {get;set;}
}

void Main()
{    
    // lens for the Name property
    var name = new Lens<Person,string>(p => p.Name, (p, v) => p.Name = v);

    // lens for the Age property
    var age = new Lens<Person,int>(p => p.Age, (p, v) => p.Age = v);

    var person = new Person {Name = "Peter", Age = 30};

    // use the lenses to get the property values
    name.Get(person).Dump();    // output: Peter
    age.Get(person).Dump();     // output: 30

    // use the lens to set the Name property value
    name.Set(person, "Michael");
    name.Get(person).Dump();    // output: Michael
}

This seems simple enough, but creating a lens for a property is more verbose than I would like it to be. I would just like to say:

create a lens for this property.

We can update our Lens<> class to add the New() static method and its helper methods:

class Lens<TObject, TProperty>
{
    public Func<TObject, TProperty> Get {get; private set;}
    public Action<TObject, TProperty> Set {get; private set;}

    public Lens(Func<TObject, TProperty> getter, Action<TObject, TProperty> setter)
    {
        Get = getter;
        Set = setter;
    }

    <summary>
       Creates a new lens by taking the property-getter function, and deriving from 
       that, the property-setter function, returning a new lens with both the
       getter/setter filled-in.
    </summary>
    public static Lens<TObject,TProperty>
    New(Expression<Func<TObject, TProperty>> propertyGetter)
    {
        var getter = propertyGetter.Compile();
        var setter = DeriveSetterLambda(propertyGetter);

        var lens = new Lens<TObject, TProperty>(getter, setter);
        return lens;
    }        

    <summary>
       Derives the property-setter function from the supplied property-getter function.
    </summary>
    static Action<TObject,TProperty>
    DeriveSetterLambda(Expression<Func<TObject, TProperty>> propertyGetter)
    {
        string propName = GetPropertyName(propertyGetter);                    

        var paramTargetExp = Expression.Parameter(typeof(TObject));
        var paramValueExp  = Expression.Parameter(typeof(TProperty));

        var propExp   = Expression.PropertyOrField(paramTargetExp , propName);
        var assignExp = Expression.Assign(propExp, paramValueExp);

        var setter = Expression.Lambda<Action<TObject,TProperty>>(assignExp, paramTargetExp, paramValueExp);

        return setter.Compile();
    }

    <summary>
       Get the name of the property mentioned in the property-getter function as a
       string.
    </summary>        
    static string GetPropertyName(Expression<Func<TObject,TProperty>> exp)
    {
        var propExp = (MemberExpression)exp.Body;
        var n = propExp.Member.Name;
        return n;
    }        
}

The New() method creates a new Lens<> by taking the getter-function, and deriving from it the corresponding setter-function for the lens. To make this happen we use C# Expression Trees to do a little meta-programming and represent code as data and generate code on the fly while the application is running.

We can now rewrite our Main() method to take advantage of the Lens<>.New() method to create lenses:

void Main()
{    
    // lens for the Name property using the New() method
    var name = Lens<Person,string>.New(p => p.Name);

    // lens for the Age property using the New() method
    var age = Lens<Person,int>.New(p => p.Age);

    var person = new Person {Name = "Peter", Age = 30};

    name.Get(person).Dump();    // output: Peter
    age.Get(person).Dump();     // output: 30

    name.Set(person, "Michael");
    name.Get(person).Dump();    // output: Michael
}

We now have a simple implementation of lenses which we can use in C#. In the next post, I will talk more about the interresting uses of lenses.

Tagged C#

Related Posts

Shifting gears // The need for speed!

June 26, 2023June 26, 2023Software Development

Keeping things simple – a challenge all teams face

June 12, 2023June 12, 2023Software Development
Copyright © 2022 Hait. All rights reserved.
↑