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.OneM开发者_如何转开发ethod 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/OutArguments
andData
(Variables
) running through theActivityStates
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 toSystem.Activities.Variable
translation. The problem is WF4 separates the workflow execution from the context. Because of that bothArguments
andVariables
areLocationReferences
that must be accessed throughvar.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 asVariable
s. I really don't know how to allow the developer, using my API, to useData
objects wherever who wants(just like in WF4) and later translate thatData
toSystem.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 ofCacheMetadata()
function to specifyArguments
andVariables
. The problem remains because they are both accessed throughvar.Get(context)
.Solution 2
Give my
Data
class its ownGet()
function. It would be only an abstract method, without logic inside that it would, somehow, translate toGet()
function ofSystem.Activities.Variable
. Is this even possible using C#? Guess not! Another problem is that aVariable.Get()
has one parameter.Solution 3
The worst solution that I thought of was
CIL-manipulation
. Try to replace the code whereData/Argument
is used withVariable/Argument
code. This smells like a nightmare to me. I know next to nothing aboutSystem.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.
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
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.
精彩评论