简体   繁体   中英

Generics in a tree

I have a segment class which is a node in the tree. There's no Tree class. The tree is just made up of segments.

public abstract class Segment 
{
    public List<Segment> Descendants { get; } => new List<Segment> Descendants;
    public abstract SegmentVisual VisualRepresentation { get; }
}

SegmentVisual is a class used to draw the segment to the screen. Both classes are abstract because I have different types of segments and each of it needs to be drawn differently.

Here's how SegmentVisual looks like:

public abstract class SegmentVisual : DrawingVisual 
{
    protected SegmentVisual(Segment owner){
        this.Owner = owner;
    }

    public Segment Owner { get; }
    public List<SegmentVisual> Descendants { get; } = new List<SegmentVisual>();

    public void Redraw()
    {
        this.RedrawItself();

        foreach (SegmentVisual visual in this.Visuals)
        {
            visual.Redraw();
        }
    }

    public abstract void RedrawItself();
}

Just like the Segment, the Visual also has descendants so that a single visual can be added to screen to draw itself and all of its descendants

Here's an implementation:

public class LineSegment : Segment
{
    public LineSegment()
    {
        this.VisualRepresentation = new LineSegmentVisual(this);
    }

    public override SegmentVisual VisualRepresentation { get; }

    public Pen Stroke { get; set; }
}

.

public class LineSegmentVisual : SegmentVisual
{
    public LineSegmentVisual(LineSegment owner) // Resharper suggests this can be Segment base class
        : base(owner)
    {
    }

    public override void RedrawItself()
    {
        using (DrawingContext ctx = this.RenderOpen())
        {
            var owner = (LineSegment)this.Owner;
            ctx.DrawLine(owner.Stroke, this.Owner.Position, this.Owner.ControlPointPos);
        }
    }
}

My problem is with the last class. You can see the ReSharper suggestion. And I am not feeling very good with down casting all the time. If I had to retrieve the owner at some other place, I would have to downcast to get the Stroke property again.

If I make SegmentVisual generic SegmentVisual<T> , with its owner being T , it introduces a new obstacle because now the SegmentVisual can only contain descendants of SegmentVisual<T> which should not be the case because I want it to contain any type of SegmentVisual , I just want the owner to be strongly typed.

I just want the SegmentVisual to be able to correspond to a specific class so that its Owner property is strongly typed. I can't seem to figure this out.

You can use new to declare strongly typed Owner in your subclass:

public class LineSegmentVisual : SegmentVisual
{
    new LineSegment Owner { get { return (LineSegment)base.Owner; } }

    public LineSegmentVisual(Segment owner)
        : base(owner)
    {
    }

    public override void RedrawItself()
    {
        using (DrawingContext ctx = this.RenderOpen())
        {
            var owner = this.Owner;
            ctx.DrawLine(owner.Stroke, this.Owner.Position, this.Owner.ControlPointPos);
        }
    }
}

This way base.Owner will be casted each time you call this.Owner , but at least you'll avoid repetitive code.


Second way is to use inheritance. Declare SegmentVisual with basic functionality

abstract class SegmentVisual
{
    public List<SegmentVisual> Descendants { get; private set; }

    ...
}

and OwnedSegmentVisual with strongly typed owner

abstract class OwnedSegmentVisual<TOwner>: SegmentVisual where TOwner: Segment
{
    public TOwner Owner { get; private set; }

    protected OwnedSegmentVisual(TOwner owner)
    {
        Owner = owner;
    }
}

Owner can be used in subclasses without casting and common functionality can just use SegmentVisual .


Third way is to use generics covariance, but you'll have to declare interfaces for your types:

public class Program
{
    public static void Main()
    {
        var sv = new LineSegmentVisual();
        sv.Descendants = new List<ISegmentVisual<Segment>> { new SquareSegmentVisual() };
    }
}


abstract class Segment {}

class LineSegment : Segment {}

class SquareSegment: Segment {}

interface ISegmentVisual<out TOwner>
{
    TOwner Owner { get; }

    List<ISegmentVisual<Segment>> Descendants { get; }
}

class LineSegmentVisual : ISegmentVisual<LineSegment>
{
    public LineSegment Owner { get; set; }
    public List<ISegmentVisual<Segment>> Descendants { get; set; }
}

class SquareSegmentVisual : ISegmentVisual<SquareSegment>
{
    public SquareSegment Owner { get; set; }
    public List<ISegmentVisual<Segment>> Descendants { get; set; }
}

Hope, this would be helpful.

C# does not support return type variance. That is, the following is not possible:

class Foo
{
    virtual Base Owner{ get; }
}

class Bar: Foo
{
    override Derived Owner { get; } // where Dervided: Base
}

You will get a compile time error informing you that Derived does not override any suitable method. I find the lack of this feature in C# a bit annoying to be honest and although its been on the line a few times for future versions it has never made the cut. To be honest, competing features have been up to date quite a bit more interesting and useful.

IMO you have two reasonable options here. One is to hide Owner :

class Bar: Foo
{
    new Derived Owner { get { return base.Owner as Derived; } // where Derived: Base
}

The other is using explicitly implemented interfaces:

interface IOwnable
{
    Base Owner { get; }
}

class Foo: IOwnable
{
    Base IOwnable.Owner { get; }
}

class Bar: Foo
{
    Derived Owner { get { return ((IOwnable)base).Owner as Derived; } // where Derived: Base
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM