2009-08-23 187 views
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); 

另外一個例子很可能是微軟實體框架V4.0,或NHibernate的。這兩個框架都提供了實體類型的動態擴展實例,將它們內部包裝起來,以便在運行時提供保持數據/對象/會話上下文所需的鉤子,以及對實體實例所做的更改。我的需求並不那麼複雜,上面的泛型方案可以很好地工作,只要有一種方法可以將泛型和動態類型混合起來。

無論如何,我希望有人知道如何實現上述情況。或者,甚至更好,有人知道更好的解決方案。我並不在乎在運行時動態擴展類型的想法(它不像EF/nHibernate場景中那麼有意義)。目前,我唯一能想到的就是它這將爲我提供我在處理器中爲每個傳入DoProcessing的類型所需的信息。

+3

我很感興趣,你可以提供更多關於你在做什麼的細節? – 2009-08-23 07:04:10

+0

看看下面的答案。我想出了一個完美的解決方案,保持關注點分離,並且不會篡改依賴關係。 – jrista 2009-08-23 07:15:30

回答

0

找到比臨時擴展更好的解決方案。我創建了一個包含我需要的狀態的實際上下文對象。無論何時該上下文存在,我都會初始化上下文並設置一個靜態屬性,該屬性可用於從任何位置檢索上下文對象,從而減少更新我的較大進程的所有依賴關係以將上下文作爲參數進行更新的需要(這不是「 t總是可能的,因爲有時這些呼叫是在其他情況下進行的。)

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

我喜歡這種方式。上下文是行爲執行的狀態。我總是感到笨拙,不得不傳遞其他對象的背景數據,並且有許多方法或方法重載,它們會接收和傳遞各種形式的上下文數據。通過設置一個在該上下文期間全局可用的上下文對象,我的代碼更加簡潔,並且我的依賴關係更加簡潔。它也應該是可嘲弄的,因爲Current屬性是可讀/寫的,所以我可以在BDD規範或TDD單元測試中創建一個模擬上下文,只要不需要很多麻煩即可。

+1

請注意,* static *上下文將是線程代碼的噩夢(比如ASP.NET,WCF等 - 或者其他任何具有多線程的東西)。至少,考慮'[ThreadStatic]' - 但IMO不是解決這個問題的最好方法。 – 2009-08-23 08:02:21

+0

(就像一個例子,因爲你提到了TDD--一些測試運行者會使用多個線程......) – 2009-08-23 08:03:36

+0

它實際上是我的實現中的ThreadStatic,它永遠不會用在ASP.NET或WCF環境中。它是一個完全安裝在用戶桌面上的Windows WPF應用程序,無需連接任何服務或網站。有線程,但我對它們有非常嚴格的控制。 – jrista 2009-08-23 21:38:43

1

我知道,這可以使用dynamicproxy(這是NHibernate的使用來完成此任務),你可以找到更多關於這裏來完成:

DynamicProxy Page

DynamicProxy tutorial

+0

我之前使用過DynamicProxy。非常強大的小圖書館。我當前需要的一種矯枉過正的行爲......:\ – jrista 2009-08-23 06:55:15

1

如果你需要的是一些額外的屬性,爲什麼不只是在BaseClass中創建一個上下文屬性?

這樣的事情,在那裏ContextBag或者是一個泛型集合類或特殊定義的環境信息收集:

Public ContextBag Context 
{ 
    get; 
    set; 
} 

當設置/訪問的背景下,您將使用語法如下:

SubClass.Context.GetInt(ContextDefinition, ContextName); 

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

數據是上下文的......它不屬於基類,在任何時候執行代碼都不是「在上下文中」它是可用的。因此,只有在需要和可用附加數據的情況下進行呼叫時,才需要使用附加數據臨時擴展類型。 – jrista 2009-08-23 06:54:35

+0

這不應該阻止基類創建上下文包來保存上下文信息。也許我不夠明確,Context類應該是一個集合,我修改了示例代碼以反映這一點。 – 2009-08-24 02:37:14

+0

它是一個有趣的想法。在不同的環境中,它可能是唯一的選擇。然而,我仍然不喜歡這樣的事實,即在這些類型的所有情況下,它都會提供短期的「上下文」數據,或者提供這種數據。如果這些信息始終可用(即無關聯),那麼我肯定會將其添加到基地。 – jrista 2009-08-24 04:41:34

2

EF等解決的問題是不同的,並且涉及像延遲加載等tihngs。我不確定動態子類化所需的複雜程度是否值得這種情況。一些想法,雖然:

  • 有一個物業袋在您的對象靈活的附加屬性;如果有必要的財產袋可以通過ICustomTypeDescriptor
  • 被暴露在數據綁定API的簡單包裝你的對象包含現有對象和附加屬性(無子類)

它是具體實現的元組這是C#不支持「mixins」的一個恥辱,這也是通過接口實現這種類型的東西的一個好方法。

+0

我很想擁有「mixins」。我想知道用C#4.0的動態類型功能是否可以實現這樣的功能...... – jrista 2009-08-23 07:11:54

+1

您可以通過在接口上實現擴展方法來讓一個窮人的mixin ... – 2009-08-24 02:47:08