Playing Func in C#. Simple Examples of Serious Programming Concepts
Hello! Welcome!
First of all, that is not a typo in the title. This article is about Func<> in C# programming, not Funk music in the key of C#.
Now, if you passed my first guardian clause, let’s talk about what I will be writing about today.
Now you need to read this warning:
There's a reason why I called my Blog "Crazy Algorithmics." I will be discussing some real life challenges either I had to solve myself, or perhaps I've heard or read about. However I may be pushing my solution close to, or sometimes even beyond the reasonable approach for the specific task at hands. Or so it may look like. However, all I'm trying to do is demonstrate possible real life challenges using artificially created extra simple use cases. So I really hope you may find some interesting techniques. Enter at your own risk, and good luck!
1. The Beauty Of Simplicity
As I mentioned in the WARNING section above, I will by introducing a ridiculously simple application, but still trying to keep somewhat OOP-ish approach. So, here’s what we have.
Customer is a part of our domain model. Because of the simplicity of this example, they are almost identical. Almost, but not exactly.
- MembershipLevel for Customer is represented by immutable type, not a string.
- ProcessingLevel is a property I added to my class just to make sure the processing is going through the channel I expect.
This class is not immutable for the sake of simplicity of my demo.
Of course, we wouldn’t make it without MembershipLevel class, which is immutable and is using private constructor and static factory method.
Factory method is conducting a very basic validation and returning MembershipLevel which contains “vip” or “regular” assigned to a private string field.
ToString() method and implicit operator return the value of the string. Like I said, simple. But enough to give us some headache later.
Now let’s look at the flow in our very-very simple application:
- The method Main is instantiating the class CustomerInputForm with customer data. Then it calls CustomerInit() method passing customer data as an argument.
- CustomerInit() is calling CustomerBuilder class, passing new Customer data as an argument. In our case the Builder is doing nothing significant, but in real life application it should do what builders do – build. And it returns newly built Customer back to the caller.
- The calling method prints the Customer. For that purpose I added ToString() method to the class.
- The result – a string in the Terminal window. you can click on the pictures to make them larger.
Everything is working. What’s the problem?
While there’re no obvious problems with code execution, in my opinion this is a code smell. In order to call a builder, CustomerInit() has to have references to a Builder, a Customer and a MembershipLevel! And that is for such a simple class! What if there’s more attached? What if we are working with an aggregate using DDD approach?
What can happen if we have to modify this code?
Let’s play with this code and see how far we can stretch it to make our application flexible enough.
2. Call "Action" To Action
Builder’s job is to create a concrete implementation of the Customer(). Therefore I think it would be fair to let Builder worry about keeping or obtaining the references, not caller’s. That’s why I will start refactoring with removing reference to Customer class from calling method – CustomerInit().
- I will modify the constructor of CustomerBuilder so it will expect an argument Action<Customer>, and not a Customer. Since Action is representing a void, I would need to provide a concrete implementation, but that will be inside the Builder. And Action delegate will set the values of properties according to my specifications.
- CustomerInit method will be calling the Builder using c => c… notation
As a result, calling method does not need any references to the Customer class. However, we still have one more reference we should not have inside the calling method. That is a reference to MembershipLevel. This one may be a little bit tricky. Let’s take care of that.
3. "Func" Stands For "Function"
As a result, I can call CustomerBuilder(), and I don’t need any reference to internally used classes. I just write specifications using fat arrow => notation. Could I just modify my MembershipLevel to avoid static method? Sure! But what if it is already being used somewhere in the application? Remember SOLID: “Open for extension, closed for modification.” This MembershipLevel will give us another trouble at the end, keep reading! In a meantime, let’s modify a couple more things before we dive a little bit deeper into extension.
Writing specifications for MembershipLevel is not as easy as it was for the Customer. And that is because delegates can apply specified actions to the instance of the class, not the type. Therefore we cannot call a static factory method, FromString() in our case, which is the only way to create a MembershipLevel. At lease I did not find the way to do it. If I’m wrong I’d appreciate you sending me a note.
However, there’s something I can do. Since technically I’m still in the development mode, I can simply refactor Customer class and add a method WithMembershipLevel() to the class. That’s right, let the Customer worry about reference to MembershipLevel. It will be using it anyway as a property type.
Now watch carefully.
I will be using a little bit of "Sleight of hands" programming to keep both the calling method and a framework happy.
- Since Action<T> requires an instance of the class, I will use a Func<T, T>, setting both input and output arguments to the same type. In our case that is MembershipLevel.
- As I mentioned above, I cannot specify m=>m.FromString(..) as input type argument because FromString is a static method. Input for Func<> has to be an instance. However, I can declare a static extension method for MembershipLevel class. In this case it will be SetLevel(…). Even though this is a static method, it takes this MembershipLevel as its first argument, so it is an instance method (this may be a bit confusing). That means I can write my specification as m=>m.SetLevel(…). Input parameter of Func<> will look at it lie “what would’ve happened if this was an instance of MembershipLevel?” Then SetLevel() method creates MembershipLevel instance by calling static method, and returns it as a result. And this result of our Func<> is actually an input argument for Customer.WithMembershipLevel(…) method.
4. Cutting All Ties
Now, let’s see how far we can go with this Object-Oriented craziness. Insanity can take many shapes, and this is the path I chose.
First of all, I will add setters to the Customer class. I should also make setters for properties private. And ideally, if we’re talking DDD, I would most likely make the Customer an immutable value object, not just a class. But this will be a good topic for another post I will write later. For today’s demo this will be sufficient.
Each method is responsible for setting one property, and it returns the same instance of the Customer class.
Now let’s take a look at what it gives us:
- CustomerBuilder() can be instantiated and brought in as an argument or through dependency injection.
- My specifications Func<Customer, Customer> specs can be declared elsewhere and come in as an argument. As you can see, now I can chain these methods like LINQ. Also I can use interface segregation here to control shat methods will be exposed to the user after each chain function call. But this would go too far off this topic. There will be another blog post about that.
- Finally, all I have left for my CustomerInit(…) method is to call WithSpecs() method of the builder with specs as an argument.
5. Tucking In Loose Ends
All this brings our CustomerInit() method down to a couple of lines of code. Once we get builder through DI or as an argument, we call Specs() method and pass it into a builder as an argument.
Now it's a good time to check our progress.
This last implementation may not be necessarily how I would implement Func<> in my code. Most likely you will take a different route as well. It is not always easy to demonstrate some patterns only using a single console projects with 2-3 classes. I just showed you one of the possible ways. However, in the next examples I will talk about the challenge we may have to face, and what we can do about it.
6. The Problem Is Coming!
Let’s pretend that our application was deployed into production, and it has been working as planned. Everything has been good! However, we just received a new requirement. Our application was designed to process a specific user info, and we designed CustomerInputForm class to match input data. And now we are receiving a totally different user: Business User.
In this example I made a BusinessInputForm different enough to make my point. What should we do?
7. The Power Of Generics
Like everything else, the solution can be different. In this example I chose creating a new Builder class which inherits from the old builder. And instead of overriding the existing WithSpecs() method, I created a new one I called WithGenericSpecs<T>(…). Now I can specify the input type as T.
This method returns a regular CustomerBuilder(customer) because the rest is the same, and I kept the first constructor I created after refactoring. My Funk<T, Customer, Customer> will return a Customer object the Builder expects. I used it this way just to leverage the power if input Customer type so I could use the arrow => notation.
Now let’s take a look at this code.
The method WithGenericSpecs(…) above is expecting an object of type T and an Func<> with input type T. To make this happen, I created two extension methods for my NewCustomerBuilder() class, and I called them both GenericSpecs(…).
As you can see, the only difference in the method signature is the type of the object: CustomerInputForm vs BusinessInputForm. Yes, using this approach I can add as many input forms as I may need without modifying the main class. These methods simply reshape the input data into Customer, what the Builder expects.
8. Putting It All Together
Now let’s modify our application. At this point I want to stop using static methods, so I create an instance of the Program() class. All I am doing here is simulating input forms – CustomerInputForm and BusinessInputForm. After that I call CustomerInit(…) method for each object. Again, this is just a simulation of the input data.
I created two overloads of CustomerInput(…) method. Therefor the previous call does not have to depend on the data type, the polymorphic behavior will take care of this call.
They call GenericSpecs() and WithGenericSpecs() methods, and any additional set-up methods for the builder because obviously I don’t expect running a Builder just for Customer conversion. And they both produce a Customer() class as a result.
Let’s take a moment to enjoy the results!
9. The Final Touch
I know, I’m getting too far off the topic of this article, but I cannot leave without fixing this issue! Let’s fix it. If you remember, I used MembershipLevel immutable object as a data type for the Customer. Also I created an extension method to use it in my Func<> to pass in specifications. However, I created the code (on purpose) that is not that easy to extend. Here’s what we have:
- The class Customer is using MembershipLevel (not an interface), so we can’t substitute a concrete object without modifying the class.
- Class MembershipLevel has a private constructor, so I cannot use inheritance without changing the access modifier.
- The only way to create MembershipLevel is by calling static method FromString(…) which is conducting the value check. That’s how Bruce Wayne got his “regular” membership.
Here’s one way to do it. Perhaps not the best, but in some situations like this one it may work.
I will create another overload of extension method SetLevel(…) for MembershipLevel class. I will change the method signature by specifying BusinessInputForm as the third parameter. Since I only need it for the purpose of polymorphism, I use an underscore instead of the variable name. I won’t use it for any purpose inside my method.
This method will create the instance of MembershipLevel with whatever parameter (doesn’t matter, it will be set to “regular” anyway). Then I use System.Reflection to change a private field inside the class instance to a new value. And after that I will return the result. Not the best way perhaps, but it works.
10. In Place Of Epilogue
I know, this article cam out rather long. But I did not want to cut it in pieces, and I did not want to leave things undone. I truly hope that you found something interesting or at least mildly entertaining.
Thanks for stopping by!
Make a wonderful day happen!
Gene