Reputation: 2457
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
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
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
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