C# 11 and .NET 7 have introduced static virtual members in interfaces, which can be used (albeit in a contrived way) to enforce a factory method with a given signature.
public interface IBase<TDerived> where TDerived : Base<TDerived>, IBase<TDerived>
{
public static abstract TDerived CreateInstance(int a, int b);
}
public abstract class Base<TDerived> where TDerived : Base<TDerived>, IBase<TDerived>
{
protected Base(int a, int b) { }
}
And then, in some other assembly:
public class Derived : Base<Derived>, IBase<Derived>
{
static Derived IBase<Derived>.CreateInstance(int a, int b)
{
return new(a, b);
}
public Derived(int a, int b) : base(a, b) { }
}
This solution is admittedly a bit code-smelly with an private protected base class constructor and a separate interface that must be implemented. Unfortunately, there are no static abstract/static virtual methods in classes (yet?), but despite the smell this solution is functional.
I am using this pattern to create managed class instances from unmanaged data. I have an extensible (non-generic) Base class and new Derived classes may be added at any time (even from other assemblies), so I can't deterministically select the right class to instantiate at compile-time, but I can use runtime data to determine this. My Base class holds a static Dictionary<int, Func<int, int, Base>>. Then in the Base<TDerived> class constructor, I populate that dictionary (new keys are added to the dictionary at the first instantiation of a Derived class).
protected Base(int a, int b) : base(a, b) // Base<TDerived> ctor, calls non-generic Base ctor
{
// cache factory functions
_ = instanceCreators.TryAdd(GetKey(a, b), TDerived.CreateInstance);
// Func is covariant and accepts the more derived return type
// GetKey maps the unmanaged data to a unique key
}
From there, my Base class can return full instances of any of the Derived classes:
public static Base GetDerived(int a, int b) // in Base class
{
return instanceCreators[GetKey(a, b)](a, b); // calls Derived class constructor
}
Because new Derived types can be added, even from outside the assembly, there isn't a more elegant solution to creating the instances I need from the Base class. Hopefully static abstract methods will be extended to abstract classes as well so the interface can be removed, at least. This approach will work for the time being.
Edit: Somehow, I overlooked the introduction of the private protected access modifier, which is a much better fit for this absurd approach than using internal.
Edit 2: On further reflection, there's no need for the non-generic Base class in the general case. I just needed it for caching the Func that actually yields the Derived instances. Removing the intermediary, non-generic Base class makes the general case implementation simpler.