Not only can classes and types apply an extension method, but interfaces, collections, and any other objects can be functionally extended using an extension method as well. We are going to discuss this in the upcoming sections.
We can extend the method in an interface in the same way we extend the method in a class or type. We still need the public static
class and the public static
method. By extending the interface abilities, we can use the extension method just after we create it without the need to create the implementation inside the class that we inherit from the interface, since the implementation is done when we declare the extension method. Let's take a look at the following DataItem
class, which we can find in the ExtendingInterface.csproj
project:
namespace ExtendingInterface { public class DataItem { public string Name { get; set; } public string Gender { get; set; } } }
We also have the following IDataSource
interface:
namespace ExtendingInterface { public interface IDataSource { IEnumerable<DataItem> GetItems(); } }
As we can see, the IDataSource
interface has only one signature of the method, which is named GetItems()
, returning IEnumerable<DataItem>
. Now, we can create a class to inherit the IDataSource
interface, which we give a name, ClubMember
; it has the implementation of the GetItems()
method, as follows:
public partial class ClubMember : IDataSource { public IEnumerable<DataItem> GetItems() { foreach (var item in DataItemList) { yield return item; } } }
From the preceding class, the GetItems()
method will yield all the data in the DataItemList
, whose content will be as follows:
public partial class ClubMember : IDataSource { List<DataItem> DataItemList = new List<DataItem>() { newDataItem{ Name ="Dorian Villarreal", Gender ="Male"}, newDataItem{ Name ="Olivia Bradley", Gender ="Female"}, newDataItem{ Name ="Jocelyn Garrison", Gender ="Female"}, newDataItem{ Name ="Connor Hopkins", Gender ="Male"}, newDataItem{ Name ="Rose Moore", Gender ="Female"}, newDataItem{ Name ="Conner Avery", Gender ="Male"}, newDataItem{ Name ="Lexie Irwin", Gender ="Female"}, newDataItem{ Name ="Bobby Armstrong", Gender ="Male"}, newDataItem{ Name ="Stanley Wilson", Gender ="Male"}, newDataItem{ Name ="Chloe Steele", Gender ="Female"} }; }
There are ten DataItem
classes in the DataItemList
. We can display all the items in the DataItemList
with the help of the GetItems()
method, as follows:
public class Program { static void Main(string[] args) { ClubMember cm = new ClubMember(); foreach (var item in cm.GetItems()) { Console.WriteLine( "Name: {0} Gender: {1}", item.Name, item.Gender); } } }
As we can can see in the preceding code, since we have inherited the ClubMember
class to the IDataSource
interface and have implemented the GetItems()
method, the instance of ClubMember
, which is cm
, can invoke the GetItems()
method. The output will be like what is shown in the following screenshot when we run the project:
Now, if we want to add the method to the interface without having to modify it, we can create a method extension to the interface. Consider that we are going to add the GetItemsByGender()
method to the IDataSource
interface; we can create the extension method as follows:
namespaceExtendingInterface { public static class IDataSourceExtension { public static IEnumerable<DataItem> GetItemsByGender(thisIDataSourcesrc,string gender) { foreach (DataItem item in src.GetItems()) { if (item.Gender == gender) yield return item; } } } }
By creating the preceding extension method, the instance of the ClubMember
class now has a method named GetItemsByGender()
. We can use this extension method in the same way as we use the method class, as follows:
public class Program { static void Main(string[] args) { ClubMember cm = new ClubMember(); foreach (var item in cm.GetItemsByGender("Female")) { Console.WriteLine( "Name: {0} Gender: {1}", item.Name, item.Gender); } } }
The GetItemsByGender()
method will return the IEnumerable
interface of the selected gender of DataItemList
. Since we only need to get all female members in the list, the output will be as follows:
We can now extend the method in the interface, and there's no need to implement the method in the inherited class since it has been done in the extension method definition.
In our previous discussion, we discovered that we apply the IEnumerable
interface in order to collect all the data we need. We can also extend the IEnumerable
interface, which is a collection type, so that we can add a method in an instance of a collection type.
The following is the code in the ExtendingCollection.csproj
project and we still use DataItem.cs
and IDataSource.cs
, which we use in the ExtendingInterface.csproj
project. Let's take a look at the following code:
public static partial class IDataSourceCollectionExtension { public static IEnumerable<DataItem> GetAllItemsByGender_IEnum(thisIEnumerablesrc,string gender) { var items = new List<DataItem>(); foreach (var s in src) { var refDataSource = s as IDataSource; if (refDataSource != null) { items.AddRange(refDataSource.GetItemsByGender(gender)); } } return items; } }
The preceding code is the extension method for the IEnumerable
type. To prevent the occurrence of an error, we have to cast the type of all sources' items using the following code snippet:
var refDataSource = s as IDataSource;
We can also extend the IEnumerable<T>
type, as follows:
public static partial class IDataSourceCollectionExtension { public static IEnumerable<DataItem> GetAllItemsByGender_IEnumTemplate (thisIEnumerable<IDataSource> src, string gender) { return src.SelectMany(x =>x.GetItemsByGender(gender)); } }
Using the preceding method, we can extend the IEnumerable<T>
type to have a method named GetAllItemsByGender_IEnumTemplate()
, which is used to get the items by a specific gender.
Now, we are ready to invoke these two extension methods. However, before we call them, let's create the following two classes, named ClubMember1
and ClubMember2
:
public class ClubMember1 : IDataSource { public IEnumerable<DataItem> GetItems() { return new List<DataItem> { newDataItem{ Name ="Dorian Villarreal", Gender ="Male"}, newDataItem{ Name ="Olivia Bradley", Gender ="Female"}, newDataItem{ Name ="Jocelyn Garrison", Gender ="Female"}, newDataItem{ Name ="Connor Hopkins", Gender ="Male"}, newDataItem{ Name ="Rose Moore", Gender ="Female"} }; } } public class ClubMember2 : IDataSource { public IEnumerable<DataItem> GetItems() { return new List<DataItem> { newDataItem{ Name ="Conner Avery", Gender ="Male"}, newDataItem{ Name ="Lexie Irwin", Gender ="Female"}, newDataItem{ Name ="Bobby Armstrong", Gender ="Male"}, newDataItem{ Name ="Stanley Wilson", Gender ="Male"}, newDataItem{ Name ="Chloe Steele", Gender ="Female"} }; } }
Now, we are going to invoke the GetAllItemsByGender_IEnum()
and GetAllItemsByGender_IEnumTemplate()
extension methods. The code will be as follows:
public class Program { static void Main(string[] args) { var sources = new IDataSource[] { new ClubMember1(), new ClubMember2() }; var items = sources.GetAllItemsByGender_IEnum("Female"); Console.WriteLine("Invoking GetAllItemsByGender_IEnum()"); foreach (var item in items) { Console.WriteLine( "Name: {0} Gender: {1}", item.Name, item.Gender); } } }
From the preceding code, first we create a sources
variable containing the array of IDataSource
. We get the data for sources
from the ClubMember1
and ClubMember2
classes. Since the source is a collection of IDataSource
, the GetAllItemsByGender_IEnum()
method can be applied to it. If we run the preceding Main()
method, the following output will be displayed on the console:
We have successfully invoked the GetAllItemsByGender_IEnum()
extension method. Now, let's try to invoke the GetAllItemsByGender_IEnumTemplate
extension method using the following code:
public class Program { static void Main(string[] args) { var sources = new List<IDataSource> { new ClubMember1(), new ClubMember2() }; var items = sources.GetAllItemsByGender_IEnumTemplate("Female"); Console.WriteLine( "Invoking GetAllItemsByGender_IEnumTemplate()"); foreach (var item in items) { Console.WriteLine("Name: {0} Gender: {1}", item.Name,item.Gender); } } }
We declare the sources
variable in the yet-to-be-displayed code, in the same way as we declared it in the previous Main()
method. Also, we can apply the GetAllItemsByGender_IEnumTemplate()
extension method to the source variable. The output will be as follows if we run the preceding code:
By comparing the two images of the output, we can see that there's no difference between them, although they extend the different collection types.
Not only can we extend an interface and a collection, we can actually extend an object as well, which means that we can extend everything. To discuss this, let's take a look at the following code, which we can find in the ExtendingObject.csproj
project:
public static class ObjectExtension { public static void WriteToConsole(this object o, stringobjectName) { Console.WriteLine( String.Format( "{0}: {1} ", objectName, o.ToString())); } }
We have a method extension named WriteToConsole()
, which can be applied to all objects in C# since it extends the Object
class. To use it, we can apply it to various objects, as shown in the following code:
public class Program { static void Main(string[] args) { var obj1 = UInt64.MaxValue; obj1.WriteToConsole(nameof(obj1)); var obj2 = new DateTime(2016, 1, 1); obj2.WriteToConsole(nameof(obj2)); var obj3 = new DataItem { Name = "Marcos Raymond", Gender = "Male" }; obj3.WriteToConsole(nameof(obj3)); IEnumerable<IDataSource> obj4 =new List<IDataSource> { new ClubMember1(), new ClubMember2() }; obj4.WriteToConsole(nameof(obj4)); } }
Before we dissect the preceding code, let's run this Main()
method, and we will get the following output on the console:
From the preceding code, we can see that all objects that are UInt64
, DateTime
, DataItem
, and IEnumerable<IDataSource>
can invoke the WriteToConsole()
extension method that we declare use the this
object as an argument.