1

I want to allow inheritance, but forbid the direct construction of any of the inherited classes. Instead, I want to force the usage of the custom method New().

The goal is to make sure that every instance of inherited classes is a transparent proxy of itself.

In this case, it's not possible to make the constructor private or internal. Otherwise, you can't inherit from the class anymore outside of the assembly.

Is there any elegant way to solve that? My current solution:

public abstract class Class<This> : MarshalByRefObject where This : Class<This>
{
    private static bool ShouldThrowOnConstruction = true;
    private static readonly object Lock = new object();

    public static This New()
    {
        lock (Lock)
        {
            ShouldThrowOnConstruction = false;
            var instance = (This)new ClassProxy<This>().GetTransparentProxy();
            ShouldThrowOnConstruction = true;
        }
        return instance;
    }

    protected Class()
    {
        if (ShouldThrowOnConstruction)
        {
            throw new InvalidOperationException("Direct use of the constructor is forbidden. Use New() instead.");
        }
    }
}
Mr.Yeah
  • 1,054
  • 2
  • 9
  • 21
  • 1
    Why can't you just call `New()` from the consructor? Am I missing something? – Martin Apr 01 '19 at 15:59
  • Isn't it just possible to make the constructor private? Or is that only possible in Java? – Peterdk Apr 01 '19 at 16:03
  • 1
    You could make the constructors private and call them within the `New` method. – Hugo Apr 01 '19 at 16:03
  • @Martin Calling `New()` inside the constructor would still return the original object instead of the proxy. – Mr.Yeah Apr 01 '19 at 18:49
  • @Peterdk @Halhex I've addressed your questions with the latest edit: In this case, it's not possible to make the constructor `private` or `internal`. Otherwise, you can't inherit from the class anymore outside of the assembly. – Mr.Yeah Apr 01 '19 at 18:50

1 Answers1

4

Why not use a static factory function instead of a constructor?

e.g.

public abstract class Class<This> : MarshalByRefObject where This : Class<This>
{
    public static Class<This> Build()
    {
        var instance = (This)new ClassProxy<This>().GetTransparentProxy();
    }

    protected Class() 
    {        
    }
}

That's extremely difficult to misuse, and doesn't have the same race condition problems that you required the thread locking for.

I suppose this means that all subclasses would also need to make their default constructor private. Is that outside your control?

Edit:

In the case you want to guarantee it can't be called, make the constructor throw an exception throw new InvalidOperationException("Direct use of the constructor is forbidden. Use Build() instead.");, and your GetTransparentProxy method shouldn't call new to construct the object but instead use FormatterServices.GetUninitializedObject() to bypass the constructor. That should allow the instance to be created, but it has a bit of a code smell.

Something like this:

public abstract class Class<This> : MarshalByRefObject where This : Class<This>
{
    public static Class<This> Build()
    {
        // Ensure GetTransparentProxy() calls FormatterServices.GetUninitializedObject() to create the object, rather than calling `new`
        var instance = (This)new ClassProxy<This>().GetTransparentProxy();
    }

    protected Class() 
    {
        throw new InvalidOperationException("Direct use of the constructor is forbidden. Use Build() instead.");
    }
}
Doug
  • 5,208
  • 3
  • 29
  • 33
  • That doesn't allow inheritance anymore: 'Class.Class()' is inaccessible due to its protection level (CS0122) – Mr.Yeah Apr 01 '19 at 18:53
  • Yeah, forgot to make the constructor `protected` vs `private`. Edited to reflect that. Thanks :) – Doug Apr 02 '19 at 19:04
  • Now the user of the API can still do it wrong and construct its derived class. – Mr.Yeah Apr 03 '19 at 10:10