ResolveVariableType/Value for object variables

Mar 11, 2008 at 9:00 AM
Hello,

Consider this Flee expression:

"x.b + y.b"

Let's say we added x and y as variables (they are objects). However, neither x or y have a method/field called 'b'.

In that cases, I understand we can use the events ResolveVariableType/Value. But, those events are not "parent-variable-sensitive", so when called I got only "b" as information, but I don't know for which variable I should evaluate it.

In my scenario I want to implement dynamic variables taken out of a HashTable. Both "x" and "y" objects implement some GetDynamicPropertyType and GetDynamicPropertyValue. If in the event I could get the object/variable (object "x" or "y"), I could call to my GetDynamic... functions. But, that information is missing.

If it is not clear enough, let me know and I will produce a little sample project. Am I missing something? Is there any way to do what I want?

Thank you!
Coordinator
Mar 12, 2008 at 2:53 AM
Can you post the sample project? I think I know what you need but I want to make sure we're on the same page. I don't think you can attach a file to a discussion post so you might have to create a new issue.
Mar 12, 2008 at 12:26 PM
    class MyClass
    {
        public void Do ()
        {
            ExpressionContext context = new ExpressionContext();
            context.Variables.Add("x", new MyInteger());
            context.Variables.Add("y", new MyString());
            context.Variables.ResolveVariableType += new EventHandler<ResolveVariableTypeEventArgs> (Variables_ResolveVariableType);
            context.Variables.ResolveVariableValue += new EventHandler<ResolveVariableValueEventArgs> (Variables_ResolveVariableValue);
            IDynamicExpression eDynamic = ExpressionFactory.CreateDynamic("x.b + y.b", context);
            eDynamic.Evaluate();
        }
 
        void Variables_ResolveVariableType (object sender, ResolveVariableTypeEventArgs e)
        {
            // At this point:
            // sender == VariableCollection
            // e.VariableName == "b" <--- which "b"????
            //
            // if "e.ResolveContext" was my object (either x or y), I could do:
            // e.VariableType = ((IDynamicPropertyProvider) (e.ResolveContext)).GetDynamicPropertyType;
        }
        void Variables_ResolveVariableValue (object sender, ResolveVariableValueEventArgs e)
        {
            // see above
        }
 
    }
 
    public interface IDynamicPropertyProvider
    {
        Type GetDynamicPropertyType (string propertyName);
        object GetDynamicPropertyValue (string propertyName);
    }
 
    public class MyInteger : IDynamicPropertyProvider
    {
        public Type GetDynamicPropertyType (string propertyName)
        {
            if (propertyName == "b")
            {
                return typeof (int);
            }
            return null;
        }
 
        public object GetDynamicPropertyValue (string propertyName)
        {
            if (propertyName == "b")
            {
                return 3;
            }
            return null;
        }
 
    }
 
    public class MyString : IDynamicPropertyProvider
    {
        public Type GetDynamicPropertyType (string propertyName)
        {
            if (propertyName == "b")
            {
                return typeof (string);
            }
            return null;
        }
 
        public object GetDynamicPropertyValue (string propertyName)
        {
            if (propertyName == "b")
            {
                return "text";
            }
            return null;
        }
    }
Mar 12, 2008 at 12:26 PM
Edited Mar 12, 2008 at 12:27 PM
<deleted because it was dupped (again?)>
Coordinator
Mar 13, 2008 at 9:01 PM
It looks like what you want is the ability to reference "virtual" properties in an expression. I just implemented this in 0.9.17.2 from the List of available functions discussion. It works by using the built-in .NET TypeDescriptor framework.

Here's the code that does the same thing as your sample.
using System;
using System.ComponentModel;
using Ciloci.Flee;
using System.Collections.Generic;
 
class Module1
{
 
    public static void Main()
    {
        // Add our custom provider to the MyString and MyInteger types
        FleeTypeDescriptionProvider provider = new FleeTypeDescriptionProvider(TypeDescriptor.GetProvider(typeof(MyString)));
        TypeDescriptor.AddProvider(provider, typeof(MyString));
        provider = new FleeTypeDescriptionProvider(TypeDescriptor.GetProvider(typeof(MyInteger)));
        TypeDescriptor.AddProvider(provider, typeof(MyInteger));
 
        ExpressionContext context = new ExpressionContext();
        context.Variables.Add("x", new MyInteger());
        context.Variables.Add("y", new MyString());
        IDynamicExpression e = ExpressionFactory.CreateDynamic("x.b", context);
        object result = e.Evaluate();
    }
 
}
 
public class MyString
{
 
}
 
public class MyInteger
{
 
}
 
public class FleeTypeDescriptionProvider : TypeDescriptionProvider
{
 
    private TypeDescriptionProvider MyParent;
 
    public FleeTypeDescriptionProvider(TypeDescriptionProvider parent)
        : base(parent)
    {
        MyParent = parent;
    }
 
    public override System.ComponentModel.ICustomTypeDescriptor GetTypeDescriptor(System.Type objectType, object instance)
    {
        // Return our custom type descriptor
        return new FleeCustomTypeDescriptor(MyParent.GetTypeDescriptor(objectType), objectType);
    }
}
 
public class FleeCustomTypeDescriptor : CustomTypeDescriptor
{
 
    private Type MyComponentType;
 
    public FleeCustomTypeDescriptor(ICustomTypeDescriptor parent, Type componentType)
        : base(parent)
    {
        MyComponentType = componentType;
    }
 
    public override System.ComponentModel.PropertyDescriptorCollection GetProperties()
    {
        PropertyDescriptorCollection props = base.GetProperties();
        List<PropertyDescriptor> list = new List<PropertyDescriptor>();
        foreach (PropertyDescriptor pd in props)
        {
            list.Add(pd);
        }
 
        // Add our custom property
        list.Add(new FleeCustomProperty("b", MyComponentType));
 
        props = new PropertyDescriptorCollection(list.ToArray());
        return props;
    }
}
 
public class FleeCustomProperty : PropertyDescriptor
{
 
    private Type MyObjectType;
 
    public FleeCustomProperty(string name, Type objectType)
        : base(name, null)
    {
        MyObjectType = objectType;
    }
 
    public override bool CanResetValue(object component)
    {
        return false;
    }
 
    public override System.Type ComponentType
    {
        get { return MyObjectType; }
    }
 
    // Could call GetDynamicPropertyValue here instead
    public override object GetValue(object component)
    {
        if (object.ReferenceEquals(component.GetType(), typeof(MyString)))
        {
            return "Flee!";
        }
        else if (object.ReferenceEquals(component.GetType(), typeof(MyInteger)))
        {
            return 100;
        }
        else
        {
            return null;
        }
    }
 
    public override bool IsReadOnly
    {
 
        get { return true; }
    }
 
    // Required by Flee
    public override System.Type PropertyType
    {
        get
        {
            if (object.ReferenceEquals(MyObjectType, typeof(MyString)))
            {
                return typeof(string);
            }
            else if (object.ReferenceEquals(MyObjectType, typeof(MyInteger)))
            {
                return typeof(int);
            }
            else
            {
                return null;
            }
        }
    }
 
    public override void ResetValue(object component)
    {
 
    }
 
    public override void SetValue(object component, object value)
    {
 
    }
 
    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }
}

And, on a side note, Flee should only be allowing the ResolveVariableType/Value events to be called on a variable and not a property (ie: it shouldn't work for anyting after a x.). I'll fix that in the next release.
Mar 25, 2008 at 1:22 PM
Sorry for answering this late.

In your solution, you need to know exactly how many properties exist for every object (you need them at "// Add our custom property"). However, in my scenario I do not know how many properties exists, nor their types. All that I know is that the object can give me types/values on request, not all the types/values but only those that I ask for. So I just need to delegate the "give-me-the-type" and "give-me-the-value" functionality to the object.

However with a little trick/workaround before sending the expression to Flee I managed to do what I wanted to, so this is not a problem for me anymore. However, just for the discussion, why don't you want the ResolveVariableType/Value events to be called on a property? Why is it that bad?
Coordinator
Mar 27, 2008 at 1:09 AM
>However, just for the discussion, why don't you want the ResolveVariableType/Value events to be called on a property? Why is it that bad?

I'm looking at it from the way I originally implemented it (in which it was only meant to work on variables). I thought that putting in the virtual properties would eliminate the need to have on-demand properties but it looks like both have their uses.

I'm curious, what workaround did you use to solve your problem?
Mar 27, 2008 at 12:00 PM
In my project we make a pre-processing step with the expression, so I am able to change "a.b" to "MyCall_a_b()". In the execution step, I hook into the Resolve/Invoke-functions, and when I see the call to "MyCall_a_b()" I call "a.GetDynamicPropertyxxxx("b")". This is valid in my situation because I know that variables will never have an underscore on them, but you get the point.

It is very long workaround, and I still think that if ResolveVariable could give some kind of context it should be nicer and easier, but still, it works... well, it does not work now just because of Common Language Runtime detected an invalid program :-)