简体   繁体   English

在运行时动态扩展类型?

[英]Dynamically extending a type at runtime?

I have the need to extend instances of various types at runtime. 我需要在运行时扩展各种类型的实例。 Most of the time, I need to work with instances of the original type, however in a few circumstances, I need to create kind of an extension-wrapper around those types that add a couple pieces of contextual information. 大多数时候,我需要处理原始类型的实例,但是在一些情况下,我需要围绕那些添加了几个上下文信息的类型创建一种扩展包装器。 Something along the lines of the following (which is not actually valid .NET/C# code...but it illustrates the point): 下面的内容(实际上并不是有效的.NET / C#代码......但它说明了这一点):

public abstract class BaseClass
{
  // ...
}

public class Concrete1: BaseClass
{
  // ...
}

public class Concrete2: BaseClass
{
  // ...
}

public class WrapperExtender<T>: T // Extending from T here is actually invalid!
  where T: BaseClass
{
    public WrapperExtender(T extensionTarget)
    {
        m_extensionTarget = extensionTarget;
    }

    private readonly T m_extensionTarget;

    public object ContextualReference { get; }
    public int ContextualValue { get; }

    // DERP: Would need to implement overrides of T here...buuut...can't...
}

// In use, special case:
var instance = new Concrete1();
var extendedInstance = new WrapperExtender(instance);

var processor = new SomeProcessorRequiringExtendedInstance();
processor.DoProcessing(extendedInstance);

Another example of this would probably be Microsoft Entity Framework v4.0, or nHibernate. 另一个例子可能是Microsoft Entity Framework v4.0或nHibernate。 Both of these frameworks provide dynamically extended instances of your entity types, wrapping them internally to provide, at runtime, the hooks required to keep a data/object/session context up to date with changes made to your entity instances. 这两个框架都提供了实体类型的动态扩展实例,在内部包装它们,以便在运行时提供保持数据/对象/会话上下文与实体实例所做更改所需的挂钩。 My needs are not nearly as complex, and the generics scenario above would work beautifully, if only there was a way to blend generics and dynamic typing somehow. 我的需求并不是那么复杂,如果只是某种方式将泛型和动态类型混合在一起,上面的泛型场景将会很好地工作。

Anyway, I'm hoping someone knows how to achieve the above scenario. 无论如何,我希望有人知道如何实现上述方案。 Or, perhaps even better, someone knows a better solution. 或者,甚至更好,有人知道更好的解决方案。 I don't care much for the idea of dynamically extending a type like that at runtime (it doesn't make as much sense as it does in the EF/nHibernate scenario.) At the moment, its the only thing I can really think of that will provide me with the information I need in the processor for each type passed in to DoProcessing. 我不太关心在运行时动态扩展类型的想法(它没有像在EF / nHibernate场景中那样有意义。)目前,它是我唯一能想到的东西这将为我提供处理器中传递给DoProcessing的每种类型所需的信息。

The problems that EF etc are solving is different, and relates to tihngs like lazy loading, etc. I'm simply not sure that the level of complexity that dynamic subclassing requires is worth it for this scenario. EF等解决的问题是不同的,并且与延迟加载等问题有关。我只是不确定动态子类化所需的复杂程度对于这种情况是值得的。 A few thoughts, though: 但是有一些想法:

  • have a property bag in your object for flexible additional properties; 在您的对象中有一个属性包,用于灵活的附加属性; if necessary the property-bag can be exposed to data-binding APIs via ICustomTypeDescriptor 如果需要,可以通过ICustomTypeDescriptor将property-bag暴露给数据绑定API
  • simply wrap your object in an implementation-specific tuple that contains the existing object and the additional properties (no subclassing) 只需将对象包装在一个特定于实现的元组中,该元组包含现有对象和其他属性(无子类化)

It is a shame that C# doesn't support "mixins", which would also be a nice way of implementing this type of thing with interfaces. 令人遗憾的是C#不支持“mixins”,这也是用接口实现这种类型的东西的好方法。

I know that this can be accomplished using dynamicproxy (which is what NHibernate uses to accomplish this task) which you can find out more about here: 我知道这可以使用dynamicproxy(这是NHibernate用来完成这个任务的东西)完成的,你可以在这里找到更多信息:

DynamicProxy Page DynamicProxy页面

DynamicProxy tutorial DynamicProxy教程

If all you need is some additional properties, why not just create a context property in BaseClass? 如果你只需要一些额外的属性,为什么不在BaseClass中创建一个context属性呢?

something like this, where ContextBag is either a generic collection class or specially defined context collection: 像这样的东西,其中ContextBag是泛型集合类或特殊定义的上下文集合:

Public ContextBag Context
{
   get;
   set;
}

When setting/accessing the context, you will be using syntax like this: 设置/访问上下文时,您将使用如下语法:

SubClass.Context.GetInt(ContextDefinition, ContextName);

SubClass.Context.Add(ContextDefinition, ContextName, ContextValue);

Found a better solution than temporarily extending. 找到比临时扩展更好的解决方案。 I created an actual context object that contained the state I needed available. 我创建了一个实际的上下文对象,其中包含我需要的状态。 Whenever that context exists, I initialize the context and set a static property that can be used to retrieve the context object from anywhere, alleviating the need to update all the dependencies of my larger process to take the context in as a parameter (which isn't always possible, as sometimes the calls are made in other contexts.) 每当上下文存在时,我初始化上下文并设置一个静态属性,可用于从任何地方检索上下文对象,从而减少更新我的大型进程的所有依赖关系以将上下文作为参数(不是'总是可能的,因为有时呼叫是在其他情况下进行的。)

public class SomeContext
{
    public SomeContext(object stateData1, object stateData2)
    {
        StateData1 = stateData1;
        StateData2 = stateData2;
    }

    public virtual object StateData1 { get; private set; }
    public virtual object StateData2 { get; private set; }

    [ThreadStatic]
    private static SomeContext m_threadInstance;    

    public static SomeContext Current
    {
        get
        {
            return m_threadInstance;
        }
        set
        {
            if (value != null && m_threadInstance != null) 
                throw new InvalidOperationException("This context has already been initialized for the current thread.");
            m_threadInstance = value;
        }
    }
}

public class SomeContextScope: IDisposable
{
    public SomeContextScope(object stateData1, object stateData2)
    {
        if (SomeContext.Current == null)
        {
            SomeContext context = new SomeContext(stateData1, stateData2);
            SomeContext.Current = context;
            m_contextCreated = true;
        }
    }

    private bool m_contextCreated;

    public void Dispose()
    {
        if (m_contextCreated)
        {
            SomeContext.Current = null;
        }
    }
}

public class ComplexProcessor
{
    public ComplexProcessor(...) // Lots of dependencies injected

    public void DoProcessing(BaseClass instance)
    {
        // do some work with instance

        if (SomeContext.Current != null)
        {
            // do contextually sensitive stuff for SomeContext with instance
            // call a dependency that does contextually sensitive stuff
        }

        // do some more work with instance
        // call a dependency that does contextually sensitive stuff

        if (SomeOtherContext.Current != null)
        {
            // do contextually sensitive stuff for SomeOtherContext with instance
            // call a dependency that does contextually sensitive stuff
        }

        // call a dependency that does contextually sensitive stuff
    }
}

// The original setup of the context and initiation of processing

public void SomeOperation(...)
{
    using (SomeContextScope scope = new SomeContextScope(stateData1, stateData2))
    {    
        // ... do some work

        var processor = complexProcessorFactory.CreateInstance();
        processor.DoProcesing(data);

        // ... do more work
    }
}

I like the way this works. 我喜欢它的工作方式。 Context is the state within which behavior executes. 上下文是行为执行的状态。 It has always felt clunky to me to have to pass contextual data around with other objects, and have dozens of methods or method overloads that take in and pass along various forms of contextual data. 我总是觉得笨拙的是必须将上下文数据与其他对象一起传递,并且有许多方法或方法重载,它们接收并传递各种形式的上下文数据。 By setting up a context object that is globally available for the duration of that context, my code is a lot cleaner, and my dependencies are more concise. 通过设置在该上下文持续时间内全局可用的上下文对象,我的代码更清晰,并且我的依赖关系更简洁。 It should be mockable too, since the Current property is read/write, I can create a mock context in a BDD specification or TDD unit test whenever one is required without a lot of hassle. 它也应该是可模拟的,因为Current属性是读/写的,我可以在BDD规范或TDD单元测试中创建一个模拟上下文,只要需要一个没有很多麻烦。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM