IT story

C #에서 믹스 인을 구현할 수 있습니까?

hot-time 2021. 1. 6. 20:23
반응형

C #에서 믹스 인을 구현할 수 있습니까?


확장 방법으로 가능하다는 말을 들었지만 스스로 알아낼 수는 없습니다. 가능하다면 구체적인 예를보고 싶습니다.

감사!


"믹신"이 의미하는 바에 따라 다릅니다. 모든 사람이 약간 다른 생각을 가지고있는 것 같습니다. 내가 것 믹스 인의 종류 처럼 보고 구현을 통해 조성 간단하고있다 (그러나 C #에서 사용할 수 없습니다)

public class Mixin : ISomeInterface
{
    private SomeImplementation impl implements ISomeInterface;

    public void OneMethod()
    {
        // Specialise just this method
    }
}

컴파일러는 클래스에 직접 다른 구현이없는 한 모든 멤버를 "impl"로 프록시하여 ISomeInterface를 구현합니다.

하지만 현재로서는 불가능합니다. :)


C #을 통해 믹스 인을 구현할 수있는 오픈 소스 프레임 워크가 있습니다. http://remix.codeplex.com/을 살펴보세요 .

이 프레임 워크로 믹스 인을 구현하는 것은 매우 쉽습니다. 샘플과 페이지에 제공된 "추가 정보"링크를 살펴보십시오.


나는 일반적으로 다음 패턴을 사용합니다.

public interface IColor
{
    byte Red   {get;}
    byte Green {get;}
    byte Blue  {get;}
}

public static class ColorExtensions
{
    public static byte Luminance(this IColor c)
    {
        return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11);
    }
}

동일한 소스 파일 / 네임 스페이스에 두 가지 정의가 있습니다. 이렇게하면 인터페이스를 사용할 때 ( 'using'사용) 항상 확장 기능을 사용할 수 있습니다.

이것은 CMS의 첫 번째 링크에 설명 된대로 제한된 믹스 인제공합니다 .

제한 사항 :

  • 데이터 필드 없음
  • 속성 없음 (괄호로 myColor.Luminance (), 확장 속성 을 호출해야 합니까?)

많은 상황에서 여전히 충분합니다.

(MS)가 확장 클래스를 자동 생성하기 위해 컴파일러 마법을 추가 할 수 있다면 좋을 것입니다.

public interface IColor
{
    byte Red   {get;}
    byte Green {get;}
    byte Blue  {get;}

    // compiler generates anonymous extension class
    public static byte Luminance(this IColor c)     
    {
        return (byte)(c.Red*0.3 + c.Green*0.59+ c.Blue*0.11);
    }
}

Jon이 제안한 컴파일러 트릭이 더 좋을지라도.


LinFuCastle의 DynamicProxy 는 믹스 인을 구현합니다. COP (Composite Oriented Programming)는 믹스 인에서 전체 패러다임을 만드는 것으로 간주 될 수 있습니다. Anders Noras의이 게시물 에는 유용한 정보와 링크가 있습니다.

편집 : 이것은 확장 메서드없이 C # 2.0에서 모두 가능합니다.


WPF의 연결된 속성과 다르지 않은 패턴으로 상태를 통합하기 위해 확장 메서드 접근 방식을 확장 할 수도 있습니다.

다음은 최소 상용구를 사용한 예입니다. 대상 클래스를 다형성으로 다룰 필요가없는 한 인터페이스 추가를 포함하여 대상 클래스에 대한 수정이 필요하지 않습니다.

// Mixin class: mixin infrastructure and mixin component definitions
public static class Mixin
{ 
    // =====================================
    // ComponentFoo: Sample mixin component
    // =====================================

    //  ComponentFooState: ComponentFoo contents
    class ComponentFooState
    {
        public ComponentFooState() {
            // initialize as you like
            this.Name = "default name";
        }

        public string Name { get; set; }
    }

    // ComponentFoo methods

    // if you like, replace T with some interface 
    // implemented by your target class(es)

    public static void 
    SetName<T>(this T obj, string name) {
        var state = GetState(component_foo_states, obj);

        // do something with "obj" and "state"
        // for example: 

        state.Name = name + " the " + obj.GetType();


    }
    public static string
    GetName<T>(this T obj) {
        var state = GetState(component_foo_states, obj);

        return state.Name; 
    }

    // =====================================
    // boilerplate
    // =====================================

    //  instances of ComponentFoo's state container class,
    //  indexed by target object
    static readonly Dictionary<object, ComponentFooState>
    component_foo_states = new Dictionary<object, ComponentFooState>();

    // get a target class object's associated state
    // note lazy instantiation
    static TState
    GetState<TState>(Dictionary<object, TState> dict, object obj) 
    where TState : new() {
        TState ret;
        if(!dict.TryGet(obj, out ret))
            dict[obj] = ret = new TState();

        return ret;
    }

}

용법:

var some_obj = new SomeClass();
some_obj.SetName("Johny");
Console.WriteLine(some_obj.GetName()); // "Johny the SomeClass"

확장 메서드는 자연스럽게 작동하므로 null 인스턴스에서도 작동합니다.

또한 컬렉션이 대상 클래스 참조를 키로 유지하여 발생하는 메모리 누수를 방지하기 위해 WeakDictionary 구현을 사용할 수도 있습니다.


비슷한 것이 필요했기 때문에 Reflection.Emit을 사용하여 다음을 생각해 냈습니다. 다음 코드에서는 'mixin'유형의 개인 멤버가있는 새 유형이 동적으로 생성됩니다. 'mixin'인터페이스의 메서드에 대한 모든 호출은이 개인 멤버에게 전달됩니다. 'mixin'인터페이스를 구현하는 인스턴스를 취하는 단일 매개 변수 생성자가 정의됩니다. 기본적으로 주어진 구체적인 유형 T 및 인터페이스 I에 대해 다음 코드를 작성하는 것과 같습니다.

class Z : T, I
{
    I impl;

    public Z(I impl)
    {
        this.impl = impl;
    }

    // Implement all methods of I by proxying them through this.impl
    // as follows: 
    //
    // I.Foo()
    // {
    //    return this.impl.Foo();
    // }
}

이것은 수업입니다.

public class MixinGenerator
{
    public static Type CreateMixin(Type @base, Type mixin)
    {
        // Mixin must be an interface
        if (!mixin.IsInterface)
            throw new ArgumentException("mixin not an interface");

        TypeBuilder typeBuilder = DefineType(@base, new Type[]{mixin});

        FieldBuilder fb = typeBuilder.DefineField("impl", mixin, FieldAttributes.Private);

        DefineConstructor(typeBuilder, fb);

        DefineInterfaceMethods(typeBuilder, mixin, fb);

        Type t = typeBuilder.CreateType();

        return t;
    }

    static AssemblyBuilder assemblyBuilder;
    private static TypeBuilder DefineType(Type @base, Type [] interfaces)
    {
        assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.RunAndSave);

        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(Guid.NewGuid().ToString());

        TypeBuilder b = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
            @base.Attributes,
            @base,
            interfaces);

        return b;
    }
    private static void DefineConstructor(TypeBuilder typeBuilder, FieldBuilder fieldBuilder)
    {
        ConstructorBuilder ctor = typeBuilder.DefineConstructor(
            MethodAttributes.Public, CallingConventions.Standard, new Type[] { fieldBuilder.FieldType });

        ILGenerator il = ctor.GetILGenerator();

        // Call base constructor
        ConstructorInfo baseCtorInfo =  typeBuilder.BaseType.GetConstructor(new Type[]{});
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeBuilder.BaseType.GetConstructor(new Type[0]));

        // Store type parameter in private field
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, fieldBuilder);
        il.Emit(OpCodes.Ret);
    }

    private static void DefineInterfaceMethods(TypeBuilder typeBuilder, Type mixin, FieldInfo instanceField)
    {
        MethodInfo[] methods = mixin.GetMethods();

        foreach (MethodInfo method in methods)
        {
            MethodInfo fwdMethod = instanceField.FieldType.GetMethod(method.Name,
                method.GetParameters().Select((pi) => { return pi.ParameterType; }).ToArray<Type>());

            MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                                            fwdMethod.Name,
                                            // Could not call absract method, so remove flag
                                            fwdMethod.Attributes & (~MethodAttributes.Abstract),
                                            fwdMethod.ReturnType,
                                            fwdMethod.GetParameters().Select(p => p.ParameterType).ToArray());

            methodBuilder.SetReturnType(method.ReturnType);
            typeBuilder.DefineMethodOverride(methodBuilder, method);

            // Emit method body
            ILGenerator il = methodBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, instanceField);

            // Call with same parameters
            for (int i = 0; i < method.GetParameters().Length; i++)
            {
                il.Emit(OpCodes.Ldarg, i + 1);
            }
            il.Emit(OpCodes.Call, fwdMethod);
            il.Emit(OpCodes.Ret);
        }
    }
}

이것은 사용법입니다.

public interface ISum
{
    int Sum(int x, int y);
}

public class SumImpl : ISum
{
    public int Sum(int x, int y)
    {
        return x + y;
    }
}

public class Multiply
{        
    public int Mul(int x, int y)
    {
        return x * y;
    }
}

// Generate a type that does multiply and sum
Type newType = MixinGenerator.CreateMixin(typeof(Multiply), typeof(ISum));

object instance = Activator.CreateInstance(newType, new object[] { new SumImpl() });

int res = ((Multiply)instance).Mul(2, 4);
Console.WriteLine(res);
res = ((ISum)instance).Sum(1, 4);
Console.WriteLine(res);

데이터를 저장할 수있는 기본 클래스가있는 경우 컴파일러 안전을 적용하고 마커 인터페이스를 사용할 수 있습니다. 그것은 받아 들여진 대답의 "Mixins in C # 3.0"이 제안하는 것입니다.

public static class ModelBaseMixins
{
    public interface IHasStuff{ }

    public static void AddStuff<TObjectBase>(this TObjectBase objectBase, Stuff stuff) where TObjectBase: ObjectBase, IHasStuff
    {
        var stuffStore = objectBase.Get<IList<Stuff>>("stuffStore");
        stuffStore.Add(stuff);
    }
}

ObjectBase :

public abstract class ObjectBase
{
    protected ModelBase()
    {
        _objects = new Dictionary<string, object>();
    }

    private readonly Dictionary<string, object> _objects;

    internal void Add<T>(T thing, string name)
    {
        _objects[name] = thing;
    }

    internal T Get<T>(string name)
    {
        T thing = null;
        _objects.TryGetValue(name, out thing);

        return (T) thing;
    }

따라서 클래스가있는 경우 'ObjectBase'에서 상속하고 IHasStuff로 장식 할 수 있습니다. 이제 sutff를 추가 할 수 있습니다.


여기 내가 방금 생각 해낸 믹스 인 구현이 있습니다. 아마 내 라이브러리 와 함께 사용할 것입니다 .

아마 전에 어딘가에서했을 것입니다.

사전이나 무언가없이 모두 정적으로 입력됩니다. 유형 당 약간의 추가 코드가 필요하며 인스턴스 당 스토리지가 필요하지 않습니다. 다른 한편으로, 원하는 경우 믹스 인 구현을 즉석에서 변경할 수있는 유연성도 제공합니다. 빌드 후, 사전 빌드, 중간 빌드 도구가 없습니다.

몇 가지 제한 사항이 있지만 재정의 등과 같은 작업을 허용합니다.

마커 인터페이스를 정의하는 것으로 시작합니다. 나중에 뭔가 추가 될 것입니다.

public interface Mixin {}

이 인터페이스는 mixins에 의해 구현됩니다. 믹스 인은 정규 수업입니다. 유형은 믹스 인을 직접 상속하거나 구현하지 않습니다. 대신 인터페이스를 사용하여 믹스 인의 인스턴스를 노출합니다.

public interface HasMixins {}

public interface Has<TMixin> : HasMixins
    where TMixin : Mixin {
    TMixin Mixin { get; }
}

이 인터페이스를 구현한다는 것은 믹스 인을 지원하는 것을 의미합니다. 유형별로 여러 개를 가질 것이기 때문에 명시 적으로 구현하는 것이 중요합니다.

이제 확장 메서드를 사용하는 약간의 트릭입니다. 우리는 다음을 정의합니다.

public static class MixinUtils {
    public static TMixin Mixout<TMixin>(this Has<TMixin> what)
        where TMixin : Mixin {
        return what.Mixin;
    }
}

Mixout적절한 유형의 믹스 인을 노출합니다. 이제 이것을 테스트하기 위해 다음을 정의하겠습니다.

public abstract class Mixin1 : Mixin {}

public abstract class Mixin2 : Mixin {}

public abstract class Mixin3 : Mixin {}

public class Test : Has<Mixin1>, Has<Mixin2> {

    private class Mixin1Impl : Mixin1 {
        public static readonly Mixin1Impl Instance = new Mixin1Impl();
    }

    private class Mixin2Impl : Mixin2 {
        public static readonly Mixin2Impl Instance = new Mixin2Impl();
    }

    Mixin1 Has<Mixin1>.Mixin => Mixin1Impl.Instance;

    Mixin2 Has<Mixin2>.Mixin => Mixin2Impl.Instance;
}

static class TestThis {
    public static void run() {
        var t = new Test();
        var a = t.Mixout<Mixin1>();
        var b = t.Mixout<Mixin2>();
    }
}

Rather amusingly (though in retrospect, it does make sense), IntelliSense does not detect that the extension method Mixout applies to Test, but the compiler does accept it, as long as Test actually has the mixin. If you try,

t.Mixout<Mixin3>();

It gives you a compilation error.

You can go a bit fancy, and define the following method too:

[Obsolete("The object does not have this mixin.", true)]
public static TSome Mixout<TSome>(this HasMixins something) where TSome : Mixin {
    return default(TSome);
}

What this does is, a) display a method called Mixout in IntelliSense, reminding you of its existence, and b) provide a somewhat more descriptive error message (generated by the Obsolete attribute).


I've found a workaround here, which while not entirely elegant, allows you to achieve fully observable mixin behavior. Additionally, IntelliSense still works!

using System;
using System.Runtime.CompilerServices; //needed for ConditionalWeakTable
public interface MAgeProvider // use 'M' prefix to indicate mixin interface
{
    // nothing needed in here, it's just a 'marker' interface
}
public static class AgeProvider // implements the mixin using extensions methods
{
    static ConditionalWeakTable<MAgeProvider, Fields> table;
    static AgeProvider()
    {
        table = new ConditionalWeakTable<MAgeProvider, Fields>();
    }
    private sealed class Fields // mixin's fields held in private nested class
    {
        internal DateTime BirthDate = DateTime.UtcNow;
    }
    public static int GetAge(this MAgeProvider map)
    {
        DateTime dtNow = DateTime.UtcNow;
        DateTime dtBorn = table.GetOrCreateValue(map).BirthDate;
        int age = ((dtNow.Year - dtBorn.Year) * 372
                   + (dtNow.Month - dtBorn.Month) * 31
                   + (dtNow.Day - dtBorn.Day)) / 372;
        return age;
    }
    public static void SetBirthDate(this MAgeProvider map, DateTime birthDate)
    {
        table.GetOrCreateValue(map).BirthDate = birthDate;
    }
}

public abstract class Animal
{
    // contents unimportant
}
public class Human : Animal, MAgeProvider
{
    public string Name;
    public Human(string name)
    {
        Name = name;
    }
    // nothing needed in here to implement MAgeProvider
}
static class Test
{
    static void Main()
    {
        Human h = new Human("Jim");
        h.SetBirthDate(new DateTime(1980, 1, 1));
        Console.WriteLine("Name {0}, Age = {1}", h.Name, h.GetAge());
        Human h2 = new Human("Fred");
        h2.SetBirthDate(new DateTime(1960, 6, 1));
        Console.WriteLine("Name {0}, Age = {1}", h2.Name, h2.GetAge());
        Console.ReadKey();
    }
}

ReferenceURL : https://stackoverflow.com/questions/255553/is-it-possible-to-implement-mixins-in-c

반응형