C# – Creating an interface for a class automatically

No Comments

The last few years have seen a huge proliferation of interfaces for C# classes. These interfaces are very useful for DI, testing and mocking. But they come at a cost: while extracting an interface is fairly easy, you now have to keep interface and class in sync. Especially in an evolving project, this can quickly become annoying. Change a parameter on the class? Edit the interface, too! Change to class-doc? Edit the interface, too. This is tedious, and smaller things like parameter names and documentation get out of sync quickly.

Luckily, C# Source Generators offer the ability to automatically generate these interfaces for us. I even wrote a post about this topic last year, but the birth of our second child prevented me from finishing my idea. Finally, I was able to complete the code and publish it as a NuGet Package: AutomaticInterface.

AutomaticInterface takes care of creating an interface for a class – it automatically copies all public properties, methods, events and the documentation to the interface. You can use it like a normal interface and every change to your class will be picked up by the interface in the next build.

How to use it

  1. Install the nuget: dotnet add package AutomaticInterface
  2. Create an attribute with the name [GenerateAutomaticInterface]. You can just copy the minimal code from the repo. It’s the easiest way to get that attribute because you cannot reference any code from the analyzer package.
  3. Let your class implement the interface, e.g. SomeClass: ISomeClass
  4. Build the solution, the interface should now be available.

using AutomaticInterfaceAttribute;
using System;
 
namespace AutomaticInterfaceExample
{
    /// <summary>
    /// Class Documentation will be copied
    /// </summary>
    [GenerateAutomaticInterface]  // you need to create an attribute with exactly this name in your solution. You cannot reference code from the analyzer.
    class DemoClass: IDemoClass // You Interface will get the Name I+classname, here IDemoclass. 
    // Generics, including constraints are allowed, too. E.g. MyClass<T> where T: class
    {
 
        /// <summary>
        /// Property Documentation will be copied
        /// </summary>
        public string Hello { get; set; }  // included, get and set are copied to the interface when public
 
        public string OnlyGet { get; } // included, get and set are copied to the interface when public
 
        /// <summary>
        /// Method Documentation will be copied
        /// </summary>
        public string AMethod(string x, string y) // included
        {
            return BMethod(x,y);
        }
 
        private string BMethod(string x, string y) // ignored because not public
        {
            return x + y;
        }
 
        public static string StaticProperty => "abc"; // static property, ignored
 
        public static string StaticMethod()  // static method, ignored
        {
            return "static" + DateTime.Now;
        }
 
        /// <summary>
        /// event Documentation will be copied
        /// </summary>
 
        public event EventHandler ShapeChanged;  // included
 
        private event EventHandler ShapeChanged2; // ignored because not public
 
        private readonly int[] arr = new int[100];
 
        public int this[int index] // currently ignored
        {
            get => arr[index];
            set => arr[index] = value;
        }
    }
}

This will create this interface:

using System.CodeDom.Compiler;
using AutomaticInterfaceAttribute;
using System;
 
/// <summary>
/// Result of the generator
/// </summary>
namespace AutomaticInterfaceExample
{
    /// <summary>
    /// Bla bla
    /// </summary>
    [GeneratedCode("AutomaticInterface", "")]
    public partial interface IDemoClass
    {
        /// <summary>
        /// Property Documentation will be copied
        /// </summary> 
        string Hello { get; set; }
 
        string OnlyGet { get; }
 
        /// <summary>
        /// Method Documentation will be copied
        /// </summary> 
        string AMethod(string x, string y);
 
        /// <summary>
        /// event Documentation will be copied
        /// </summary> 
        event System.EventHandler ShapeChanged;
    }
}

Christian Sauer works as an IT Consultant for codecentric Solingen. He works mainly on C# or Python-based web applications. Also, he loves to build applications that bridge the gap between Data Science and more traditional applications.
Last but not least he loves cloud native applications and DevOps.

More content about Programming

Comment

Your email address will not be published. Required fields are marked *