Developer Tips: C# Extension Methods on Interfaces

Our guest blogger, Andrew, covers the topic of creating extension methods on interfaces making dependency injection easier.

Written by Andrew Hinkle • Last Updated: • Develop •
USB Extension cables

I write my code with dependency injection in mind.  Given that extension methods are static and I've only ever seen them implemented on concrete classes I've avoided them for a while.  While watching "C# Extension Methods by Elton Stoneman" PluralSight course I learned that you may apply extension methods to interfaces.  This was reinforced with the article "Extension Methods Guidelines in C# .NET by Michael Shpilt."  This has brought extension methods back into my toolkit.

Elton gave a great example of a use case involving design patterns.  His example involved three different repositories that shared an interface that implemented a method called "GetItems".  He continued that he wanted to add a new method "DistinctItems" to the repository interface, but did not want to create an implementation for each repository class.  Instead he created an extension method on the repository interface.  Cool!

I highly recommend watching Elton's PluralSight course for more information and advanced examples. All of his examples are followed up with unit tests and design pattern best practices in mind. 

I expect a "DistinctItems" method to be performant and efficient, so at some point I'd expect the implementation to be unique in each repository.  Instead I liked the idea of adding the "DistinctItems" method as an extension of IEnumerable<T> where T is an Item in the following example.  Below is an implementation of an extension method on an interface.  The code base is located with another example at:

Project: Tips.ExtensionMethods.ExtendInterfaces

Item

This is the model the repositories will return.

namespace Tips.ExtensionMethods.ExtendInterfaces.Models
{
    internal class Item
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public bool IsActive { get; set; }
    }
}

IRepository

The interface does not contain the method "DistinctItems." We'll extend the interface next.

using System.Collections.Generic;
using Tips.ExtensionMethods.ExtendInterfaces.Models;

namespace
 Tips.ExtensionMethods.ExtendInterfaces.Repositories {     internal interface IRepository     {         IEnumerable<Item> GetItems();     } }

IEnumerableItemExtension

I liked this implementation of LINQ to get distinct items.

using System.Collections.Generic;
using System.Linq;
using Tips.ExtensionMethods.ExtendInterfaces.Models;

namespace
 Tips.ExtensionMethods.ExtendInterfaces.ExtensionMethods {     internal static class IEnumerableItemExtension     {         // Inspiration         // https://stackoverflow.com/questions/489258/linqs-distinct-on-a-particular-property
        public static IEnumerable<Item> DistinctItems(this IEnumerable<Item> items)         {             return items.GroupBy(item => (item.Id, item.Name, item.IsActive)).Select(item => item.First());         }     } }

Project: Tips.ExtensionMethods.ExtendInterfaces.Test

ITestItemFactory

Even though we're in a test project, I still follow dependency injection principles and created an interface for the test item factory used in the mock repository factory.

using System.Collections.Generic;
using Tips.ExtensionMethods.ExtendInterfaces.Models;
namespace Tips.ExtensionMethods.ExtendInterfaces.Tests.Repositories
{
    internal interface ITestItemFactory
    {
        IEnumerable<Item> CreateExpectedUniqueItems();
        IEnumerable<Item> CreateDatabaseItems();
        IEnumerable<Item> CreateFileItems();
        IEnumerable<Item> CreateStreamItems();
    }
}

TestItemFactory

I created an item factory to group the test data together for easier maintenance.

using System.Collections.Generic;
using System.Linq;
using Tips.ExtensionMethods.ExtendInterfaces.Models;

namespace
 Tips.ExtensionMethods.ExtendInterfaces.Tests.Repositories {     internal class TestItemFactory : ITestItemFactory     {         public IEnumerable<Item> CreateExpectedUniqueItems() => new List<Item>         {             new Item {Id = 1, Name = "A", IsActive = true},             new Item {Id = 2, Name = "B", IsActive = false},             new Item {Id = 3, Name = "C", IsActive = true},             new Item {Id = 4, Name = "D", IsActive = false}, // different             new Item {Id = 4, Name = "D", IsActive = true},  // different             new Item {Id = 5, Name = "E", IsActive = true}         };
        public IEnumerable<Item> CreateDatabaseItems() =>             CreateExpectedUniqueItems().Where(                 item => new List<int>{ 1, 2, 3 }.Contains(item.Id));
        public IEnumerable<Item> CreateFileItems() =>             CreateExpectedUniqueItems().Where(                 item => new List<int> { 2, 3, 4 }.Contains(item.Id)                         && !(item.Id == 4 && item.IsActive));         public IEnumerable<Item> CreateStreamItems() =>             CreateExpectedUniqueItems().Where(                 item => new List<int> { 3, 4, 5 }.Contains(item.Id)                         && !(item.Id == 4 && !item.IsActive));     } }

MockRepositoryFactory

This factory created the mock repositories with the test items given the test item factory.

using System.Collections.Generic;
using Moq;
using Tips.ExtensionMethods.ExtendInterfaces.Models;
using Tips.ExtensionMethods.ExtendInterfaces.Repositories;

namespace
 Tips.ExtensionMethods.ExtendInterfaces.Tests.Repositories {     internal class Item     {         public int Id { get; set; }         public string Name { get; set; }         public bool IsActive { get; set; }     }
    internal interface IRepository     {         IEnumerable<Item> GetItems();     }
    internal class MockRepositoryFactory     {         private readonly TestItemFactory _testItemFactory;
        public MockRepositoryFactory(TestItemFactory testItemFactory)         {             _testItemFactory = testItemFactory;         }
        // Assume this repository gets items via a database.         public IRepository CreateMockDatabaseRepository() => CreateMockDatabaseRepository(_testItemFactory.CreateDatabaseItems());
        // Assume this repository gets items via a file.         public IRepository CreateMockFileRepository() => CreateMockDatabaseRepository(_testItemFactory.CreateFileItems());
        // Assume this repository gets items via a stream.         public IRepository CreateMockStreamRepository() => CreateMockDatabaseRepository(_testItemFactory.CreateStreamItems());
        private static IRepository CreateMockDatabaseRepository(IEnumerable<Item> list)         {             var mockRepository = new Mock<IRepository>();             mockRepository.Setup(x => x.GetItems()).Returns(list);             return mockRepository.Object;         }     } }

RepositoryTests

Test method "GetItemsDistinctTest" tests the LINQ method implementation on its own. "GetItemsDistinctExtensionTest" tests the extension method.

using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Tips.ExtensionMethods.ExtendInterfaces.ExtensionMethods;
using Tips.ExtensionMethods.ExtendInterfaces.Models;

namespace
 Tips.ExtensionMethods.ExtendInterfaces.Tests.Repositories {     [TestClass]     public class RepositoryTests     {         private readonly List<Item> _allItems = new List<Item>();         private readonly IEnumerable<Item> _expectedItems;
        public RepositoryTests()         {             var testItemFactory = new TestItemFactory();             _expectedItems = testItemFactory.CreateExpectedUniqueItems();             var factory = new MockRepositoryFactory(testItemFactory);             var repository1 = factory.CreateMockDatabaseRepository();             var repository2 = factory.CreateMockFileRepository();             var repository3 = factory.CreateMockStreamRepository();             _allItems.AddRange(repository1.GetItems());             _allItems.AddRange(repository2.GetItems());             _allItems.AddRange(repository3.GetItems());         }
        [TestMethod]         public void GetItemsDistinctTest()         {             var actualItems = _allItems.GroupBy(item => (item.Id, item.Name, item.IsActive)).Select(item => item.First());             AssertDistinctItems(_expectedItems.ToList(), actualItems.ToList());         }

        [TestMethod]         public void GetItemsDistinctExtensionTest()         {             var actualItems = _allItems.DistinctItems();             AssertDistinctItems(_expectedItems.ToList(), actualItems.ToList());         }
        private static void AssertDistinctItems(IReadOnlyList<Item> expectedItems, IReadOnlyList<Item> actualItems)         {             Assert.IsNotNull(expectedItems);             Assert.IsNotNull(actualItems);
            Assert.AreEqual(expectedItems.Count, actualItems.Count);
            for (var i = 0; i < expectedItems.Count; i++)             {                 Assert.AreEqual(expectedItems[i].Id, actualItems[i].Id);                 Assert.AreEqual(expectedItems[i].Name, actualItems[i].Name);                 Assert.AreEqual(expectedItems[i].IsActive, actualItems[i].IsActive);             }         }     } }

Conclusion

The code base can be found at:

While you can't mock static extension methods on concrete classes, you can mock them on interfaces and that adds another tool in your toolbox when working with dependency injection.

How do you use extension methods? Did you create extension methods on interfaces? How do you test your extension methods? Post your comments below and let's discuss.

Did you like this content? Show your support by buying me a coffee.

Buy me a coffee  Buy me a coffee
Picture of Andrew Hinkle

Andrew Hinkle has been developing applications since 2000 from LAMP to Full-Stack ASP.NET C# environments from manufacturing, e-commerce, to insurance.

He has a knack for breaking applications, fixing them, and then documenting it. He fancies himself as a mentor in training. His interests include coding, gaming, and writing. Mostly in that order.

comments powered by Disqus