Generic Interfaces C# Part 1

Introduction

An interface contains only the signatures of methods, properties, events or indexers. A class or struct that implements the interface must implement the members of the interface that are specified in the interface definition (definition from Microsoft documents). Interfaces are used many times when developing applications even without this is not known to a developer and in some cases see an Interface being used without understanding reasons for an Interface. Two common Interfaces when working with Entity Framework, IQueryable<T> and IEnumerable<T> while other common operations uses Interfaces also e.g. performing a case insensitive distinct list which uses IEqualityComparer<T>.

Main benefit

Are about code readability, code maintainability and code semantics. 

Readability: An interface constitutes a declaration about intentions. It defines a capability of your class, what your class can do. If you implement ISortable you're clearly stating that your class can be sorted. 

Semantics: By providing interfaces and implementing them you're actively separating concepts in a similar way HTML and CSS does. A class is a concrete implementation of an "object class" some way of representing the reality by modeling general properties of real-life objects or concepts. An interface defines a behavioral model, a definition of what an object can do. Separating those concepts keeps the semantics of your code clearer. That way some methods may need an instance of an animal class while other may accept whatever object you throw at them. 

Maintainability: Interfaces helps to reduce coupling and therefore allow you to easily interchange implementations for the same concept without the underlying code being affected. You can change the implementation of an IMessage easily by defining a new class that implements the interface. Compare that to systematically replacing all references from CMessage to CNewMessageClass.

Implementing in house Interfaces

The following provides ideas for using generic Interfaces in developer code for various reasons which are defined in Main Benefits section above which can be used as a boilerplate for creating new Interfaces, used, modified or for creating completely new Interfaces implementing single or more than one Interface to a class. 

Goals for part 1

The goal is to provide examples that are relatable to developers starting off with very simply code samples moving to complex code samples utilizing Microsoft Entity Framework against SQL-Server database. In the complex code samples, there will be room for improvement which will follow in part 2 of this series. Code presented in this part of the series can not used without moving to a more generic format in part 2 of the series.

Recommendations

 Read Microsoft documentation on both generics and interfaces is highly recommended along with take time to carefully walkthrough code samples in this series to properly learn about working with generic interfaces as this series is not all encompassing, covering all aspects of generic interfaces.

Basic example

The following example has the following Interface which provides a contract that any class which implements the Interface must implement a Speak method which returns a string value.

public interface  IAnimal
{
    string Speak();
}

Three classes will implement IAnimal, Person, Dog and Cat.

Person

public class  Person : IAnimal
{
    public string  Speak() => "Hello";
}

Dog

public class  Dog : IAnimal
{
    public string  Speak() => "Bark Bark";
}

Cat

public class  Cat : IAnimal
{
    public string  Speak() => "Meow meow";
}

To see this work create an instance of each of the above classes

var person = new  Person();
Console.WriteLine($"\tPerson says {person.Speak()}");
 
var dog = new  Dog();
Console.WriteLine($"\tDog says {dog.Speak()}");
 
var cat = new  Cat();
Console.WriteLine($"\tCat says {cat.Speak()}");

Results from each of the Console.WriteLine

Person says Hello
Dog says Bark Bark
Cat says Meow meow

None of the above are generic, to create a generic class the class will be generic via <T> after the method name then to ensure that T implements IAnimal a where constraint is placed on the class followed by new(). The new constraint specifies that a type argument in a generic class declaration must have a public parameterless constructor. The single method can have any name that makes sense. Within the method a new instance of T is created followed by calling the Speak method.

public class  AnimalGenericClass<T> where T : IAnimal, new()
{
    public void  CallThisMethodWhateverYouWant()
    {
        T item = new  T();
        Console.WriteLine($"\t{item.Speak()}");
    }
}

 
To use AnimalGenericClass pass either Person, Dog or Cat as T. Once an instance of the class is created call the single method.

var personDemo = new  AnimalGenericClass<Person>();
personDemo.CallThisMethodWhateverYouWant();
 
var dogDemo = new  AnimalGenericClass<Dog>();
dogDemo.CallThisMethodWhateverYouWant();
 
var catDemo = new  AnimalGenericClass<Cat>();
catDemo.CallThisMethodWhateverYouWant();

The results are the same as the non-generic method shown prior to this.

Person says Hello
Dog says Bark Bark
Cat says Meow meow

From here there are generic properties. In this example a generic type is specified a second parameter onto the class e.g.  in this case TEntity represents Person, Dog or Cat while TVariable is a generic type.

public class  AnimalGenericClass<TEntity, TVariable> where TEntity : IAnimal, new()
{
    public void  CallThisMethodWhateverYouWant()
    {
        TEntity item = new  TEntity();
        Console.WriteLine($"\t{item.Speak()}");
    }
    public TVariable GenericVariable {get;set;}
    public override  string ToString()
    {
        return $"{GenericVariable}";
    }
}

When creating a new instance of AnimalGenericClass the second parameter is defined a string.

Here three instances are created, one for Person, one for Dog and one for Cat.

var p1 = new  AnimalGenericClass<Person, string>{GenericVariable = "John"};
Console.WriteLine($"\t{p1.GenericVariable}");
var d1 = new  AnimalGenericClass<Dog, string> { GenericVariable = "Fiddo" };
Console.WriteLine($"\t{d1.GenericVariable}");
var c1 = new  AnimalGenericClass<Cat, string> { GenericVariable = "Kitty" };
Console.WriteLine($"\t{c1.GenericVariable}");

Output

Generic class  example with generic property
    John
    Fiddo
    Kitty

Another way to work with a generic property is to use Convert.ChangeType as shown in SampleProcedure.

public class  AnimalGenericClass<TEntity, TVariable> where TEntity : IAnimal, new()
{
    public void  CallThisMethodWhateverYouWant()
    {
        TEntity item = new  TEntity();
        Console.WriteLine($"\t{item.Speak()}");
    }
     
    public void  SampleProcedure(string information)
    {
        GenericVariable = (TVariable) Convert.ChangeType(information, typeof(TVariable));
    }
 
    public TVariable GenericVariable {get;set;}
    public override  string ToString()
    {
        return $"{GenericVariable}";
    }
}

Experimenting

While developing a solution in Visual Studio different versions of a class may be needed rather than to use one class and modify the class over and over then rely on source code to get back a specific version. In the following project there three versions of a Person class which are used in a Window form. To allow three versions to be used is simple, there are three using statements with aliases.

using Normal = CommonPractice.BaseExampleClasses;
using nf = CommonPractice.BaseNotifyClasses;
using jetVersion = CommonPractice.BaseJetNotifyClasses;

Then to use them the alias is used e.g.

((List<nf.ColorItem>)ColorsComboBox1.DataSource).AddRange(bColors.ToArray());
((Normal.Person)listBox1.Items[0]).FirstName = "Kane";
((jetVersion.Person)_personBindingSource[0]).FirstName = "Kane";

The challenge is how to access these different versions of the Person class? By adding an Interface IPerson.

using CommonPractice.Interfaces;
 
namespace CommonPractice.BaseExampleClasses
{
    public class  Person : IPerson
    {
        public int  Id { get; set; }
        public string  FirstName { get; set; }
        public string  LastName { get; set; }
        public override  string ToString() => $"{FirstName} {LastName}";
    }
}

In this version there are two Interfaces.

using System.ComponentModel;
using System.Runtime.CompilerServices;
using CommonPractice.Interfaces;
 
namespace CommonPractice.BaseNotifyClasses
{
    public class  Person : INotifyPropertyChanged, IPerson
    {
        private string  _firstName;
        private string  _lastName;
        public int  Id { get; set; }
 
        public string  FirstName
        {
            get => _firstName;
            set
            {
                _firstName = value;
                OnPropertyChanged();
            }
        }
 
        public string  LastName
        {
            get => _lastName;
            set
            {
                _lastName = value;
                OnPropertyChanged();
            }
        }
 
        public override  string ToString() => $"{FirstName} {LastName}";
 
        public event  PropertyChangedEventHandler PropertyChanged;
        protected virtual  void OnPropertyChanged([CallerMemberName]  string  propertyName = null)
        {
            PropertyChanged?.Invoke(this, new  PropertyChangedEventArgs(propertyName));
        }
    }
}

To access either one an item is not typed as Person but as IPerson.

MessageBox.Show(((IPerson)_personBindingSource.Current).LastName);

For a complete working code sample clone the following GitHub repository.

Interfaces for data operations

When working with data in a conventional windows form solution there are generally two paths considered, the first is to place all data operations within one or more forms while the second consideration is to break out data operations out of forms into a single class. The second option can be done also by breaking out data operations to a class per table to work on e.g. instead of having one class for customers, orders, products etc. break out so there is a class for customers, another class for orders and finally another class for products which can be expanded for each table other than references tables in a database.

Breaking out data operations into smaller classes is how many professional write code for Windows Forms and with web projects will start off in a similar fashion which breaks down code by model (a table entity), a DTO (Data Transfer Object) and repository along views and view models which is not conducive in windows form projects.

What will be a common part of working with data separated into different classes is common methods will be needed, a method to get all data for a table, get data by primary key, add records, update records, remove records, find records by one or more conditions. This is where an Interface can keep consistency between each class.

The following is a base Interface for basic operations as per above.

public interface  IGenericRepository<TEntity> where TEntity : class
{
    IEnumerable<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate);
    IEnumerable<TEntity> GetAll();
    TEntity GetById(object id);
    TEntity Insert(TEntity obj);
    void Update(TEntity obj);
    void Delete(object id);
    int Save();
    /// <summary>
    /// DbContext
    /// </summary>
    object Context { get; set; }
}

To implement an Interface a concrete class is needed, in this case an Employee class created from Entity Framework 6 code first.

namespace NorthWindEntityFramework
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
 
    public partial  class Employee
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Usage", 
            "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Employee()
        {
            Orders = new  HashSet<Order>();
        }
 
        [Key]
        public int  EmployeeID { get; set; }
 
        [Required]
        [StringLength(20)]
        public string  LastName { get; set; }
 
        [Required]
        [StringLength(10)]
        public string  FirstName { get; set; }
 
        [StringLength(30)]
        public string  Title { get; set; }
 
        [StringLength(25)]
        public string  TitleOfCourtesy { get; set; }
 
        public DateTime? BirthDate { get; set; }
 
        public DateTime? HireDate { get; set; }
 
        [StringLength(60)]
        public string  Address { get; set; }
 
        [StringLength(15)]
        public string  City { get; set; }
 
        [StringLength(15)]
        public string  Region { get; set; }
 
        [StringLength(10)]
        public string  PostalCode { get; set; }
 
        [StringLength(15)]
        public string  Country { get; set; }
 
        [StringLength(24)]
        public string  HomePhone { get; set; }
 
        [StringLength(4)]
        public string  Extension { get; set; }
 
        public string  Notes { get; set; }
 
        public int? ReportsTo { get; set; }
 
        [System.Diagnostics.CodeAnalysis.SuppressMessage(
            "Microsoft.Usage", 
            "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public virtual  ICollection<Order> Orders { get; set; }
    }
}

Next a class is needed to work with Employee data and implement methods from IGenericRepository (this is one example in part 2 Interfaces will be implemented directly on a class rather than as a property).

Here is the important parts, defining a generic class via EmployeesOperations<TEntity> and where TEntity : class to ensure only a class can be TEntity.

public class  EmployeesOperations<TEntity> where TEntity : class
{
    private readonly  IGenericRepository<TEntity> _repository = null;
    private readonly  NorthContext _northContext = new NorthContext();
 
    public EmployeesOperations()
    {
        _repository = new  GenericRepository<TEntity, NorthContext>();            
    }
 
    public EmployeesOperations(IGenericRepository<TEntity> repository)
    {
        _repository = (IGenericRepository<TEntity>) Convert.ChangeType(repository, typeof(TEntity));
    }

The same applies for other tables in a database, in this case a customer model.

public class  CustomersOperations<TEntity> where TEntity : class
{
    private IGenericRepository<Customer> _repository = null;
    public CustomersOperations()
    {
        _repository = new  GenericRepository<Customer, NorthContext>();
    }
 
    public CustomersOperations(IGenericRepository<Customer> repository)
    {
        _repository = repository;
    }

Back to working with Employee class, to create a new instance of the EmployeeOperations class in a form as a private variable.

public partial  class Form1 : Form
{
    private EmployeesOperations<Employee> _employeesOperations =  new  EmployeesOperations<Employee>();
  • Implementing method from IGenericRepository for Employee vs Customers or other table will have a common method name but how they are written will be dependent on rules of the database (for instance prohibit removal of a record that other tables are dependent on where in some cases its okay to cascade deletions in either direction while in other cases this would leave orphan data).
  • The generic constructor explicitly defines the DbContext while the overloaded constructor implicitly creates the DbContext which means this class used with the default constructor is not fully generic which will be changed in part 2 of this series to be fully generic. 
  • When implementing methods like GetById, Edit, Delete etc. parameter names can be generic parameter names or meaningful parameter names as per figure 1. This can be a defined rule for a development team or for a single developer a personal choice.

Figure 1

public TEntity Edit(int employeeIdentifier)
{
 
    TEntity entity = _repository.GetById(employeeIdentifier);
    return entity;
 
}
 
// or
 
public TEntity Edit(int identifier)
{
 
    TEntity entity = _repository.GetById(identifier);
    return entity;
 
}

There can be optional methods for versions of a method were the default is synchronous and an optional asynchronous version (see figure 2) dependent of intended amount of data to be loaded, for example the employee table may have a small footprint while customers may have a large footprint of records.

Figure 2

/// <summary>
/// Uses IGenericRepository GetAll synchronously 
/// </summary>
/// <returns></returns>
public IEnumerable<TEntity> GetEmployees()
{
    return _repository.GetAll();
}
/// <summary>
/// Uses IGenericRepository GetAll asynchronously
/// </summary>
/// <returns></returns>
public async Task<IEnumerable<TEntity>> GetEmployeesAsync()
{
    IEnumerable<TEntity> iEnumerableResults = await Task.Run(() => _repository.GetAll());
    return iEnumerableResults;
}

For finding records generically an Expression<TDelegate> which represents a strongly typed lambda expression as a data structure in the form of an expression tree works here.

/// <summary>
/// Perform a search
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public IEnumerable<Employee> SearchFor(Expression<Func<Employee, bool>> predicate)
{
    return _northContext.Set<Employee>().Where(predicate).AsEnumerable();
}

Example usage with hard coded values which may come from a control on a form.

private void  SearchForButton_Click(object sender, EventArgs e)
{
    var employees = _employeesOperations.SearchFor(
        emp => emp.Title == "Sales Representative"  && emp.Country == "USA");
 
    var results = employees.Select(
        emp => $"{emp.SearchForInformation}").ToDelimitedString("\n");
 
    MessageBox.Show(results);
 
}

Switching to customers 

var customers = _customersOperations.SearchFor(
    customer => customer.ContactTypeIdentifier == 9);

Think of the SearchFor method as a generic way to collect data with a where predicate.

Once each method has been written these methods are available in a form or other class. There is one major issue, each method implemented by IGenericRepository are done in methods names different from the method names in the Interface, this will be remedied in part 2 of this series. Why has this not been remedied in this article? because to learn proper generics for occasional or developer just starting off with generic Interfaces may be a little to much so the idea is to step slowly into a full implementation of a generic class which implements a generic Interface. 

Preparing the source code from GitHub to run

  1. Ensure SQL-Server is installed
  2. Change the server name in this project app.config file from KARENS-PC to your SQL-Server instance name e.g. .\SQLEXPRESS
  3. Change the server name in this project app.config to match the one in bullet 2
  4. Create the database and populate with data using the following data script in SSMS or within Visual Studio
  5. Select "Restore NuGet Packages" from Solution Explorer and run.
  6. Build the solution.
  7. Run the various Windows form project
  8. Examine the code carefully.
  9. Repeat steps 7 and 8 to learn.

Summary

In this article first steps have been taken to work with generic classes and generic interfaces while only part of the code is generic part 2 will introduce full generic class constrained by a generic class where all generic code will be placed into a separate solution usable by one or more projects in different Visual Studio solutions.

Peeking ahead the following shows code using multiple interfaces.

namespace CompanyName.Common.Interfaces
{
    public interface  IBaseEntity
    {
        int Id { get; set; }
        DateTime ModifiedDate { get; set; }
        int ModifiedByUserId { get; set; }
    }
}
 
 
namespace CompanyName.Common.Interfaces
{
    public interface  ISoftDeleteRepository<TEntity> : ICompanyRepository<TEntity>
        where TEntity : class, IBaseSoftDeleteEntity
    {
        Task<bool> Undelete(TEntity pEntity);
 
        Task<bool> HardDelete(TEntity pEntity);
    }
}

Recommend cloning the source code from this repository on GitHub or to simply download the code and open in Visual Studio.

Next up is part 2 of the series.

See also

Usage of Interfaces and Abstract Classes in Real World (and Difference between them)
.NET 4.5 Read-Only Interfaces
WPF 4.5: Validating Data in Using the INotifyDataErrorInfo Interface

Source code

Find source code in the following GitHub repository