在运行时更改对象类型以维护功能

本文关键字:维护 功能 类型 对象 运行时 | 更新日期: 2023-09-27 17:50:10

长话短说

假设我有以下代码:

// 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));
}

是否可以将FirstObject类型更改为SecondObject并继续使用其属性和方法?

我完全控制了FirstObject,但SecondObject密封的,完全超出了我的范围!

我可以通过反思来实现这一点吗?怎样你认为做这件事可能需要做什么工作?显然,这两个类都可能比上面的例子复杂得多。

这两个类都可以有FirstObject<T>SecondObject<T>这样的模板,这让我不敢在这样的任务中使用反射!


现实中的问题

为了简单起见,我试图用更简单的方式陈述我的问题,并试图提取一些知识来解决它,但通过寻找答案,我似乎很明显,为了帮助我,你需要了解我的真正问题,因为改变对象类型只是冰山一角。

我正在开发工作流定义API。主要目标是在我可能想要使用的任何引擎(CLR通过WF4、NetBPM等(上拥有一个能够可重复使用的API

到目前为止,我正在编写中间层,以将API转换为WF4,从而通过CLR运行工作流。

  • 我已经完成的

    在这个阶段,API概念在某种程度上类似于带有ActivityStates的WF4,带有使用其参数通过ActivityStates运行的输入/输出ArgumentsData(Variables(。

    伪代码:中非常简化的API

    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;
    }
    

    我最初将其转换为WF4的方法也是通过ActivitiesStates图运行,并以某种方式直接分配属性,在需要的地方使用反射。

    再次简化伪代码,类似于:

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

    在翻译结束时,我会有一个可执行的System.Activities.Activity对象。我已经轻而易举地做到了。

  • 大问题

    当我开始将Data对象转换为System.Activities.Variable时,这种方法出现了一个大问题。问题是WF4将工作流执行与上下文分离。因此,ArgumentsVariables都是LocationReferences,必须通过var.Get(context)函数才能让引擎知道它们在运行时的位置。

    使用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)))
            }
        }
    };
    

    但是如果我想通过我的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 !!
            }
        }
    };
    

    当使用Data对象作为Variable s时,我最终会遇到一个大问题。我真的不知道如何允许开发人员使用我的API,在任何需要的地方使用Data对象(就像在WF4中一样(,然后将Data转换为System.Activities.Variable


脑海中浮现的解决方案

如果您现在理解了我的问题,那么FirstObjectSecondObject分别是DataSystem.Activities.Variable。正如我所说,将Data翻译成Variable只是冰山一角,因为我可能在代码中使用Data.Get(),但在翻译时不知道如何将其翻译成Variable.Get(context)

我尝试过或想过的解决方案:

  • 解决方案1

    我将为每个流控制活动(IfSequenceSwitch…(开发NativeActivites,并使用CacheMetadata()函数指定ArgumentsVariables,而不是直接转换属性。问题仍然存在,因为它们都是通过var.Get(context)访问的。

  • 解决方案2

    给我的Data类赋予它自己的Get()函数。这只是一个抽象的方法,里面没有逻辑,它会以某种方式转换为System.Activities.VariableGet()函数。这甚至可以使用C#吗?我猜不会!另一个问题是Variable.Get()具有一个参数。

  • 解决方案3

    我想到的最坏的解决方案CIL-manipulation。尝试将使用Data/Argument的代码替换为Variable/Argument代码。这对我来说就像一场噩梦。我对System.reflection.Emit几乎一无所知,即使我学会了,我猜这也需要很长时间。。。甚至不可能做到。

很抱歉,如果我最终引入了一个更大的问题,但我真的被困在这里,迫切需要一个提示/路径来继续。

在运行时更改对象类型以维护功能

这被称为"鸭子打字"(如果它看起来像鸭子,嘎嘎作响,你可以像鸭子一样调用它的方法(。将myObject声明为动态的,而不是特定的类型,这样就可以开始了。

编辑:为了清楚起见,这需要.NET 4.0

dynamic myObject = new FirstObject();

// do stuff

myObject = new SecondObject();

// do stuff again

反射不一定是正确的任务。如果SecondObject超出了您的控制范围,您的最佳选择可能只是创建一个扩展方法,该方法实例化它的新副本,并逐个属性地跨数据进行复制。

您可以在复制过程中使用反射,并以这种方式工作,但这实际上是一个单独的问题。