C# - Allow Inheritance but Forbid Direct Use of Constructor

398 Views Asked by At

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.");
        }
    }
}
1

There are 1 best solutions below

3
On

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.");
    }
}