I've heard that it's possible with extension methods, but I can't quite figure it out myself. I'd like to see a specific example if possible.
Thanks!
I've heard that it's possible with extension methods, but I can't quite figure it out myself. I'd like to see a specific example if possible.
Thanks!
It really depends on what you mean by "mixin" - everyone seems to have a slightly different idea. The kind of mixin I'd like to see (but which isn't available in C#) is making implementation-through-composition simple:
public class Mixin : ISomeInterface
{
private SomeImplementation impl implements ISomeInterface;
public void OneMethod()
{
// Specialise just this method
}
}
The compiler would implement ISomeInterface just by proxying every member to "impl" unless there was another implementation in the class directly.
None of this is possible at the moment though :)
I usually employ this pattern:
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);
}
}
I have the two definitions in the same source file/namespace. That way the extensions are always available when the interface is used (with 'using').
This gives you a limited mixin as described in CMS' first link.
Limitations:
It's still sufficient for many situations.
It would be nice if they (MS) could add some compiler magic to auto-generate the extension class:
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);
}
}
Although Jon's proposed compiler trick would be even nicer.
There is an open source framework that enables you to implement mixins via C#. Have a look on http://remix.codeplex.com/.
It is very easy to implement mixins with this framework. Just have a look on the samples and the "Additional Information" links given on the page.
LinFu and Castle's DynamicProxy implement mixins. COP (Composite Oriented Programming) could be considered as making a whole paradigm out of mixins. This post from Anders Noras has useful informations and links.
EDIT: This is all possible with C# 2.0, without extension methods
I needed something similar so I came up with the following using Reflection.Emit. In the following code a new type is dynamically generated which has a private member of type 'mixin'. All the calls to methods of 'mixin' interface are forwarded to this private member. A single parameter constructor is defined that takes an instance which implements the 'mixin' interface. Basically, it is equal to writing the following code for a given concrete type T and interface 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();
// }
}
This is the class:
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);
}
}
}
This is the usage:
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);
You could also augment the extension method approach to incorporate state, in a pattern not unlike WPF's attached properties.
Here is an example with minimum boilerplate. Note that no modification are required on the target classes, including adding interfaces, unless you need to deal with the target class polymorphically - in which case you end up with something very close to actual Multiple Inheritance.
// 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;
}
}
Usage:
var some_obj = new SomeClass();
some_obj.SetName("Johny");
Console.WriteLine(some_obj.GetName()); // "Johny the SomeClass"
Note that it also works with null instances, since extension methods naturally do.
You might also consider using a WeakDictionary implementation to avoid memory leaks caused by the collection's holding on to target class references as keys.
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();
}
}
If you have a base class that can store data you can enforce compiler safety and use marker interfaces. That's more or less what "Mixins in C# 3.0" from the accepted answer proposes.
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);
}
}
The 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;
}
So if you have a Class you can inherit from 'ObjectBase' and decorate with IHasStuff you can add sutff now
Here is a mixin implementation I've just come up with. I'll probably use it with a library of mine.
It's probably been done before, somewhere.
It's all statically typed, with no dictionaries or something. It requires a little bit of extra code per type, you don't need any storage per instance. On the other hand, it also gives you the flexibility of changing the mixin implementation on the fly, if you so desire. No post-build, pre-build, mid-build tools.
It has some limitations, but it does allow things like overriding and so on.
We begin by defining a marker interface. Perhaps something will be added to it later:
public interface Mixin {}
This interface is implemented by mixins. Mixins are regular classes. Types do not inherit or implement mixins directly. They instead just expose an instance of the mixin using the interface:
public interface HasMixins {}
public interface Has<TMixin> : HasMixins
where TMixin : Mixin {
TMixin Mixin { get; }
}
Implementing this interface means supporting the mixin. It's important that it's implemented explicitly, since we're going to have several of these per type.
Now for a little trick using extension methods. We define:
public static class MixinUtils {
public static TMixin Mixout<TMixin>(this Has<TMixin> what)
where TMixin : Mixin {
return what.Mixin;
}
}
Mixout
exposes the mixin of the appropriate type. Now, to test this out, let's define:
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).
There are, fundamentally, several techniques to getting Mixin behavior in your classes:
IEnumerable
and IDisposable
) can use default interface members with explicit implementation of said interface. Then, then new interface behaves as a mixin where concrete behavior can be added and leveraged without using extension methods, and can support constructs such as using
and foreach
.ConditionalWeakTable
to hold data.Default implementation of interfaces have pretty much enabled mixins to be possible with a bit of creativity.
Here's a demo:
using System;
public class Program {
public static void Main(string[] args) {
var inst = new Baseline();
Console.WriteLine("Baseline.Method() returns " + inst.Method());
Console.WriteLine("Baseline.Method() returns " + inst.Method());
}
}
public struct MixinImpl {
public static MixinImpl Create() => new MixinImpl(); // Idiom to provide the mixin constructor
private int counter;
public int Method() => ++counter;
}
public interface Mixin {
ref MixinImpl Impl { get; }
int Method() => Impl.Method();
}
public class Baseline : Mixin {
private MixinImpl mixinImpl = MixinImpl.Create();
ref MixinImpl Mixin.Impl => ref mixinImpl;
}
So this works by defining a mixin as a struct
and a corresponding interface
; where the interface contains the mixin default implementation and the struct
contains the state. This implementation has the benefit of not breaking the ABI when you add either state or methods to the mixin.
The only real downside is there's no way to do protected
access qualifier. Your only protection choices are public
and private
. You can kind of fake protected by not providing the forwarder but it doesn't really work because half of the callers will be via variables of the interface type, which can see the Impl member and access members of the struct.
Here's the overhead in the user of the mixin isolated so you can see it alone:
: Mixin {
private MixinImpl mixinImpl = MixinImpl.Create();
ref MixinImpl Mixin.Impl => ref mixinImpl;
That's it. One interface declaration and two lines of code. The first line declares and creates the mixin state and the second line provides the glue that ties everything together. There's no limit to the number of mixins you can have either.
The overhead on the mixin provider is as follows:
int Method() => Impl.Method();
Every public
property and method that accesses anything private
needs a forwarder from the interface
to the struct
.
Mixins done this way don't have inheritance; however you can fake it with composition and implementing the base interface in the derived struct
. The interface
forwarding provides capacity to do virtual methods while you're at it. This also means mixins can have mixins, but what are you doing if you need that.
Apologies; TIO's compiler is out of date and doesn't support default implementations.
© 2022 - 2024 — McMap. All rights reserved.