4

I have a service that takes an object and based on the properties within will perform different actions; with this any of these properties can be null, meaning don't perform this action.

I am trying to create a very simple to use API to do this in cases where some properties can be multiple levels deep, here is an example of the current implementation

service.PerformActions(DataFactory.GetNewData<ActionsInfo> (
    data => data.SomeParent = DataFactory.GetNewData<SomeParentInfo>(), 
    data => data.SomeParent.SomeProperty = "someValue" ));

This is a slightly simplified version and in real cases I some times have to setup multiple parent properties this way in order to set one string property at the bottom.

What I would like to do is adjust the code within the GetNewData method to handle instantiating these properties as needed so that the code could look like this:

service.PerformActions(DataFactory.GetNewData<ActionsInfo> (
    data => data.SomeParent.SomeProperty = "someValue" ));

Here is my current code for GetNewData:

public static T GetNewData<T>(params Action<T>[] actions)
{
    var data = Activator.CreateInstance<T>();

    foreach (var action in actions)
    {
        try
        {
            action(data);

        }
        catch (NullReferenceException)
        {
            throw new Exception("The property you are attempting to set is within a property that has not been set.");
        }
    }

    return data;
}

My first thought is to change the params array to be Expression<Action<T>>[] actions and somehow get a member expression for any of these parents that are null, which would allow me to use the activator to create an instance. However my experience with the more advanced features of Expression trees is slim at best.

The reason for attempting to make this API as simplistic as possible is that it is a UI testing framework that will eventually be used by non developers.

Edit: I want to add one further example of the current implementation to hopefully demonstrate that what I'm trying to do will provide for more readable code, yes there is a very slight 'side-effect' if I can pull this off but I would argue it is a helpful one.

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>(
    x => x.Property1 = ExampleDataFactory.GetNewData<Property1Type>(),
    x => x.Property1.Property2 = ExampleDataFactory.GetNewData<Property2Type>(),
    x => x.Property1.Property2.Property3 = ExampleDataFactory.GetNewData<Property3Type>(),
    x => x.Property1.Property2.Property3.Property4 = true);

Edit 2: The classes that I'm working with here are generated from Apache Thrift struct definitions and as such I have no control over them to be able to set up some kinda of smart constructor.

  • @puretppc I am curious as to why you disagreed with my formatting on the first two code blocks, would it not be more readable with the new lines? –  Jan 16 '14 at 22:43
  • Oh sorry I didn't really see that. Oh well it got edited back anyways. – puretppc Jan 16 '14 at 22:44
  • I would suggest not to mess around with the lambda expressions like you intent to, because it will hurt readibility of your code and invite misunderstandings. Like, everybody reading `x => x.MyProperty = value` expects that just x.MyProperty is set; and doesn't expect some other 'magic' thing taking place as well. –  Jan 16 '14 at 22:51
  • @elgonzo Though in a normal coding environment I would completely agree with you, in this case as I mentioned, substantially less technical people are going to be writing tests using this API. In the current situation they will need to know to instantiate each containing property, or they need to run the test and get the exception telling them they need to set some property (I could not find a way to tell them which property) –  Jan 16 '14 at 22:54
  • It might sound like a stupid question but why don't you just do `data.SomeParent = DataFactory.GetNewData();` right before executing `action(data)` inside your GetNewData method? (If you don't want this lambda expr./delegate passed through the API, it has to be somewhere behind your API facade anyway.) –  Jan 16 '14 at 23:03
  • @elgonzo Because that would not allow for that property to remain null if the test requires. I had at one point actually just iterated through all the properties and newed up anything that fit this description, the problem was that it caused some side affects in the tests because code was running that should not have been. –  Jan 16 '14 at 23:05
  • Why not? What would stop you to test for *null*, or to do a try-catch like you already do for the Action objects... –  Jan 16 '14 at 23:09
  • @elgonzo The data.SomeParent property should remain null if none of its properties are set. Each of these parent properties represents a section of the UI that is being tested. –  Jan 16 '14 at 23:12
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/45415/discussion-between-elgonzo-and-justinm) –  Jan 16 '14 at 23:13

2 Answers2

1

After getting an answer on my other question I now have a fully working solution for this, it isn't quite as simple syntax as I was originally aiming for, but it isn't bad.

 public static DataBuilder<T> GetNewData<T>() where T : class, new()
    {
        return new DataBuilder<T>();
    }

The DataBuilder Class:

public class DataBuilder<T>
{
    public readonly T data;

    public DataBuilder()
    {
        data = Activator.CreateInstance<T>();
    }

    public DataBuilder(T data)
    {
        this.data = data;
    }

    public DataBuilder<T> SetValue<T2>(Expression<Func<T, T2>> expression, T2 value)
    {
        var mExpr = GetMemberExpression(expression);

        var obj = Recurse(mExpr);
        var p = (PropertyInfo)mExpr.Member;
        p.SetValue(obj, value); 
        return this;
    }

    public T Build()
    {
        return data;
    }

    public object Recurse(MemberExpression expr)
    {
        if (expr.Expression.Type != typeof(T))
        {
            var pExpr = GetMemberExpression(expr.Expression);
            var parent = Recurse(pExpr);

            var pInfo = (PropertyInfo) pExpr.Member;
            var obj = pInfo.GetValue(parent);
            if (obj == null)
            {
                obj = Activator.CreateInstance(pInfo.PropertyType);
                pInfo.SetValue(parent, obj);
            }

            return obj;
        }
        return data;
    }

    private static MemberExpression GetMemberExpression(Expression expr)
    {
        var member = expr as MemberExpression;
        var unary = expr as UnaryExpression;
        return member ?? (unary != null ? unary.Operand as MemberExpression : null);
    }

    private static MemberExpression GetMemberExpression<T2>(Expression<Func<T, T2>> expr)
    {
        return GetMemberExpression(expr.Body);
    }
}

The Usage:

ExampleDataFactory.GetNewData<ServicesAndFeaturesInfo>()
            .SetValue(x=> x.Property1.EnumProperty, EnumType.Own)
            .SetValue(x=> x.Property2.Property3.Property4.BoolProperty, true)
            .Build();
-1

I think you could use the ExpandoObject or the ElasticObject.

ExpandoObject as far as I know it will get "transformed" into a dictionary ( Properties => Values ).

dburner
  • 1,007
  • 7
  • 22
  • I'm not sure how I could get an expando object from either the action I'm using now or even an Epression. –  Jan 16 '14 at 22:49
  • I don't like this much, it would require changing the existing type and it also loses compile-time safety (e.g. if you make a typo in the expression, it will still compile). – svick Jan 17 '14 at 15:52