Programmatically discourage constructor initialization?

300 views Asked by At

I have a class Foo and a static class FooFactory, which is used to create instances of Foo and Foo derived classes via the following API:

public static class FooFactory {
  public static T Create<T>() where T : Foo, new() { 
    ...
    return new T();
  }
}

The new() specialization of the T type parameter requires Foo to have a public default constructor. In other words, nothing is preventing a user of Foo to initialize the class directly via the constructor.

However, FooFactory is intended to keep track of all Foo instances, so I want to enforce the user to create all Foo and Foo derived instances via the FooFactory.

The closest I have been able to prevent direct constructor initialization is to decorate the Foo default constructor with the [Obsolete("message", error: true)] attribute:

public class Foo {      
  [Obsolete("Fail", true)]
  public Foo() { ... }
}

With this decoration the code does not compile when I call the Foo default constructor directly, whereas initialization via FooFactory.Create<Foo>() works.

But with this [Obsolete] decoration, I still have problems with derived classes. The following won't even compile due to the error: true setting for the Foo default constructor:

public class Bar : Foo { ... }

public class Baz : Foo { public Baz() : base() { ... } }

I can declare a protected Foo constructor overload that the derived classes invoke instead of the default constructor, but then I will have no ability to programmatically prevent direct initialization of the derived classes.

If I set error in the ObsoleteAttribute to false, I get compilation warnings instead, but I would like to have stronger discouragement than warnings...

Is there any way that I can programmatically prevent direct invocation of the default constructor for both Foo and derived classes when the default constructors are required to be declared public?

1

There are 1 answers

8
Alex Zaitsev On

You cannot inherit from class with private constructor only but you can make your empty constructor private and add constructor with property like createdInsideFooFactory. You should also remove new() constraint in your factory. This is a hack but will let developer know that instance should be created inside foo factory.

public class Foo
{
    private Foo()
    {
    }

    protected Foo(bool createdInsideFooFactory)
    {
        if (createdInsideFooFactory) return;

        throw new MyException();
    }
}