Roles in C# Implementation

After seeing what roles in C# might look like, let's see the necessary transformations that would enable them. This is one possibility that a C# compiler could use to implement roles without having to make changes to the underlying CLR.

Roles

A role is transformed into a set of types: an interface (which the compositions will implement), a static class with the role code, and a class that encapsulates the role state. The interface for the role is the most important type and it will carry the role name. Because all the generated types are related, they'll be packaged together as nested types inside the role interface. Nesting types inside an interface is not supported in C#, but is supported by the underlying CIL. So we'll be looking at some invalid C# along the way; just think of it as "decompiled" CIL.

Let's see these transformations for a simple role:

role RPrintableWithCount {
  int count = 0;
  public void Print() {
    Console.WriteLine("{0}: {1}", ++count, ToString());
  }
}

This role prints a count and the object representation (ToString) to the console. It's transformed by the compiler into this:

// Warning: invalid C#
interface RPrintableWithCount {
  void Print();
  [Hide] __State __Data { get; }
  
  public sealed class __State {
    internal int count = 0;
  }
  public static class __Code {
    public static void __ctor(RPrintableWithCount @this) { }
    public static void Print(RPrintableWithCount @this) {
      Console.WriteLine("{0}: {1}", ++@this.__Data.count, @this.ToString());
    }
  }
}

The role interface contains the role's original members that are visible outside of the assembly, basically its public and protected members.

The names with double underscores are compiler-generated. They should be invalid C# identifiers, so that they can't be used in normal C# code. The __State class holds the state for the role. The state is hidden behind this class so that the compositions don't need to explicitly provide it (they just provide an instance of the state class through the __Data property). This is reminiscent of the cheshire cat technique that is used in C++ to hide implementation details from dependent clients; which doesn't force them to recompile if these details change. The __Code class holds the code for the role. It's a static class and it will "implement" the role interface in a way similar to extension methods, by taking the interface type as the first parameter in each method (that will be the type of this in the compositions, since they'll implement the role interface). Abstract members in the role will not have a corresponding code member. The role constructor is also migrated to this class.

The HideAttribute (provided by the roles implementation) is applied to the state property in the interface. This indicates that this member should be explicitly implemented in the composition classes, thus hidden from their public interfaces.

Protected members are present in the interface and are marked with an attribute provided by the implementation (FamilyAttribute) that causes them to also be protected in the compositions (yet another CIL feature not supported by C#). Private and internal members are just moved to the code class and all references to them (which are internal to the assembly) are adjusted (the InternalsVisibleToAttribute should generate a warning that internal members are now out-of-reach from code outside the assembly).

The accessibility semantics for roles is then:

  • Protected members in a role are visible by its compositions.
  • Private members in a role are not visible by its compositions.
  • Protected members in a self-type constraint class are visible by a role.
  • Private members in a self-type constraint class are not visible by a role.
  • Public and internal members work as expected.

Static members would just remain in the interface, since they're supported in the CIL. The C# compiler should be extended to be able to work with them. Currently it just issues a poorly named error message.

Public or protected fields must first be turned into auto-properties (which have private backing fields) to be able to be part of the interface:

role R {
  public int Field;
}

The above role is changed into the following before the role transformation:

role R {
  public int Field { get; set; }
}

Self-type constraints

For self-type constraints, when a role accesses a visible member of a constrained type, the code class just needs to cast @this to the desired type. The cast will be safe because the compositions are required to conform to the constraints. For example:

interface I {
  void M();
}
role R where this : I {
  void A() {
    M();
  }
}

Will turn into this:

// Warning: invalid C#
interface R {
  void A();
  [Hide] __State __Data { get; }
  public sealed class __State { }
  public static class __Code {
    public static void _ctor(R @this) { }
    public static void A(R @this) {
      ((I)@this).M();
    }
  }
}

Note that the state class (and property) is generated even for a role with no state. Again, this is to allow the role to change its implementation details without forcing its compositions to recompile.

If a role accesses a base type member or a protected member of a self-type constraint class, the composition will have to make these members available to the role. There are basically two options to do this. Let's look at an example:

class B {
  public virtual void M1() { }
  protected virtual void M2() { }
}
role R where this : B {
  public void MR() {
    base.M1(); // base call
    M2(); // protected method call
  }
}
class D : B, R { 
  public override void M1() { }
  protected override void M2() { }
}

With the transformations that were described, the above role method would not be able to call the base method and the protected method in its body. An easy solution would be to also include those methods as special methods in the role interface, and make sure that the compositions implement them by calling the desired methods:

// Warning: invalid C#
interface R {
  void MR();
  [Hide] __State __Data { get; }
  [Hide, BaseCall("M1")] void __base__M1();
  [Hide, Call("M2")] void __M2();
  public sealed class __State { }
  public static class __Code {
    public static void __ctor(R @this) { }
    public static void MR(R @this) {
      @this.__base_M1();
      @this.__M2();
    }
  }
} 
// the composition looks like this:
class D : B, R { 
  public override void M1() { }
  protected override void M2() { }
  
  void R.__Base_M1() {
    base.M1();
  }
  void R.__M2() {
    M2();
  }
  
  // other role member implementations....
}

This approach has the problem that implementation details of the role (like what methods it calls in its code) are now lifted to its interface. If any of these implementation details change, e.g. if the role now needs to call another base method, its compositions will have to be recompiled. This kind of fragility should be avoided.

The alternative to this approach is much more complex. Two separate interfaces could be generated: one for all the base methods that could be called (all visible methods in the class hierarchy, up to class Object - one interface could be generated for each class), and one for all the protected methods in the class itself that could be called. The composing classes would also have to implement those interfaces and just delegate the calls to the appropriate members. This seems too overkill, so much that the first approach can be seen as a viable solution.

Note that all these problems go away if we restrict self-type constraints to be just interfaces or if we disallow roles to call protected and base members of their self-types.

Compositions

Compositions will implement the role interface and just delegate the calls to the corresponding members of the role code class, passing this as their first parameter. They'll also create an instance of the role state class in their constructor and call the corresponding role "constructor" in the role code class.

These types:

role R {
  int i;
  public R() {
    i = 42;
  }
  public void M() { }
}
class C : R { }

Will be transformed into this:

// Warning: invalid C#
interface R {
  void M();
  [Hide] __State __Data { get; }
  public sealed class __State {
    internal int i;
  }
  public static class __Code {
    public static void __ctor(R @this) {
      @this.__Data.i = 42;
    }
    public static void M(R @this) { }
  }
}
class C : R {
  public C() {
    __R_data = new R.__State();
    R.__Code.__ctor(this);
  }
  public void M() {
    R.__Code.M(this);
  }
  R.__State R.__Data {
    get { return __R_data; }
  }
  R.__State __R_data;
}

The role view operators exclude and impose will just define which member for which role to call. The hide operator will explicitly implement the corresponding member (so that it will be private in the class). The alias operator will create a new member name that will implement the corresponding role interface member. This interface aliasing is not supported in C#, but it's supported in VB.NET and C++/CLI.

As an example:

role R1 { public void M() { } }
role R2 { public void M() { } }
class C : R1, R2 {
  R1 { 
    hide void M();
  }
  R2 {
    alias(AliasedM) void M();
  }
}

The above composition will be changed into this:

// Warning: invalid C#
class C : R1, R2 { 
  // constructor and state class members ...
  void R1.M() { 
    R1.__Code.M(this);
  }
  public void AliasedM() = R2::M { // borrowing some C++/CLI syntax
    R2.__Code.M(this);
  }
}

Note: to create compilable code, and to validate the proposed transformations, all invalid C# code in this post can be written in valid C++/CLI. Here is the code for the last example:

interface class R1 {
  void M();
  ref class __State sealed { };
  ref class __Code abstract sealed { 
  public:
    static void __ctor(R1^ __identifier(this)) { }
    static void M(R1^ __identifier(this)) { }
  };
  property __State^ __Data {
    __State^ get();
  };
};

// R2 is just like R1 ...

ref class C : public R1, public R2 {
private:
  R1::__State^ __R1_data;
  R2::__State^ __R2_data;
  property R1::__State^ __R1_Data {
    virtual R1::__State^ get() sealed = R1::__Data::get { 
      return __R1_data;
    }
  }
  property R2::__State^ __R2_Data {
    virtual R2::__State^ get() sealed = R2::__Data::get { 
      return __R2_data;
    }
  }
  virtual void M() sealed = R1::M { 
    R1::__Code::M(this);
  }
public:
  C() : __R1_data(gcnew R1::__State), __R2_data(gcnew R2::__State) { 
    R1::__Code::__ctor(this);
    R2::__Code::__ctor(this);
  }
  virtual void AliasedM() sealed = R2::M {
    R2::__Code::M(this);
  }
};

Note how the state getter properties and the hidden R1::M method are implemented in the private section of the class. Also note that the method R2::M is implemented as C::AliasedM.

There're certainly many details and gaps which I didn't address, and some that are still not ideal (like protected or base member calls). You can even imagine how other features, such as inheritance, generics, operator overloading and events would fit into and interact with these transformations. Roles are supposed to be just compiler-backed compositions, so you can already answer many more questions and concerns. Just think: how would that be if I implemented the composition manually?

Comments

Popular posts from this blog

The Acyclic Visitor Pattern

Some OO Design

NRoles: An experiment with roles in C#