This article is the second one of a series of 3 articles on C# delegates. If you haven’t gone through the part 1, I suggest you to go through it first.
In the previous article we went through a brief intro on what delegates are, how are they declared, initialized and invoked. Here we will take a simple use case where delegates can be useful.
One thing which the previous article did not mention is that, we use delegates majorly to pass functions as parameters. We will work on this usage here.
We have a Person class with FirstName and LastName as its member properties. The requirement is to get a person’s name in different formats. For one of the case, we have overridden ToString as below.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return $"{this.FirstName} {this.LastName}";
}
}
I created a small WPF application to demonstrate this. Screenshot of the UI is below. It has a set of radio buttons to select a format, then on click of “Display Name” button, names should be displayed within the list box on the right as per the selected format radio button.
Coming back to our Person class. In order for the class to support different formats of name, one way we can achieve this is by creating a parameterized ToString method in the class, send some kind of enum to decide in which format we need to return, add a switch case, construct that format and return the formatted name. Below is how this can be done.
public enum FormatType
{
Default = 1,
LastNameFirst,
LastNameOnly,
FirstNameOnly,
InitialsOnly
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return $"{this.FirstName} {this.LastName}";
}
public string ToString(FormatType format)
{
switch (format)
{
case FormatType.Default:
return this.ToString();
case FormatType.LastNameFirst:
return $"{this.LastName} {this.FirstName}";
case FormatType.LastNameOnly:
return this.LastName;
case FormatType.FirstNameOnly:
return this.FirstName;
}
return default;
}
}
Though above design would work fine but there is a problem with that. Every time we need a new format to be added, we need modify Person’s ToString method to add one more case for a new format type.
This goes against the Open-Close principle of SOLID design which states that a software entity (Person class in this case) should be open for extension but closed for modification.
This also goes against Single Responsibility principle of SOLID which states an entity should have responsibility for a single part of the program. In this case the responsibility of Person class should only be of holding the data, and not what to do with this data.
We can address both the issues using delegates
Below is a modified Person class that uses a ToString method that takes a delegate as a parameter and returns a string. We invoke the delegate inside this method and return its result, in this case it being string.
public delegate string PersonFormatter(Person persion);
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string ToString(PersonFormatter formatter)
{
return formatter(this);
}
}
We can see how the Person class is now lean and does not do anything apart from storing information.
We create a NameFormatter class with different methods to format the name. This was we keep the name formatting code separate from Person class. It has four methods as per the radio buttons in our WPF UI.
public static class NameFormatter
{
public static string Default(Person person)
{
return $"{person.FirstName} {person.LastName}";
}
public static string LastNameFirst(Person person)
{
return $"{person.LastName} {person.FirstName}";
}
public static string FirstNameOnly(Person person)
{
return person.FirstName;
}
public static string LastNameOnly(Person person)
{
return person.LastName;
}
}
Remember the WPF app screenshot above ? In the code behind file, we have the following
List of persons which is dummy data — people
Local variable of type PersonFormatter delegate — formatter
Function which assigns the above formatter variable based on which radio button is selected.
List<Person> people = new List<Person>
{
new Person { FirstName = "John", LastName = "Doe" },
new Person { FirstName = "Tony", LastName = "Stark" }
};
public PersonFormatter formatter;
public void AssignFormatter()
{
if (rdBtn_Default.IsChecked.Value)
formatter = NameFormatter.Default;
else if (rdBtn_LastNameFirst.IsChecked.Value)
formatter = NameFormatter.LastNameFirst;
else if (rdBtn_FirstNameOnly.IsChecked.Value)
formatter = NameFormatter.FirstNameOnly;
else if (rdBtn_LastNameOnly.IsChecked.Value)
formatter = NameFormatter.LastNameOnly;
}
On click of the “Display Names” button, below function is invoked.
private void DisplayName_Click(object sender, RoutedEventArgs e)
{
namesList.Items.Clear(); // clearing list box
AssignFormatter(); // based on radio button
foreach (var person in people)
namesList.Items.Add(person.ToString(formatter));
}
We clear all items from the listbox named as namesList
AssignFormatter function is invoked which assigns the type of formatter function to be assigned to our delegate variable formatter.
We iterate through people, and for each of the item we call it’s ToString method and pass our delegate formatter. If you remember, we had a ToString method which takes PersonFormatter delegate as a parameter
public string ToString(PersonFormatter formatter)
{
return formatter(this);
}
Here the formatter is invoked which in turn invokes which ever NameFormatter function was assigned earlier in the AssignFormatter based on selected radio button. The result is returned as string.
This example is a fairly simple use of delegates but gives more of an idea how they are useful to follow SOLID design principles.
In the next article we discuss how delegates are related to lambda expressions.
Top comments (0)