Can I evaluate an expression in a way to determine

2019-05-03 17:41发布

问题:

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.

回答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();


回答2:

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 ).