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
- Ensure SQL-Server is installed
- Change the server name in this project app.config file from KARENS-PC to your SQL-Server instance name e.g. .\SQLEXPRESS
- Change the server name in this project app.config to match the one in bullet 2
- Create the database and populate with data using the following data script in SSMS or within Visual Studio
- Select "Restore NuGet Packages" from Solution Explorer and run.
- Build the solution.
- Run the various Windows form project
- Examine the code carefully.
- 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