在运行时更改对象类型并保持功能性。

4

长话短说

假设我有以下代码:

// 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,但是SecondObjectsealed 的,完全超出了我的范围!

我是否可以通过反射实现这一点?如何实现?您认为需要多少工作量才能完成此操作?显然,两个类都可以比上面的示例复杂得多。

两个类都可以具有像 FirstObject<T>SecondObject<T> 这样的模板,这让我想使用反射来完成这样的任务感到害怕!


现实问题

为了简单起见并尝试提取一些解决问题的知识,我已经尽力陈述我的问题,但通过查看答案,我认为您需要了解我的真正问题来帮助我,因为更改对象类型只是冰山一角。

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

现在,我正在编写中间层,将该API转换为WF4以通过CLR运行工作流程。

  • 我已经完成的工作

    在这个阶段,API的概念与WF4中的ActivityStates与输入/输出Arguments和通过其参数运行的Data(Variables)相似。极简化的伪代码:

    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时,我遇到了一个大问题。 我真的不知道如何允许开发人员在我


    解决方案浮现

    如果您现在理解我的问题,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。尝试用Variable/Argument代码替换使用Data/Argument的代码。这对我来说就像噩梦一样。我对System.reflection.Emit几乎一无所知,即使我学会了,我的猜测是这可能需要很长时间...甚至可能无法完成。

    抱歉如果我引入了更大的问题,但我真的陷入困境了,需要一个提示/路径。


1
你究竟想通过这个做什么?你是想以某种方式扩展FirstObject的功能还是其他什么? - Craig Suchanec
@craig-suchanec 这是一篇相当长的文章,但正如你所说,我确实需要深入解释。恐怕我的初始问题只是冰山一角。 - Joao
你应该将此问题重新发布为WF4问题,以便正确的专家看到它。 - Robert Levy
已经完成并获得了Thumbleweed徽章:\没有一个评论。我想这是因为它不是一个WF4问题,而更多的是编程方法的问题。 - Joao
2个回答

8
这被称为“鸭子类型”(如果它看起来像一只鸭子,嘎嘎叫起来像一只鸭子,你可以像真的鸭子一样对其调用方法)。将myObject声明为动态类型而不是特定类型,然后您就可以继续使用它了。
编辑:要明确,这需要.NET 4.0。 dynamic myObject = new FirstObject(); // 做一些事情 myObject = new SecondObject(); // 再次做一些事情

@robert-levy 在我的情况下,我主要无法这样做,因为我必须使用另一个对象的数据构建一个对象,这只是冰山一角。无论如何,我已经编辑了最初的问题,并附上了真实世界的问题。希望你们能给我一些跟进的提示/见解。谢谢! - Joao

3

对于这个问题,反射并不一定是正确的任务。如果SecondObject不在您的控制范围内,则最好的选择可能是创建一个扩展方法,该方法实例化一个新副本并逐个属性复制数据。

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


@reed-copsey,这就是我现在正在做的事情。也许您可以检查一下我的最后编辑并给予一些反馈。可能需要一些WF4知识。我试图以更简单的方式来解决问题,并提取出你们所说的任何内容,但我担心只有呈现真正的问题才能真正解释自己,让您能够给我适当的见解。谢谢! - Joao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接