Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory of inner class objects

Tags:

c#

generics

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();
    }
}
like image 554
Remot H Avatar asked Nov 25 '17 17:11

Remot H


3 Answers

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).

like image 56
Olivier Jacot-Descombes Avatar answered Sep 30 '22 12:09

Olivier Jacot-Descombes


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();
        }
    }
}
like image 38
Mor A. Avatar answered Sep 30 '22 12:09

Mor A.


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());
    }

}
like image 31
CodingYoshi Avatar answered Sep 30 '22 12:09

CodingYoshi