Source Generator to create an Interface from a class definition
$ dotnet add package AutomaticInterfaceA C# Source Generator to automatically create Interfaces from classes.
Not all .NET Interfaces are created equal. Some interfaces are lovingly handcrafted, e.g. the public interface of your .NET package which is used by your customers. Other interfaces are far from lovingly crafted, they are birthed because you need an interface for testing or for the DI container. They are often implemented only once or twice: The class itself and a mock for testing. They are noise at best and often create lots of friction. Adding a new method / field? You have to edit the interface, too!. Change parameters? Edit the interface. Add documentation? Hopefully you add it to the interface, too!
This Source Generator aims to eliminate this cost by generating an interface from the class, without you needing to do anything. This interface will be generated on each subsequent build, eliminating the friction.
using AutomaticInterface;
using System;
namespace AutomaticInterfaceExample
{
/// <summary>
/// Class Documentation will be copied
/// </summary>
[GenerateAutomaticInterface]
class DemoClass: IDemoClass // Your 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
[IgnoreAutomaticInterface]
public string? AnotherGet { get; } // ignored with help of attribute
/// <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 string CMethod<T, T1, T2, T3, T4>(string? x, string y) // included
where T : class
where T1 : struct
where T3 : DemoClass
where T4 : IDemoClass
{
return "Ok";
}
public Task<string> ASync(string x, string y)
{
return Task.FromResult("");
}
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:
#nullable enable
/// <summary>
/// Result of the generator
/// </summary>
namespace AutomaticInterfaceExample
{
/// <summary>
/// Class documentation will be copied
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.Hello" />
string Hello { get; set; }
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.OnlyGet" />
string OnlyGet { get; }
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(string, string)" />
string AMethod(string x, string y);
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.CMethod{T, T1, T2, T3, T4}(string?, string)" />
string CMethod<T, T1, T2, T3, T4>(string? x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass;
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.ASync(string, string)" />
global::System.Threading.Tasks.Task<string> ASync(string x, string y);
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.ShapeChanged" />
event global::System.EventHandler ShapeChanged;
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.ShapeChangedNullable" />
event global::System.EventHandler? ShapeChangedNullable;
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.ShapeChangedNullable2" />
event global::System.EventHandler<string?> ShapeChangedNullable2;
}
}
#nullable restoredotnet add package AutomaticInterface.using AutomaticInterface; or (Pro-tip) add global using AutomaticInterface; to your GlobalUsings.[GenerateAutomaticInterface] attribute.To validate or use the interface:
SomeClass: ISomeClassAny errors? Ping me at: christiian.sauer@codecentric.de
Newer Visual Studio Versions (2019+) can see the source code directly:

Alternatively, the Source Generator generates a log file - look out for a "logs" folder somewhere in bin/debug/... OR your temp folder /logs. The exact location is also reported on Diagnosticlevel Info.
Please create an issue and a minimally reproducible test for the problem.
PRs are welcome! Please make sure that you run CSharpier on the code for formatting.
Should be simply a build and run Tests Note that we use Verify for testing. It's recommended that you use te Verify plugin for your UI.
notnull type constraints on generic type parametersnew() type constraints on generic type parameters; emit params keyword for method parameters. Thanks, @simonmckenzie!init in property settersasync Task should no longer generated as System.Threading.Task. I hope this does not break thingsForAttributeWithMetadataName to improve performance. Thanks crwsolutions!GenerateAutomaticInterfaceAttribute, as it is generated automatically now. Thanks crwsolutions!IgnoreAutomaticInterfaceAttribute, as it is generated automatically now. Thanks crwsolutions!new void DoSomething()). Thanks simonmckenzie!ref / in / out parameters. Thanks mohummedibrahimtrue / false correctly. Thanks simonmckenzie!new, previously duplicates entries where createdref parameters@eventvoid test(string x = null) should now work.