I am trying to create an abstract class (A) that returns an instance of a generic child of itself (B).
I made B's costructor internal, making the creation of a new instance without my factory impossible, this caused an error I cannot seem to find a solution for:
'A.B' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method A.Create().
Is there a solution for such a problem in C#?
namespace C
{
class P
{
static void Main() => Console.WriteLine(A.Create<A.B>().GetType());
}
public abstract class A
{
public class B : A { protected B() { } }
public static T Create<T>() where T : A, new() => new T();
}
}
public static T Create<T>() where T : A, new()
defines a public method stating that T
must have a public constructor because of the generic type constraint new()
. Therefore the constructor cannot be protected or internal.
Move the static factory method Create
to the class B
instead.
class P
{
static void Main() => Console.WriteLine(A.B.Create().GetType());
}
public abstract class A
{
public class B : A
{
private B() { }
public static B Create() => new B();
}
}
A more generic approach is to use a dictionary of factory delegates allowing you to keep the constructors private.
class P
{
static void Main() => Console.WriteLine(A.Create<A.B>().GetType());
}
public abstract class A
{
private static Dictionary<Type, Func<A>> _factories = new Dictionary<Type, Func<A>> {
[typeof(B)] = () => B.Create(), // Example using factory method.
[typeof(C)] = () => new C() // Example using constructor.
};
public static T Create<T>() where T : A => (T)_factories[typeof(T)]();
public class B : A
{
private B() { }
internal static B Create() => new B();
}
public class C : A
{
internal C() { }
}
}
In a real world example you might want to add some error checking. If making the constructors internal
is enough protection for you, you don't need the factory methods in the internal classes (shown with class C
).
You could also expose interfaces instead and keep the classes (except some static factory class) completely private.
Consider also using a dependency injection container. Their aim is to solve this type of problem (and much more).
Here's another possible solution where the only accessible way to create an inner type of A
is to use Create<T>
with the inner type as the type argument, or by reflection on the IFactory<T>
implementations.
public abstract class A
{
private A() { }
private static IDictionary<Type, IFactory> factories = new Dictionary<Type, IFactory>
{
{typeof(B), new B.BFactory() },
{typeof(C), new C.CFactory() }
};
public static T Create<T>() where T : A
=> factories.TryGetValue(typeof(T), out var factory) ? ((IFactory<T>)factory).Create() : throw new InvalidOperationException();
private interface IFactory { }
private interface IFactory<T> : IFactory
{
T Create();
}
public class B : A
{
private B() { }
public class BFactory : IFactory<B>
{
B IFactory<B>.Create() => new B();
}
}
public class C : A
{
private C() { }
public class CFactory : IFactory<C>
{
C IFactory<C>.Create() => new C();
}
}
}
This is your class:
public class B : A { protected B() { } }
The constructor is protected
; therefore, only those classes that inherit B
will be able to call the constructor. No one else.
Another Approach
Not sure if you like this but one approach you can take is to create a public abstract class and then make your concrete types private. The clients will only use the abstract interface but ask you to create a specific concrete type through a method. Something like below:
public abstract class SomeClass
{
public abstract string DoSomething();
}
public abstract class A
{
private class B : SomeClass
{
public override string DoSomething()
{
return "Something is done in B";
}
}
public static SomeClass CreateB()
{
SomeClass sc = new B();
return sc;
}
}
public static class Program
{
public static void Main()
{
var b = A.CreateB();
Console.WriteLine(b.DoSomething());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With