I have an interface called IHierarchable, intended for both the 'Entity' class and the 'Scene' class which serves as the root of all entities.
/// <summary>
/// Signifies that an object can exist in the game hierarchy and can have children.
/// </summary>
public interface IHierarchable
{
public abstract Scene Scene { get; protected init; }
/// <summary>
/// The parent of this object.
/// </summary>
public abstract IHierarchable Parent { get; protected set; }
/// <summary>
/// Any children belongong to this object.
/// </summary>
public abstract ReadOnlyCollection<IHierarchable> Children { get; protected set; }
}
But when I implement this on my Scene class:
/// <summary>
/// A base 2D world, into which <see cref="Entity"/> instances can be placed.
/// </summary>
public class Scene : IHierarchable
{
Scene IHierarchable.Scene { get; init; }
public IHierarchable Parent { get; set; }
public ReadOnlyCollection<IHierarchable> Children { get; set; }
public Scene()
{
(this as IHierarchable).Scene = null;
}
}
I have the following error in the constructor:
Init-only property or indexer 'IHierarchable.Scene' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor
I have to implement the interface explicitly in this class, as the 'Scene' property happens to share the name of the 'Scene' class. But once I implement it this way, I'm unable to actually set the property, even though I'm setting it within Scene's constructor and using the 'this' keyword, just as the error message suggests.
You've defined your interface
type using DIM syntax which isn't what you want. I do appreciate that this is a big cause of confusion, especially as it's also a very new language feature too.
A big gotcha with DIM properties in particular is the fact that interface
types (including DIM interfaces) cannot have fields - therefore you cannot have auto-properties - so when you use C# class
/struct
auto-property syntax in a DIM interface you're actually just defining an abstract
-like property
First, change your interface
type to a good ol' fashioned interface
:
Scene
and Parent
as nullable properties, as I assume eventually the top-most IHierarchable
object will have a null
parent - and you're explicitly setting Scene = null
so I assume that's intentional.
null
or default
- that's implicit in the CLR.IHierarchable
is intended to be a read-only interface for its consumers (i.e. a consumer of IHierarchable
shouldn't be able to overwrite any of the properties - and there's no point having init
members of an interface because you can't use (post-construction) object-initializer syntax with an interface
type.Children
is a collection property it should not have a set
accessor - only get
- and use a covariant collection interface instead of concrete type (i.e. use IReadOnlyList<out T>
instead of ReadOnlyCollection<T>
), that way you can legally pass Children
around as, e.g. IEnumerable<IHierarchable>
, or IReadOnlyCollection<Object?>
without needing an unsafe cast or creating a .ToList()
copy. Using IReadOnly...<T>
interfaces also means that recipients of the collection can't modify it unexpectedly, which is another common cause of software bugs.public interface IHierarchable
{
Scene? Scene { get; }
/// <summary>The parent of this object.</summary>
IHierarchable? Parent { get; }
/// <summary>Any children belongong to this object.</summary>
IReadOnlyList<IHierarchable> Children { get; }
}
Then implement that interface
...
Scene
's Scene
property a field-backed auto-property if it's always going to be null - just make it a get
-only property that always returns null
sealed
unless you absolutely know that you need to subclass it, because subclassing and explicit interface implementations (like Scene? IHierarchable.Scene => null;
) don't play nice together.public sealed class Scene : IHierarchable
{
public Scene()
{
// Nothing needs to go here - unless it does?
}
public IHierarchable? Parent { get; }
public IReadOnlyList<IHierarchable> Children { get; } = new List<IHierarchable>();
Scene? IHierarchable.Scene => this;
// or return null:
Scene? IHierarchable.Scene => null;
}