C# Journey - 4. Func and Action with a practical demonstration
Embarking on a profound C# journey: exploring the power of Func and Action through Practical Demonstrations
Introduction
Delegates in C# are powerful constructs that make our code more flexible and efficient. They allow us to define and manipulate methods as first-class citizens, enabling us to create dynamic code structures. Two essential delegates in this realm are Func and Action, each with its unique capabilities, types of delegates that Microsoft made for us to make writing easier and faster. Let's explore when and how to use them effectively.
If you're unfamiliar with delegates, I recommend checking out our previous blog on the topic: link to the previous delegate blog. Because in this blog we will deal only with Func and Action
Func
Func is a versatile, generic delegate that we define ourselves, providing us with the freedom to shape its structure. It can have up to 16 input parameters and must always return a value, with the type of the return value defined by us. Notably, if we're dealing with a function that returns void, we can't use Func, it's tailored for functions with return values.
// Type17 is return value
Func<Type1, Type2, Type3, ... , Type16, Type17>
Action
On the other hand, Action, also a delegate we define, differs from Func in that it's specifically designed for void methods. It can accept up to 16 input parameters, making it useful for scenarios where we don't need a return value from the method.
While both Func and Action provide a lot of convenience, there are situations where using plain delegates becomes necessary, albeit rarely. If we encounter such cases, we must understand the limits of Func and Action.
Why not always use Func and Action?
A crucial question arises: why not always rely on Func if it simplifies our code? The answer lies in the maximum of 16 input parameters for Func and Action. Although these scenarios are infrequent, when we exceed this parameter count, we're forced to turn to plain delegates.
Examples
In this section, we'll explore a practical examples involving a Product model, delegates, Func, and Action to illustrate the concepts we've covered.
First Example
Product Model:
Let's start with our Product model, which represents products with their names and prices. This model will be essential in our demonstration.
// Product Model
namespace Delegates
{
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
}
Delegates , Func and Action:
We'll now dive into the core of our example, showcasing how delegates, Func, and Action can work together to process product data.
// Delegates, Func, Action
namespace Delegates
{
internal class ShippingFunctions
{
public List<Product> Products { get; set; } = new List<Product>();
public delegate void TotalMention (double subTotal);
public double GetTotal(TotalMention totalMention, Func<List<Product>, double, double> calculateDiscountedTotal, Action<string> messageUser)
{
double subTotal = Products.Sum(x => x.Price);
totalMention(subTotal);
string message = "Discount processing...";
messageUser(message);
return calculateDiscountedTotal(Products, subTotal);
}
}
}
Main Program:
Now, let's put it all together in our main program.
// Program.cs
namespace Delegates
{
internal class Program
{
static ShippingFunctions cart = new ShippingFunctions();
static void Main(string[] args)
{
InitData();
Console.WriteLine($"With discount:{cart.GetTotal(SubTotal, SubTotalFunc, MessageUser)}");
}
private static void SubTotal(double subTotal)
{
Console.WriteLine($"Without discount:{subTotal}");
}
private static double SubTotalFunc(List<Product> products, double subTotal)
{
if (subTotal > 100)
{
return subTotal * 0.80;
}
return subTotal;
}
private static void MessageUser(string message)
{
Console.WriteLine($"{message}");
}
public static void InitData()
{
cart.Products.Add(new Product { Name="Bosch", Price=200.20 });
cart.Products.Add(new Product { Name="Dewalt", Price=201.10 });
cart.Products.Add(new Product { Name="Makita", Price=500.50 });
cart.Products.Add(new Product { Name="Metabo", Price=50.20 });
}
}
}
Anonymous Delegates
You can utilize anonymous functions as well, they might be quicker to write, although they can get quite messy, honestly. Personally, I'd stick to the previous approach, but let's see the code differences, follow along with the commented code. The outcome remains the same.
namespace Delegates
{
internal class Program
{
static ShippingFunctions cart = new ShippingFunctions();
static void Main(string[] args)
{
InitData();
//Console.WriteLine($"With discount:{cart.GetTotal(SubTotal, SubTotalFunc, MessageUser)}");
Console.WriteLine();
// GetTotal function is returning double so we will store it inside double variable
double total = cart.GetTotal((subTotal) => Console.WriteLine($"Without discount:{subTotal}"),
(products, subTotal) =>
{
if (subTotal > 100)
{
return subTotal * 0.80;
}
return subTotal;
},
(message) => Console.WriteLine($"{message}")
);
Console.WriteLine($"With discount:{total}");
}
//private static void SubTotal(double subTotal)
//{
// Console.WriteLine($"Without discount:{subTotal}");
//}
//private static double SubTotalFunc(List<Product> products, double subTotal)
//{
// if (subTotal > 100)
// {
// return subTotal * 0.80;
// }
// return subTotal;
//}
//private static void MessageUser(string message)
//{
// Console.WriteLine($"{message}");
//}
public static void InitData()
{
cart.Products.Add(new Product { Name="Bosch", Price=200.20 });
cart.Products.Add(new Product { Name="Dewalt", Price=201.10 });
cart.Products.Add(new Product { Name="Makita", Price=500.50 });
cart.Products.Add(new Product { Name="Metabo", Price=50.20 });
}
}
}
Second Example for Func
{
// 1. way of calling the function
Func<int, int> square = delegate (int number) { return number * number; };
Console.WriteLine(square(4)); // Output: 16
// 2. way of calling the function
Func<int,int> cubic = (int number) => { return number * number * number; };
Console.WriteLine(cubic(2)); // Output: 8
// 3. way of calling the function
Func<int> ten = Ten;
Console.WriteLine(ten()); // Output: 10
}
public static int Ten()
{
return 10;
}
// You can't call void function Text
public static void Text()
{
Console.WriteLine("Hello World!");
}