C# Corner

Lambda Properties: An Alternative to Subclassing?

This article will introduce you to the concept of exposing parts of your application logic as lambda properties. By making these properties read/write, you can plug in specific functionality with more control than subclassing.

We all know the power of subclassing. You define a base class with the necessary functionality and then extend that class by subclassing. You get all of the predefined implementations of the base class members along with the ability to add new functionality, as well as replace existing functionality by overriding base class methods.

Sometimes it seems that subclassing can be overkill for what you need. I'm sure you've seen cases where a derived implementation only changes a single method. Perhaps there's an edge case where, in one spot in your application, the code needs to behave a little differently. To do that, you might define a whole new class, override the method with your implementation and tweak the code to make sure it uses the new implementation. Or you'll break the code up some more and introduce an interface plus more components -- again, all for one edge case. This article offers a possible alternative.

Pluggable Methods via Lambdas
What if you could just "plug in" a new implementation of a method? Let's take the canonical example of a base "Animal" class and see how, using pluggable methods, you can tailor the animal's behavior:

  public class Animal
  {
    public Func<string> Speak { get; set; }
    public Action Swim { get; set; }
  }

As you can see, you simply use lambdas to expose the functionality of the Animal. How do you assign behavior to such a class? You assign some lambdas to the Speak and Swim properties, like so:

var dog = new Animal
  {
    Speak = () => "Woof!", 
    Swim = () => Console.WriteLine("do the doggie paddle!")
  };

var cat = new Animal
  {
    Speak = () => "Meow!", 
    Swim = () => { throw new CantSwimException(); }
  };

Console.WriteLine(dog.Speak());
Console.WriteLine(cat.Speak());

dog.Swim();
cat.Swim();

Once you've set up the behavior for these instances, you use them just like any other object. A very important point here is that you're changing the behavior of an object that has already been created. With subclassing, you have to derive a brand-new type, override the base functionality and then use that new type in your applications. With this approach, you tailor the behavior of an individual instance of a class.

As an example, let's give any animal the ability to swim:

static void TeachToSwim(Animal animal)
{
  animal.Swim = () => Console.WriteLine("Hold breath and float.");
}

This shows you can take any Animal instance and "attach" behavior to it. Of course, a dog already knows how to swim and you'd hate to erase his memory, so you can add a little protection in those cases:

static void TeachToSwim(Animal animal)
{
  if( animal.Swim == null )
    animal.Swim = () => Console.WriteLine("Hold breath and float.");
}

This is definitely a contrived example, but it does open up some interesting possibilities.

More Flexibility
One aspect of being able to modify behavior on a per-instance type instead of a per-class type is the granularity of control. Imagine an application that may use many instances of a certain type. But when one of those instances is sent to a certain part of the code, there are some performance issues. You can use this "pluggable" approach to throw in some extra logging when your instances get inside your performance-bottleneck area.

Let's start with a class that's having some performance issues when adding an Animal to a cage (yes, I know -- very clever). Here's what the current implementation looks like:

public class PerfIssue
{
  public Func<Animal, Cage> PutAnimalInCage { get; set; }
 
  public PerfIssue()
  {
    this.PutAnimalInCage = AddAnimalToNewCage;
  }
 
  private Cage AddAnimalToNewCage(Animal animal)
  {
    var cage = new Cage();
    cage.Animals.Add(animal);
 
    return cage;
  }
}

Notice that, unlike the Animal class earlier, this class contains a default implementation for the PutAnimalInCage lambda. As with subclassing, providing a default implementation allows you to include standard functionality, while exposing the lambda property allows the consumer to replace (or augment) that standard functionality.

This is how you can use this architecture to add just a little bit of extra logging:

var dog = new Animal
  {
    Speak = () => "Woof!",
    Swim = () => Console.WriteLine("do the doggie paddle!")
  };

var issue = new PerfIssue();
var originalCall = issue.PutAnimalInCage;
issue.PutAnimalInCage = a =>
  {
    Console.WriteLine("Start: {0}", DateTime.Now);
    var result = originalCall(a);
    Console.WriteLine("End: {0}", DateTime.Now);
    return result;
  };

To try and narrow down the problem, you plug in some simple logging around the PutAnimalInCage method. As you see, you'll get a reference to the default implementation (the "base" implementation, if you were doing a true object-oriented implementation). You then weave in a couple of Console.WriteLine calls before and after the call to the original method. Everything else will behave as normal with all of the instances of the "PerfIssue" class -- you won't get those Console.WriteLine calls from every call to PutAnimalInCage. You'll only see them in this one part of the code, because you only changed the way the method works for this instance of the PerfIssue class.

IDE Integration
Once you start playing around with this technique of using lambdas to plug in different implementations, you'll notice something with the IDE integration: There's no IntelliSense for parameters.

Your lambda properties (as viewed by the compiler) are just a property with a specific type, which happens to be a lambda. You can add a summary description that will show up when scrolling through the available methods:

/// <summary>
/// Takes the indicated animal and returns the Cage
/// it is placed in.
/// </summary>
public Func<Animal, Cage> PutAnimalInCage { get; set; }

Now IntelliSense will show, as illustrated in Figure 1.


[Click on image for larger view.]
Figure 1. Full summary descriptions.

However, when you try to get IntelliSense on your parameters, Visual Studio looks at the type of the property. In this case, it's a generic delegate (Func<T1,T2>) from the Microsoft .NET Framework. The help for that is used to display IntelliSense and you get the pop-up shown in Figure 2. Not very helpful!


[Click on image for larger view.]
Figure 2. Default parameter help.

Whether this affects you negatively or not depends on your situation. If this is part of a small component or will be used internally by a small team, the lack of IntelliSense may not be a huge issue. But if this is going to be part of a public API, you'll want to improve the IntelliSense for these methods.

To do that will take a little bit of extra work. Define a delegate specific to the method signature and document the delegate parameters. For this example, you need a delegate that takes an Animal and returns a Cage. You'll create XML documentation for this new delegate, as well, like so:

  /// <summary>
  /// Takes the indicated animal and returns the Cage
  /// it is placed in.
  /// </summary>  
  /// <param name="animal">The wild animal to be caged.</param>
  /// <returns>The steel trap that will be its home.</returns>
  public delegate Cage PutAnimalInCageDelegate(Animal animal);

Now, change your lambda property from a Func<T1,T2> to our custom delegate:

public PutAnimalInCageDelegate PutAnimalInCage { get; set; }

Notice, too, that we don't need the <summary> information either. IntelliSense will use the summary from the delegate for the property as well as the parameters. Now when we use the PutAnimalInCage lambda, we'll get more help for our parameters, as shown in Figure 3.


[Click on image for larger view.]
Figure 3. Complete parameter descriptions.

No More Mocking?
As most developers know, unit testing is -- or should be -- an important part of software development. As you make your systems more loosely coupled and rely on interfaces, your unit tests require you to mock out the "other parts" of the system you're not testing. (See my Web article, "Use Mocking Frameworks to Improve Code Quality".)

One of the goals of any mock object is to be able to control what it returns. When you're testing a widget and it needs to get a response from a "Foo" object, you don't want possible bugs in Foo to pollute your widget tests, so you use a mock (or stub) Foo while unit testing your widget.

But if you take what I've covered so far, the technique described in this article makes the need for such mocks moot. You can simply plug in a new implementation of a method instead of using a mocking framework to do it for you. A simple example starts with a Foo object that calculates a discount rate based on a customer's type, as shown in Listing 1.

Next, create a Widget that uses this Foo to calculate an amount due:

public class Widget
{
  private readonly Foo foo;
 
  public Widget(Foo foo)
  {
    this.foo = foo;
  }
 
  public decimal CalculateAmount(decimal amount, Customer customer)
  {
    var rate = foo.GetDiscountRate(customer);
    var discount = amount*rate;
    return amount - discount;
  }
}

When it comes time to test your Widget, you want to specify the code to run when Foo.GetDiscountRate is called. You don't want to use the actual implementation, as any bugs introduced there could taint the results of your Widget tests. Normally, this would be a perfect place for a mock, but because the Foo class supports plugging in a different implementation, you can do just that in your unit test:

[TestMethod]
public void Discount_rate_is_used_to_calculate_amount_due()
{
  var foo = new Foo
    {
      GetDiscountRate = c => .50m
    };
  var widget = new Widget(foo);
  var customer = new Customer();
 
  var amountDue = widget.CalculateAmount(100m, customer);
 
  Assert.AreEqual(50m, amountDue);
}

In the test here, you plug in a new implementation of the GetDiscountRate method that simply returns a discount rate of 50 percent. In your test, make sure that if $100 is passed in as the amount, you get $50 as the answer.

This technique could be used whenever you previously used mocks. Your "plugged-in" implementations in your unit tests could contain logic to verify arguments or even perform an Assert.Fail if there's a specific call you want to make sure is not made during the test.

What about scope? Suppose you don't want the GetDiscountRate to be public -- you don't want clients to be able to change how the discount rate is calculated. But you still want it available for mocking in your unit tests. This can be accomplished by making the property setter internal and then exposing internals to your unit test assembly via the InternalsVisibleTo attribute. The getter would remain the public interface to your method.

Unlike all of my other C# columns, I've approached this one as more of a "what if" learning experience. My intent was not to teach, but to merely point you in the direction of a possible, alternative approach to designing components in your applications.

I'm by no means an expert on this approach and have only been using it in sample projects for a few months now. I'm not entirely sold on the idea -- after all, polymorphism through subclassing does have precedence as a strong solution for many challenges. But I like some of the flexibility this approach offers.

Is this something you see as being useful in your projects? Or do you think it's just a "cute hack" that isn't appropriate for real-world situations? Let me know what you think.

About the Author

Patrick Steele is a senior .NET developer with Billhighway in Troy, Mich. A recognized expert on the Microsoft .NET Framework, he’s a former Microsoft MVP award winner and a presenter at conferences and user group meetings.

comments powered by Disqus

Featured

  • Creating Reactive Applications in .NET

    In modern applications, data is being retrieved in asynchronous, real-time streams, as traditional pull requests where the clients asks for data from the server are becoming a thing of the past.

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

Subscribe on YouTube