2011-07-11 104 views
26

我見過關於如果可以在C#中創建混合使用的各種問題,並且他們經常被引導到codeplex上的重新混合項目。但是,我不知道我是否喜歡「完整界面」的概念。理想情況下,我會擴展類,像這樣:與C#4.0混合使用

[Taggable] 
    public class MyClass 
    { 
     .... 
    } 

通過簡單地將加標籤界面,我可以通過某種對象工廠的創建MyClass類型的對象。返回的實例將具有在MyClass中定義的所有成員以及通過添加標記屬性(如標記集合)提供的所有成員。這似乎很容易使用C#4.0(動態關鍵字)來實現。重新混合項目使用C#3.5。有沒有人有任何好的方法來通過C#4.0擴展對象而不改變它們自己的類?謝謝。

+0

部分類也許?擴展方法? –

+0

通過使用擴展方法,我會寫更多顯式的代碼,將MyClass與我的標記相關的代碼(當然,除了taggable屬性)結合在一起。我想在沒有明確結婚的情況下這樣做。 – ActionJackson

回答

65

您可以在C#4.0中創建類似mixin的構造,而不使用動態,在接口上使用擴展方法並使用ConditionalWeakTable類來存儲狀態。看看這個想法here

下面是一個例子:

public interface MNamed { 
    // required members go here 
} 
public static class MNamedCode { 
    // provided methods go here, as extension methods to MNamed 

    // to maintain state: 
    private class State { 
    // public fields or properties for the desired state 
    public string Name; 
    } 
    private static readonly ConditionalWeakTable<MNamed, State> 
    _stateTable = new ConditionalWeakTable<MNamed, State>(); 

    // to access the state: 
    public static string GetName(this MNamed self) { 
    return _stateTable.GetOrCreateValue(self).Name; 
    } 
    public static void SetName(this MNamed self, string value) { 
    _stateTable.GetOrCreateValue(self).Name = value; 
    } 
} 

使用方法如下:

class Order : MNamed { // you can list other mixins here... 
    ... 
} 

... 

var o = new Order(); 
o.SetName("My awesome order"); 

... 

var name = o.GetName(); 

使用屬性的問題是,你不能從類流泛型參數的混入。你可以用標記界面來做到這一點。

+8

這是很棒的廢話。它如何得票太少?沒有多重繼承的語言使得編寫mixin非常困難。這是.NET語言和Java的一大缺點。非常好的博客文章! – kevinarpe

+3

該方法很好,但不允許來自mixin的方法的多態變體。你的其他答案因此更加強大(有趣的是,你可以添加兩個答案...) –

17

您可以創建一個DynamicObject轉發接收到的目標的列表,在責任風格鏈中的電話(注意,多態分派也是這樣工作 - 從向上最派生類):

public class Composition : DynamicObject { 
    private List<object> targets = new List<object>(); 

    public Composition(params object[] targets) { 
    AddTargets(targets); 
    } 

    protected void AddTargets(IEnumerable<object> targets) { 
    this.targets.AddRange(targets); 
    } 

    public override bool TryInvokeMember(
     InvokeMemberBinder binder, object[] args, out object result) { 
    foreach (var target in targets) { 
     var methods = target.GetType().GetMethods(); 
     var targetMethod = methods.FirstOrDefault(m => 
     m.Name == binder.Name && ParametersMatch(m, args)); 
     if (targetMethod != null) { 
     result = targetMethod.Invoke(target, args); 
     return true; 
     } 
    } 
    return base.TryInvokeMember(binder, args, out result); 
    } 

    private bool ParametersMatch(MethodInfo method, object[] args) { 
    var typesAreTheSame = method.GetParameters().Zip(
     args, 
     (param, arg) => param.GetType() == arg.GetType()); 
    return typesAreTheSame.Count() == args.Length && 
      typesAreTheSame.All(_=>_); 
    } 

} 

請注意,您還需要爲屬性(TryGetMemberTrySetMember),索引器(TryGetIndexTrySetIndex)和運算符(TryBinaryOperationTryUnaryOperation)執行委派。

然後,給出了一組類:

class MyClass { 
    public void MyClassMethod() { 
    Console.WriteLine("MyClass::Method"); 
    } 
} 

class MyOtherClass { 
    public void MyOtherClassMethod() { 
    Console.WriteLine("MyOtherClass::Method"); 
    } 
} 

可以「混合」他們在一起:

dynamic blend = new Composition(new MyClass(), new MyOtherClass()); 
blend.MyClassMethod(); 
blend.MyOtherClassMethod(); 

您還可以擴展動態對象使用類屬性或其他種類的註釋來尋找mixin。例如,給定此註釋接口:

public interface Uses<M> where M : new() { } 

你可以有這樣的DynamicObject

public class MixinComposition : Composition { 

    public MixinComposition(object target) : 
    base(target) { 
    AddTargets(ResolveMixins(target.GetType())); 
    } 

    private IEnumerable<object> ResolveMixins(Type mainType) { 
    return ResolveMixinTypes(mainType). 
     Select(m => InstantiateMixin(m)); 
    } 

    private IEnumerable<Type> ResolveMixinTypes(Type mainType) { 
    return mainType.GetInterfaces(). 
     Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(Uses<>)). 
     Select(u => u.GetGenericArguments()[0]); 
    } 

    private object InstantiateMixin(Type type) { 
    return Activator.CreateInstance(type); 
    } 

} 

,並創建 「混合」 是這樣的:

class MyMixin { 
    public void MyMixinMethod() { 
    Console.WriteLine("MyMixin::Method"); 
    } 
} 

class MyClass : Uses<MyMixin> { 
    public void MyClassMethod() { 
    Console.WriteLine("MyClass::Method"); 
    } 
} 

... 

dynamic blend = new MixinComposition(new MyClass()); 
blend.MyClassMethod(); 
blend.MyMixinMethod(); 
+0

好主意。你認爲XML和JSON序列化器可以處理mixins嗎? –

+0

有些人在遇到問題時會想:「我知道,我會用動態打字。」現在他們動態地重新分配問題並投向解決方案並獲得編程必殺技。 – tjbtech

3

我已經工作C#pMixins的開源Mixin框架。它利用部分類和代碼生成器導線在混合類到目標:

//Mixin - Class that contains members that should be injected into other classes. 
public class Mixin 
{ 
    // This method should be in several class 
    public void Method(){ } 
} 

//Target (Note: That it is partial) - Add members from Mixin 
[pMixn(Target = typeof(Mixin)] 
public partial class Target{} 


//Example of using Target 
public class Consumer 
{ 
    public void Example() 
    { 
     var target = new Target(); 

     // can call mixed in method 
     target.Method(); 

     // can implicitly convert Target to Mixin 
     Mixin m = new Target(); 
     m.Method(); 
    } 
} 
+2

+1有趣的框架。我也開始[類似](https://code.google.com/p/nroles/)前段時間。 –

3

我知道這是一個老話題,但我也想向大家介紹一個開源項目,我目前工作:mixinSharp

這是一個基於Roslyn的Visual Studio 2015重構擴展,它通過生成所需的委託代碼將Mixin支持添加到C#中。

例如,假設您有以下混入代碼要重用:

// child class where the mixin should be included 
public class Person 
{ 
    // reference to the mixin 
    private NameMixin _name = new NameMixin(); 
} 

如果執行:

// mixin class with the code you want to reuse 
public class NameMixin 
{ 
    public string Name { get; set; } 
    public void DoSomething() { } 
} 

而且要包括你的mixin給定的子類在NameMixin _name字段中的mixinSharp重構步驟中,擴展名將自動添加所需的所有膠水代碼,以將您的類中包含mixin:

public class Person 
{ 
    // reference to the mixin 
    private NameMixin _name = new NameMixin(); 

    public string Name 
    { 
     get { return _name.Name; } 
     set { _name.Name = value; } 
    } 
    public void DoSomething() => _name.DoSomething(); 
} 

除此之外,mixinSharp還有一些其他功能,比如用於mixin實例的構造函數注入,用於實現mixin等的接口。

來源可在githubVisual Studio Gallery中可用的二進制文件(編譯的Visual Studio擴展)。