在运行时动态扩展类型?

4

我需要在运行时扩展各种类型的实例。大多数情况下,我需要使用原始类型的实例,但在一些情况下,我需要创建一种扩展封装器来添加一些上下文信息。类似于以下示例(这实际上不是有效的.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中的类型的处理器所需的信息。


3
我感兴趣,请您详细介绍一下您正在做的事情? - Noon Silk
请看下面我的答案。我找到了一个完美运作、保持关注点分离且不会混淆依赖的解决方案。 - jrista
4个回答

2
EF等工具解决的问题是不同的,涉及到诸如延迟加载等内容。我不确定动态子类化所需的复杂程度是否值得为此场景付出代价。不过有几点想法:
  • 在您的对象中加入属性包以获取灵活的附加属性;如果必要,可以通过ICustomTypeDescriptor将属性包暴露给数据绑定API。
  • 仅用实现特定元组包装您的对象,其中包含现有对象和附加属性(无需子类化)。
很遗憾C#不支持“混合”功能,这也是使用接口实现此类事情的好方法。

我也很想拥有“mixins”。我想知道是否有可能利用C# 4.0的动态类型功能实现这样的东西…… - jrista
1
你可以通过在接口上实现扩展方法来实现一个简单的混合功能... - Sam Saffron

1
如果您只需要一些额外的属性,为什么不在BaseClass中创建一个上下文属性呢?
类似于这样的代码,其中ContextBag可以是通用集合类或特别定义的上下文集合:
Public ContextBag Context
{
   get;
   set;
}

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

SubClass.Context.GetInt(ContextDefinition, ContextName);

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

数据是有上下文的...它不属于基类,也不应该在执行代码不处于“上下文内”时随时可用。因此,在需要额外数据并且可用的调用时,需要暂时扩展类型以添加附加数据。 - jrista
这不应该阻止基类创建一个上下文包来保存上下文信息。也许我没有明确指出Context类应该是一个集合,我已经修改了示例代码以反映这一点。 - Bill Yang
这是一个有趣的想法。在不同的环境下,它可能是唯一的选择。然而,我仍然不喜欢它将短期“上下文”数据或这些类型的所有实例中的数据包放在一起的事实。如果这是始终可用的信息(即非上下文),那么我肯定会将其添加到基础中。 - jrista

1

我知道可以使用动态代理来实现这个功能(NHibernate就是使用它来完成这个任务的),你可以在这里了解更多信息:

DynamicProxy 页面

DynamicProxy 教程


我以前用过DynamicProxy。这是一个相当强大的小型库。对于我的当前需求来说有点过头了... :\ - jrista

0
发现了比临时扩展更好的解决方案。我创建了一个实际的上下文对象,其中包含我需要的状态。每当该上下文存在时,我都会初始化上下文并设置一个静态属性,可以用于从任何地方检索上下文对象,从而减轻了更新我的大型进程的所有依赖项以将上下文作为参数传递的需要(这并不总是可能的,因为有时调用是在其他上下文中进行的)。
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单元测试中创建一个模拟上下文,无需太多麻烦。


1
请注意,静态上下文对于多线程代码(例如ASP.NET、WCF等或任何其他具有多个线程的内容)来说可能是一场噩梦。至少要考虑使用[ThreadStatic],但在我看来,这不是解决此问题的最佳方法。 - Marc Gravell
(仅作为示例,因为您提到TDD-某些测试运行程序将使用多个线程...)某些测试运行器将使用多个线程。 - Marc Gravell
实际上,在我的实现中,它是ThreadStatic,并且永远不会在ASP.NET或WCF环境中使用。这是一个完全安装在用户桌面上的Windows WPF应用程序,不需要连接任何服务或网站。有线程,但我对它们有非常严格的控制。 - jrista
我已更新我的示例以反映我的实际实现。仍然有点牵强,但它展示了这个想法。 - jrista

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