代码之家  ›  专栏  ›  技术社区  ›  jrista

在运行时动态扩展类型?

  •  4
  • jrista  · 技术社区  · 15 年前

    我需要在运行时扩展各种类型的实例。大多数情况下,我需要处理原始类型的实例,但是在少数情况下,我需要围绕那些添加一些上下文信息的类型创建一种扩展包装器。以下几行的内容(实际上不是有效的.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);
    

    另一个例子可能是Microsoft Entity Framework v4.0或NHibernate。这两个框架都提供实体类型的动态扩展实例,在内部包装它们,以便在运行时提供保持数据/对象/会话上下文最新所需的钩子,以及对实体实例所做的更改。我的需求并没有那么复杂,如果有一种方法能够以某种方式混合泛型和动态类型的话,上面的泛型场景就可以很好地工作。

    不管怎样,我希望有人知道如何实现上述情况。或者,也许更好,有人知道更好的解决方案。我不太关心在运行时动态扩展这样一个类型的想法(它不像在ef/nhibernate场景中那样有意义)。目前,我唯一真正能想到的是,它将为传入到DoProcessing的每种类型提供我在处理器中需要的信息。

    4 回复  |  直到 15 年前
        1
  •  2
  •   Marc Gravell    15 年前

    ef等正在解决的问题是不同的,并且与延迟加载等技巧有关。我只是不确定动态子类化所需的复杂程度对于这种情况是否值得。不过,有一些想法:

    • 在对象中有一个属性包,用于灵活的附加属性;如有必要,可以通过 ICustomTypeDescriptor
    • 只需将对象包装在一个特定于实现的元组中,该元组包含现有对象和附加属性(无子类化)。

    很遗憾,C不支持“mixin”,这也是通过接口实现这类事情的一种好方法。

        2
  •  1
  •   apiguy    15 年前

    我知道这可以通过使用动态氧气(这是NHibernate用来完成此任务的)来完成,您可以在这里了解更多信息:

    DynamicProxy Page

    DynamicProxy tutorial

        3
  •  1
  •   Bill Yang    15 年前

    如果您只需要一些附加属性,为什么不在基类中创建一个上下文属性呢?

    类似这样,其中ContextBag是通用集合类或特殊定义的上下文集合:

    Public ContextBag Context
    {
       get;
       set;
    }
    

    设置/访问上下文时,您将使用如下语法:

    SubClass.Context.GetInt(ContextDefinition, ContextName);
    
    SubClass.Context.Add(ContextDefinition, ContextName, ContextValue);
    
        4
  •  0
  •   jrista    15 年前

    找到了比临时扩展更好的解决方案。我创建了一个实际的上下文对象,其中包含我需要的可用状态。每当上下文存在时,我初始化上下文并设置一个静态属性,该属性可用于从任何位置检索上下文对象,从而减少了更新较大进程的所有依赖项以将上下文作为参数的需要(这并不总是可能的,因为有时调用是在其他上下文中进行的)。

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

    我喜欢这种工作方式。上下文是行为执行的状态。在我看来,必须将上下文数据与其他对象一起传递总是很笨拙,并且有几十个方法或方法重载可以接收和传递各种形式的上下文数据。通过设置一个在该上下文期间全局可用的上下文对象,我的代码更干净,依赖关系也更简洁。它也应该是可模拟的,因为当前属性是读/写的,所以只要需要一个模拟上下文,我就可以在BDD规范或TDD单元测试中创建一个模拟上下文,而不会有太多麻烦。