Manuel Faux
Manuel Faux

Reputation: 2457

Passing "this" to base constructor

Is there any way to pass "this" to the base constructor?

abstract class Base<TSelf>
{
    public ICollection<TSelf> List;

    public Base(ICollection<TSelf> list, TSelf self)
    {
        List = list;
        List.Add(self);
    }
}

class Implementation : Base<Implementation>
{
    public Implementation() : base(new List<Implementation>(), this)
    {
    }
}

Obviously there is a compilation error in the constructor of Implementation where this is passed to base.

I also do not see any way to instantiate list at Base level.

Upvotes: 3

Views: 890

Answers (3)

Alexander Sorokin
Alexander Sorokin

Reputation: 76

It is not commonly known, but you can easily pass a child class instance to the base class constructor without using downcasts, reflection and runtime errors.

To do so you can add an abstract method that returns TSelf.

See the example:

abstract class Base<TSelf> where TSelf : Base<TSelf>
{
    public ICollection<TSelf> List;

    public Base(ICollection<TSelf> list)
    {
        List = list;
        List.Add(GetSelf());
    }
    
    protected abstract TSelf GetSelf();
}

class Implementation : Base<Implementation>
{
    public Implementation() : base(new List<Implementation>())
    {
    }
    
    protected override Implementation GetSelf() => this;
}

where TSelf : Base<TSelf> is just the additional not the mandatory constraint.

Upvotes: 0

William
William

Reputation: 1

As @Jon Skeet has said, you cannot pass this through a regular old constructor call. It's possible to do so using reflection and the FormatterServices class, however. The example code below uses the factory pattern for better readability.


Base class implementation:

public abstract class Model<TSelf>
    where TSelf : Model<TSelf>
{
    public ICollection<TSelf> Items { get; }

    protected Model(TSelf self, ICollection<TSelf> items)
    {
        if ((self == null) || (items == null))
        {
            throw new ArgumentNullException();
        }

        if (self != this)
        {
            throw new ArgumentException();
        }

        Items = items;
        Items.Add(self);
    }
}

Derived class implementation:

public sealed class ModelImplementation
    : Model<ModelImplementation>
{
    private ModelImplementation(ModelImplementation self)
        : base(self, new List<ModelImplementation>()) { }
}

The bread and butter of this technique is the ModelFactory class, which takes an uninitialized object and manually calls the appropriate constructor on it. Support for additional constructor parameters can be implemented by modifying the GetConstructor and Invoke calls.

You should call ModelFactory.Create<ModelImplementation>() to get a new ModelImplementation in lieu of the new keyword.

Factory class implementation:

public static class ModelFactory
{
    public static Model<TSelf> Create<TSelf>()
        where TSelf : Model<TSelf>
    {
        var result = FormatterServices
            .GetUninitializedObject(typeof(TSelf));

        result.GetType()
            .GetConstructor(
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new[] { typeof(TSelf) },
                null)
            .Invoke(
                result,
                new[] { result });

        return (TSelf)result;
    }
}

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500155

No, you can't use this in a constructor initializer. You'd have to add call Add(this) afterwards - but you can do that in the Base<TSelf> constructor, so long as you cast to TSelf. You need to cast this to object first before casting to TSelf for fairly complicated reasons around the conversions allowed with type parameters, unfortunately.

You can create a List<TSelf> in the Base constructor though, with no problem. Here's sample code showing both of these:

abstract class Base<TSelf>
{
    // Let's make it a property rather than a public field...
    public ICollection<TSelf> List { get; }

    public Base()
    {
        List = new List<TSelf>();
        // This will obviously fail if you try to create a `Base<Foo>`
        // from a class that isn't a Foo
        TSelf selfThis = (TSelf) (object) this;
        List.Add(selfThis);
    }
}

class Implementation : Base<Implementation>
{
}

You can add a constraint to TSelf to make the casting failure less likely accidentally but not impossible:

abstract class Base<TSelf> where TSelf : Base<TSelf>

That doesn't stop you from writing

class Implementation : Base<Implementation> {}
class Evil : Base<Implementation> {}

Then when you construct an instance of Evil, you're trying to add an Evil reference to a List<Implementation> which can't work... and the cast fails to stop you from getting that far.

Upvotes: 8

Related Questions