under the hood of re-motion mixins -- mixins based on 'Mixin-TThis-TBase'

If we want to access base members from the target class in the mixin class, we must use the generic Mixin<TThis, TBase> type, i.e. we must provide a parameter for TBase.

As the wiki page

re-motion mixins basics -- the generic 'Mixin' type

explains, "for technical reasons" you are constrained to pass an interface as TBase parameter. This wiki page explains this constrain and how data is organized at run-time.

As soon as TBase enters the picture, we face a complication, because then the mixed class ("Parrot_Mixed_SOM3GUID") can have two versions of each target method:

  • the one overridden by the mixin class (e.g. Say (string s)
  • the original method from the target class (that would be Base.Say (string s) when seen from the mixin class)

Now look at this code snippet:


public void PretendToTalkOnThePhone ()
{
    This.Say ("Halloh?"); // overridden 'Say'
    Base.Say ("Hi!");     // original (base) 'Say'
}

Clearly, Base and This can't hold a reference to the same instance of the mixed class, because that would make the overridden Say () the same as the original (base) Say ().

So Base holds a reference to a different instance – a so-called base call proxy. Another synthesized class enters the UML picture, in this example Parrot_BaseCallProxy_SOM3GUID (labeled "Parrot_BaseCallProxy..." in the illustration).

It is an inner class to Parrot_Mixed_SOM3GUID and holds a reference to its nesting class. Remember that you can't pass classes as a TBase parameter to TThis, only interfaces. Parrot_BaseCallProxy_SOM3GUID implements IParrot, which in turn was passed as second parameter to Mixin<TThis, TBase> for this diagram:

The most important points in the diagram are:

  • OnThePhoneMixin holds a reference to Parrot_BaseCallProxy_SOM3GUID (labeled "Parrot_BaseCallProxy..." in the illustration) in Base
  • Parrot_BaseCallProxy_SOM3GUID holds a reference to the mixed class Parrot_Mixed_SOM3GUID (labeled "Parrot_SOM3GUID")
  • Parrot_BaseCallProxy_SOM3GUID implements IParrot (note that, in this diagram, Parrot or Parrot_Mixed_SOM3GUID do not}}
  • Parrot_Mixed_SOM3GUID has been endowed with a Base_Say method

A call OnThePhoneMixin's PretendToTalkOnThePhone () to Base.Say will eventually call Parrot's Say (). Here is a walk-through for how this works.

  • Base.Say ("Halloh?") delegates to the Say () method in Parrot_BaseCallProxy_SOM3GUID
  • Parrot_BaseCallProxy_SOM3GUID's Say () method delegates to Base_Say () in Parrot_Mixed_SOM3GUID
  • Parrot_Mixed_SOM3GUID's Say () delegates to Parrot's Say () via base.Say () (note the lowercase 'b' in base.Say () – not a typo; Parrot_Mixed_SOM3GUID calls its base-class' Say ()

Why can't Parrot_Mixed_SOM3GUID's own Say () method be called here? Simple: that's the Say () overridden by the mixin class – the one called by This.Say ().

"Technical reasons"

As promised in the opening of this wiki page, a word on the technical reasons why you can't pass a class as type parameter to Mixin<TThis, TBase>. The problem is that you basically need two instances to delegate to – one for the Base property, one for the This property. Two instances mean two instantiations, and instantiations can have side-effects (dumping to the screen, opening a socket, new thread, etc.) If the ObjectFactory would cause two such side effects for each instantiation of the desired target class, the programmer using mixins could get into trouble and would have a hard time figuring out why. Having an ObjectFactory create two instances instead of one is more tasteless prank than sound engineering, so that's a no-go. The base-call proxy class synthesized at run-time has no side-effects upon instantiation, and no state. It simply steals the interface signatures (IParrot, in this example) and delegates every method (and every property) implementation to the mixed class – either to the corresponding method (if no call to a base method is needed) or the corresponding Base_X method.

Note that the construction of the two implementor classes works differently for each interface if you pass TWO interfaces to Mixin<TThis, TBase>, as in Mixin<IParrot, IParrot>, for example. This call is equivalent to Mixin<Parrot, IParrot>, because re-motion mixin is smart enough to do the right thing here:

  • for the first IParrot, the object factory finds Parrot as the implementor and subclasses it to the mixed class
  • for the second IParrot, the object factory generates the base call proxy with no life of its own, delegating everything to the mixed class