Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change object type at runtime maintaining functionality

Long story short

Say I have the following code:

// a class like this 
class FirstObject {
    public Object OneProperty {
        get;
        set;
    }

    // (other properties)

    public Object OneMethod() {
        // logic
    }
}

// and another class with properties and methods names 
// which are similar or exact the same if needed 
class SecondObject {
    public Object OneProperty {
        get;
        set;
    }

    // (other properties)

    public Object OneMethod(String canHaveParameters) {
        // logic
    }
}

// the consuming code would be something like this 
public static void main(String[] args) {
    FirstObject myObject=new FirstObject();

    // Use its properties and methods
    Console.WriteLine("FirstObject.OneProperty value: "+myObject.OneProperty);
    Console.WriteLine("FirstObject.OneMethod returned value: "+myObject.OneMethod());

    // Now, for some reason, continue to use the
    // same object but with another type
    // -----> CHANGE FirstObject to SecondObject HERE <-----

    // Continue to use properties and methods but
    // this time calls were being made to SecondObject properties and Methods
    Console.WriteLine("SecondObject.OneProperty value: "+myObject.OneProperty);
    Console.WriteLine("SecondObject.OneMethod returned value: "+myObject.OneMethod(oneParameter));
}

Is it possible to change FirstObject type to SecondObject and continue to use it's properties and methods?

I've total control over FirstObject, but SecondObject is sealed and totally out of my scope!

May I achieve this through reflection? How? What do you think of the work that it might take to do it? Obviously both class can be a LOT more complex than the example above.

Both class can have templates like FirstObject<T> and SecondObject<T> which is intimidating me to use reflection for such a task!


Problem in reality

I've tried to state my problem the easier way for the sake of simplicity and to try to extract some knowledge to solve it but, by looking to the answers, it seems obvious to me that, to help me, you need to understand my real problem because changing object type is only the tip of the iceberg.

I'm developing a Workflow Definition API. The main objective is to have a API able to be reusable on top of any engine I might want to use(CLR through WF4, NetBPM, etc.).

By now I'm writing the middle layer to translate that API to WF4 to run workflows through the CLR.

  • What I've already accomplished

    The API concept, at this stage, is somehow similar to WF4 with ActivityStates with In/Out Arguments and Data(Variables) running through the ActivityStates using their arguments.

    Very simplified API in pseudo-code:

    class Argument {
        object Value;
    }
    
    class Data {
        String Name;
        Type ValueType;
        object Value;
    }
    
    class ActivityState {
        String DescriptiveName;
    }
    
    class MyIf: ActivityState {
        InArgument Condition;
        ActivityState Then;
        ActivityState Else;
    }
    
    class MySequence: ActivityState {
        Collection<Data> Data;
        Collection<ActivityState> Activities;
    }
    

    My initial approach to translate this to WF4 was too run through the ActivitiesStates graph and do a somehow direct assignment of properties, using reflection where needed.

    Again simplified pseudo-code, something like:

    new Activities.If() {
        DisplayName=myIf.DescriptiveName,
        Condition=TranslateArgumentTo_WF4_Argument(myIf.Condition),
        Then=TranslateActivityStateTo_WF4_Activity(myIf.Then),
        Else=TranslateActivityStateTo_WF4_Activity(myIf.Else)
    }
    
    new Activities.Sequence() {
        DisplayName=mySequence.DescriptiveName,
        Variables=TranslateDataTo_WF4_Variables(mySequence.Variables),
        Activities=TranslateActivitiesStatesTo_WF4_Activities(mySequence.Activities)
    }
    

    At the end of the translation I would have an executable System.Activities.Activity object. I've already accomplished this easily.

  • The big issue

    A big issue with this approach appeared when I began the Data object to System.Activities.Variable translation. The problem is WF4 separates the workflow execution from the context. Because of that both Arguments and Variables are LocationReferences that must be accessed through var.Get(context) function for the engine to know where they are at runtime.

    Something like this is easily accomplished using WF4:

    Variable<string> var1=new Variable<string>("varname1", "string value");
    Variable<int> var2=new Variable<int>("varname2", 123);
    
    return new Sequence {
        Name="Sequence Activity",
        Variables=new Collection<Variable> { var1, var2 },
        Activities=new Collection<Activity>(){
            new Write() {
                Name="WriteActivity1",
                Text=new InArgument<string>(
                    context => 
                        String.Format("String value: {0}", var1.Get(context)))
            },
            new Write() {
                //Name = "WriteActivity2",
                Text=new InArgument<string>(
                    context => 
                        String.Format("Int value: {0}", var2.Get(context)))
            }
        }
    };
    

    but if I want to represent the same workflow through my API:

    Data<string> var1=new Data<string>("varname1", "string value");
    Data<int> var2=new Data<int>("varname2", 123);
    
    return new Sequence() {
        DescriptiveName="Sequence Activity",
        Data=new Collection<Data> { var1, var2 },
        Activities=new Collection<ActivityState>(){
            new Write() {
                DescriptiveName="WriteActivity1",
                Text="String value: "+var1 // <-- BIG PROBLEM !!
            },
            new Write() {
                DescriptiveName="WriteActivity2",
                Text="Int value: "+Convert.ToInt32(var2) // ANOTHER BIG PROBLEM !!
            }
        }
    };
    

    I end up with a BIG PROBLEM when using Data objects as Variables. I really don't know how to allow the developer, using my API, to use Data objects wherever who wants(just like in WF4) and later translate that Data to System.Activities.Variable.


Solutions come to mind

If you now understand my problem, the FirstObject and SecondObject are the Data and System.Activities.Variable respectively. Like I said translate Data to Variable is just the tip of the iceberg because I might use Data.Get() in my code and don't know how to translate it to Variable.Get(context) while doing the translation.

Solutions that I've tried or thought of:

  • Solution 1

    Instead of a direct translation of properties I would develop NativeActivites for each flow-control activity(If, Sequence, Switch, ...) and make use of CacheMetadata() function to specify Arguments and Variables. The problem remains because they are both accessed through var.Get(context).

  • Solution 2

    Give my Data class its own Get() function. It would be only an abstract method, without logic inside that it would, somehow, translate to Get() function of System.Activities.Variable. Is this even possible using C#? Guess not! Another problem is that a Variable.Get() has one parameter.

  • Solution 3

    The worst solution that I thought of was CIL-manipulation. Try to replace the code where Data/Argument is used with Variable/Argument code. This smells like a nightmare to me. I know next to nothing about System.reflection.Emit and even if I learn it my guess is that it would take ages ... and might not even be possible to do it.

Sorry if I ended up introducing a bigger problem but I'm really stuck here and desperately needing a tip/path to go on.

like image 427
Joao Avatar asked Dec 21 '22 15:12

Joao


2 Answers

This is called "duck typing" (if it looks like a duck and quacks like a duck you can call methods on it as though it really were a duck). Declare myObject as dynamic instead of as a specific type and you should then be good to go.

EDIT: to be clear, this requires .NET 4.0

dynamic myObject = new FirstObject();

// do stuff

myObject = new SecondObject();

// do stuff again

like image 56
Robert Levy Avatar answered Feb 15 '23 20:02

Robert Levy


Reflection isn't necessarily the right task for this. If SecondObject is out of your control, your best option is likely to just make an extension method that instantiates a new copy of it and copies across the data, property by property.

You could use reflection for the copying process, and work that way, but that is really a separate issue.

like image 28
Reed Copsey Avatar answered Feb 15 '23 21:02

Reed Copsey