Pages

Friday, December 17, 2010

Fluent C#

Fluent C#


The title says it all, Fluent is a very cool pattern that goes under used in my opinion, but it's better if it will be under used then over used ;-). Since NET 3.5 there is a possibility do extend methods and create some kind of fluent calls, take collections for example. This feature of NET can be very dangerous and it has to be used with caution, but then again this feature is just perfect to do Fluent interfaces. Before we go to any code let's answer the basic question shall we :-)

What are Fluent Interfaces?

The short answer: the methods defined on a certain object return the object itself, making it possible to use dot notation when invoking multiple methods on that object.

Why Should I Care?

Because it creates a fluent typing pattern, and it more natural (language natural) typing. If you define the method names to be Language neutral you can almost type words to the compiler what to do in a natural language fashion.

Plus having Extension methods, Generics, and Lambdas one can create very flexible human readable fluent interfaces, that can do magic (syntactic sugar work).

We can define fluent interfaces by extending the Net ones on ICollection for e.g (more on extensions can be found in my earlier post here), Like so:

The Code For Extensions

.
    public static class ICollectionExtensions
    {
        public static void Call<T, R>(this ICollection<T> obj, Func<T,R> action)
        {
            var result = default(R);

            foreach(var item in obj)
                result = (R)action(item);
        }
    }

    public static class ElementExtensions
    {
        public static T NotNull<T, R>(this T obj, Func<T, R> property)
        {
            if (property(obj) != null)
                return obj;
            else
                throw new Exception(obj.ToString() + " is null");
        }
    }

The first method is an extension for collections and calls methods of each object in the collection, this can come in handy in many situations, and can make a complex code into one liner.

The second method is a validation method for objects, and checks is a specified field is not null.

As you can imagine this sort of extending can get out of hand really fast, even with using namespaces, if a object will have 2 methods of it's own and 150 extended ones, so it can become quite hard to maintain in time, but it adds unmatched flexibility.

I personally prefer to do it old fashioned way and explicitly add some needed Fluent functionality for a time for my specific objects, for ICollection and other NET build types in my opinion the extension methods are fine. So the class that want's to use fluent have to inherit from a Fluent base Class that implements the Fluent interface.

The Diagram


Basically the interface defines three types of methods.

  • Set Actions that set values based on predicate actions.
  • Predicate Actions that set predicates.
  • With (Notification) Actions that set notifications and evaluate predicate actions.

The Code

.
    public interface IFluent<T>
    {
        IFluent<T> IsNull<R>(Func<T, R> prop);
        IFluent<T> IsNotNull<R>(Func<T,R> prop);
        IFluent<T> WithMessage(string message);
        IFluent<T> WithError(Exception ex);
        IFluent<T> SetValueIf(Func<T, object> property);
        IFluent<T> WithNoNotification();
        IFluent<T> HasValue(Func<T, bool> property);
    }

    public enum FluentActionType : byte
    {
        PredicateAction = 1,
        SetValueAction = 2
    }

    public class Fluent<T> : IFluent<T>
    {
        private T obj;
        private List<bool> predicateResult;
        private List<Func<T, object>> actionList;
        private FluentActionType lastAction;

        public void MakeFluent(T obj)
        {
            this.obj = obj;
            predicateResult = new List<bool>();
            actionList = new List<Func<T, object>>();
        }

        public IFluent<T> IsNotNull<R>(Func<T, R> property)
        {
            lastAction = FluentActionType.PredicateAction;

            if(property(obj) != null)
                predicateResult.Add(true);
            return this;
        }

        public IFluent<T> IsNull<R>(Func<T, R> property)
        {
            lastAction = FluentActionType.PredicateAction;

            if (property(obj) == null)
                predicateResult.Add(true);
            return this;
        }

        public IFluent<T> HasValue(Func<T, bool> property)
        {
            lastAction = FluentActionType.PredicateAction;

            if (property(obj) == true)
                predicateResult.Add(true);
            return this;
        }

        public IFluent<T> SetValueIf(Func<T, object> property)
        {
            if(lastAction == FluentActionType.PredicateAction)
                EvaluatePredicates(null);

            lastAction = FluentActionType.SetValueAction;

            actionList.Add(property);
            return this;
        }

        public IFluent<T> WithNoNotification()
        {
            return EvaluatePredicates(null);
        }

        public IFluent<T> WithMessage(string message)
        {
            EvaluatePredicates(() => Console.WriteLine(message));
            return this;
        }

        public IFluent<T> WithError(Exception ex)
        {
            EvaluatePredicates(() => { throw ex; });
            return this;
        }

        private IFluent<T> EvaluatePredicates(Action action)
        {
            if (predicateResult.Count > 0 &&
                predicateResult.FindAll(x => true).Count == predicateResult.Count)
            {
                actionList.Call(x => x(obj));
                if (action != null)
                    action();
                actionList.Clear();
            }
            predicateResult.Clear();

            return this;
        }
    }

A few words of explanation:

The class just holds a list of predicates that are true, and if we have a 'With' action it will validate the predicates and perform the action defined in the 'With' action like writing to Console. The enumeration of actions if for storing the history of the last action and it's needed for the SetValue action (and other set actions if one would want to define others) as we need to determine if we have to evaluate the predicates or to group a few actions on a set of predicates.

The code uses the extension method that we defined earlier that calls method of the element in a collection, this shows the use of such method and how it shortened the code.

Take this code for e.g

.
            ABC a = new ABC();
            a.MakeFluent(a);
            a.SetValueIf(x => x.Name = "a")
                .SetValueIf(x=>x.SurName = "b")
                .HasValue(x => x.Id == 1)
                .WithNoNotification();

We want to set more then one value on a given set of predicate actions thus the SetValue needs to determine if more of the same actions will be grouped or to evaluate the previous group.

Examples


Let's define a dummy class for our testing operations.

.
    public class ABC : Fluent<ABC>
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string SurName { get; set; }

        public int Sum(int a, int b)
        {
            return a + b;
        }

    }

Now some examples to show what can be done with Fluent interfaces, I should note that this is just a little fraction that can be done with it, but for validation and some conditional operations it's perfect, one could imagine what cool stuff could be done with fluent (Fluent Streams any one? :-) ).

.
            ABC a = new ABC();
            a.MakeFluent(a);
            a.SetValueIf(x => x.Name = "Bartosz")
                .IsNull(x => x.Name)
                .HasValue(x => x.Id == 0)
                .WithMessage("value Set!");

            a = new ABC();
            a.MakeFluent(a);
            a.SetValueIf(x => x.Name = "Bartosz")
                .SetValueIf(x=> x.SurName = "Adamczewski")
                .HasValue(x => x.Id == 0)
                .WithMessage("value Set!");

            a = new ABC();
            a.MakeFluent(a);
            a.SetValueIf(x => x.Name = "Bartosz")
                .HasValue(x => x.Id == 0)
                .SetValueIf(x => x.Name = x.Name + "!!!")
                .IsNotNull(x => x.Name)
                .WithMessage("value Set!");

Summing Up


Fluent is nothing new, Orm framework use it, Builder and Constructor as well as Generator classes use it, but there are many more places where this pattern could prove very useful like validation, extensions, and others so that's why in my opinion it is still under used, sure I would not rather see being used in every single scenario (that would be terrible), but if used in the correct situation it could provide some great flow control.

No comments:

 
ranktrackr.net