0

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?

Anders Gustafsson
  • 15,837
  • 8
  • 56
  • 114
  • 1
    Make the constructor `internal` to the library and remove the `new()` constraint. This now meant that you are responsible for the creation of all Foo types – Nkosi Sep 04 '17 at 15:28
  • If I am not mistaken, the `new()` requirement only works if the constructor is declared `public`. – Anders Gustafsson Sep 04 '17 at 15:30
  • You will have to remove the constraint – Nkosi Sep 04 '17 at 15:30
  • That is not an option... – Anders Gustafsson Sep 04 '17 at 15:30
  • well that is how the constraint works. You can't get what it is you are requesting. – Nkosi Sep 04 '17 at 15:31
  • 4
    When you're in a hole, stop digging. Make the constructor of `Foo` do the instance tracking, through invoking a static field and `this.GetType()`. – Jeroen Mostert Sep 04 '17 at 15:33
  • If you have complete control of Foo and derived types, you could make them internal and expose Interfaces they implement. Your Create method could take the interface as a parameter and through reflection determine which class to instantiate. However: indoing so, you lose the compile time checking of the parameter type for Create. – Johan Donne Sep 04 '17 at 15:41
  • Good point, Jeroen. I initially did this, but removed it when I refactored the class. When re-examining the current source code I see that it could make sense to re-introduce the registration in the constructor. Thanks! – Anders Gustafsson Sep 04 '17 at 15:41
  • 1
    Great, because as stated, your demands are literally impossible to meet otherwise. In particular, it is impossible to forbid declaring a `Baz` that can be constructed directly -- the author could *always* provide a public constructor for users that bypasses any of your demands, regardless of how `Foo` was set up. If you assume authors are always in cahoots and can assume they'll behave, my second suggestion would have been to drop the `new()` constraint and pass the constructor call as a `Func` to an internal method. This maintains type safety but is cumbersome for authors. – Jeroen Mostert Sep 04 '17 at 15:47
  • @JohanDonne Thanks for your suggestion, but this is not really in line with what I am looking for. One reason for sticking with the `new()` specialization is that it portable across platforms. Reflection, in particular against non-`public` methods, does not always play well on all platforms. – Anders Gustafsson Sep 04 '17 at 16:33
  • Is it a requirement that third parties be able to subclass `Foo`? If that is not a requirement then it is much easier to solve your problem. – Eric Lippert Sep 04 '17 at 19:53
  • Thanks for looking into this, Eric. Yes, my intention is that third parties should be able to subclass Foo. – Anders Gustafsson Sep 04 '17 at 20:43

1 Answers1

0

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();
    }
}
Alex Zaitsev
  • 2,544
  • 5
  • 18
  • 27
  • Your statement that you cannot inherit from a class with a private constructor is false. Can you figure out how to write a program that has a class with a private constructor and a derived class? There are at least two ways to do it. – Eric Lippert Sep 04 '17 at 19:51
  • @EricLippert: I can't, please enlighten us. The obvious ways I can think of are 1) provide an additional constructor that isn't private (but the answer specified "private constructor *only*", so that's cheating and 2) make the derived class a nested class (but that requires control over the definition of `Foo`, which is also cheating). – Jeroen Mostert Sep 04 '17 at 21:40
  • Yes only a private ctor. A nested class is the easy way. There's also a hard way. – Eric Lippert Sep 04 '17 at 23:50
  • Hint: does a derived class *have* to call a base class ctor? – Eric Lippert Sep 05 '17 at 01:47
  • @EricLippert: if we go by nothing but the C# language spec, then yes. It either calls a base class constructor (explicitly or implicitly) or another constructor of the same class, and loops (directly or indirectly) are prohibited, so that ultimately, a bcc must be called. Of course, at runtime you could avoid calling it through an exception, but that doesn't get around the declaration. Even if there was some hideous way to get this done, I'm pretty sure you'd necessarily end up with a class with no instances. Now spill the beans already, some of us didn't write the compiler. :-P – Jeroen Mostert Sep 05 '17 at 06:21
  • @AlexZaitsev, for several reasons, it is not an option for me to remove the `new()` constraint. What you have proposed will make it possible to define a subclass default constructor, that is correct, but I see no way to control whether or not that default constructor is called directly or via the factory? I.e., you can have `public Bar() : Foo(true)` or `public Bar() : Foo(false)`, but I see no way to control that this constructor is called either from the factory or directly. – Anders Gustafsson Sep 05 '17 at 06:30
  • @JeroenMostert: Ah, the second technique has been removed. It used to be legal to have a loop in "this" constructor calls. That lets you get around calling the base ctor, which lets the program compile. Then you cause an exception in construction, catch the exception, and resurrect the object via a finalizer, and you have an instance of a derived class that never called a base class ctor. – Eric Lippert Sep 05 '17 at 13:50
  • @EricLippert: gee, of course, now I feel silly for not thinking of that. :-P The fact that you can resurrect partially constructed objects in finalizers is scary enough on its own, though. I can confirm that *that* still works quite "well". – Jeroen Mostert Sep 05 '17 at 14:18