Using Extension Methods on a C# Interface to Enable DCI in Xamarin

Scala has several nice language features, including the elegant use of val for immutable variables and var for mutable, but the feature that I miss the most on a day-to-day basis is "traits."

Traits allow you to implement one or more methods of an interface. The canonical use is to "mix-in" behavior while avoiding the "diamond-problem."

DCI has the idea that Objects (domain-meaningful entities that correspond to user conceptions) adopt Roles, which are context-specific. Roles interact to produce value. So, for instance, when you're transferring money at an ATM, you're dealing with two accounts that are the same type of Object (Account), but which are in two different roles in the context of "Transfer Money": a TransferSource and a TransferSink. And an Account in a TransferSource role has different behavior than an Account in a TransferSink role (e.g., TransferSource expects to withdraw(Money amount) while TransferSink expects to credit(Money amount)).

In C#, the way to specify that a class has a certain set of behaviors is to specify those behaviors in an interface and specify that the class implements them:

public class Account: TransferSource, TransferSink

And then, of course, you would implement the various methods of TransferSource and TransferSink within Account.

But the very essence of DCI is the premise that classic OOP type-systems don't appropriately capture the relationships between Objects-in-Roles, even though "Objects-in-Roles working with each other" is the domain-users mental model ("I pick a source account, and a destination account, and specify an amount, and the amount is debited from the source and credited to the destination"). So DCI says that the TransferTo method that corresponds to the use-case should be elevated to a first-class object.

But in C# you cannot partially implement an interface. But you can create and implement an extension method on an interface!

  public static class TransferContextTrait
  {
    public static void TransferTo(this TransferSource self, TransferSink sink, Decimal amount)
    {
        try
        {
            if(self.Funds < amount)
            {
                self.FailTransfer(new TransferFailedReason("Insufficient Funds"));
            }
            else
            {
                self.Withdraw(amount);
                sink.Deposit(amount);

                var details = new TransferDetails(self.Name, sink.Name, amount);
                self.AccomplishTransfer(details);
            }
        }
        catch(Exception x)
        {
            self.FailTransfer(new TransferFailedReason(x.ToString()));
        }
    }
}

Note an interesting restriction, though: You cannot trigger an event from within an extension method! So in this case, although I would have preferred to propagate the results of the calculation by self.TransferAccomplished(this, details) I have to use a proxy function in Account:

public void AccomplishTransfer(TransferDetails details)
{
       TransferAccomplished(this, new TArgs&lt;TransferDetails>(details));
}

public event EventHandler&lt;TArgs &lt;TransferDetails>> TransferAccomplished = delegate {};

I'll be talking more about DCI and other cross-platform architectural techniques at MonkeySpace in Chicago next week. Hope to see you there!