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 class
es (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 class
es 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.