【问题标题】:Change object type at runtime maintaining functionality在运行时更改对象类型维护功能
【发布时间】:2011-08-05 23:34:12
【问题描述】:

长话短说

假设我有以下代码:

// 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&lt;T&gt;SecondObject&lt;T&gt; 这样的模板,这让我害怕使用反射来完成这样的任务!


现实中的问题

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

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

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

  • 我已经完成的事情

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

    伪代码中非常简化的 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 对象作为Variables 时,我最终遇到了一个大问题。我真的不知道如何允许开发人员使用我的 API 在任何需要的地方使用 Data 对象(就像在 WF4 中一样),然后将 Data 转换为 System.Activities.Variable


想到解决方案

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

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

  • 解决方案 1

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

  • 解决方案 2

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

  • 解决方案 3

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

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

【问题讨论】:

  • 你到底想用这个来完成什么?您是否尝试以某种方式或其他方式扩展 FirstObject 的功能?
  • @craig-suchanec 这是一个相当大的文本,但就像你说的,我真的需要深入解释它。恐怕我最初的问题只是冰山一角。
  • 您可能应该将此作为 WF4 问题重新发布,以便正确的专家看到它
  • 已经做到了,并为此赢得了拇指草袋:\ 没有一条评论。我猜是因为这与其说是 WF4 问题,不如说是编程方法的问题。

标签: c# reflection types runtime


【解决方案1】:

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

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

【讨论】:

  • @reed-copsey 这就是我现在正在做的事情。也许您可以查看我的上一次编辑并提供一些反馈。可能需要一点 WF4 知识。我试图以更简单的方式解决这个问题,并提取你们要说的任何内容,但恐怕只有提出真正的问题我才能真正解释自己,你可以给我正确的见解。谢谢!
【解决方案2】:

这被称为“鸭子打字”(如果它看起来像鸭子并且嘎嘎叫得像鸭子,您可以在其上调用方法,就好像它真的是鸭子一样)。将 myObject 声明为动态而不是特定类型,然后您应该一切顺利。

编辑:要清楚,这需要 .NET 4.0

dynamic myObject = new FirstObject();

// do stuff

myObject = new SecondObject();

// do stuff again

【讨论】:

  • @robert-levy 在我的情况下,我不能这样做主要是因为我必须用另一个对象的数据构造一个对象,而这只是冰山一角。无论如何,我已经用现实世界的问题编辑了最初的问题。希望你们能给我一些提示/见解。谢谢!
猜你喜欢
  • 2011-02-04
  • 1970-01-01
  • 1970-01-01
  • 2016-09-28
  • 2013-07-09
  • 2022-11-11
  • 2018-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多