question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unable to cast an IPen implementation to Pen in Render method

See original GitHub issue

Describe the bug If you create a class that implements IPen (e.g., ‘GenericPen’) and use it inside Render(DrawingContext) method, an exception will throw with the following message: 'Unable to cast object of type 'GenericPen' to type 'Avalonia.Media.Pen'

To Reproduce I created a simple Avalonia MVVM project (one of the default Avalonia templates) in VS2022 and rewrote the MainWindow.axaml.cs file as follows:

using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;

namespace AvaloniaRenderTest.Views
{
    public partial class MainWindow : Window
    {
        GenericPen _pen;

        public MainWindow()
        {
            _pen = new GenericPen(Brushes.Black);
            InitializeComponent();
        }

        public override void Render(DrawingContext context)
        {
            base.Render(context);
            context.DrawLine(_pen, new Point(1, 1), new Point(4, 4)); // System.InvalidCastException: 'Unable to cast object of type 'GenericPen' to type 'Avalonia.Media.Pen'.'
        }

        class GenericPen : IPen
        {
            public IBrush Brush { get; }

            public GenericPen(IBrush brush)
            {
                Brush = brush;
            }

            public IDashStyle? DashStyle { get; set; }
            public PenLineCap LineCap { get; set; }
            public PenLineJoin LineJoin { get; set; }
            public double MiterLimit { get; set; } = 10;
            public double Thickness { get; set; } = 1;
        }
    }
}

By running this program, an InvalidCastException exception will be thrown inside the overriden Render method, line context.DrawLine (as commented above).

Expected behavior No InvalidCastException when passing an IPen argument to DrawLine.

Suggested Fix By looking into the Avalonia’s source code, I found that this exception is actually thrown in the extension method ToImmutable(IPen) found in src/Avalonia.Base/Media/BrushExtensions.cs file. The offending code is found as shown below:

public static ImmutablePen ToImmutable(this IPen pen)
{
    _ = pen ?? throw new ArgumentNullException(nameof(pen));

    return pen as ImmutablePen ?? ((Pen)pen).ToImmutable();
}

The pen as ImmutablePen and ((Pen)pen) casts expect that any IPen would automatically be either a Pen or an ImmutablePen, which prevents any further implementations to be used. I understand that the ImmutablePen is needed in this context, so my suggestion is to do choose one of the alternatives below:

  • Introduce a ToImmutable() method to IPen. This could be implemented as a default interface method (available from C# 8) to reduce the impact of such breaking changes;
  • Create a convertible interface that implements ToImmutable(), such as IConvertibleToImmutable<IPen>, or perhaps something more straightforward such as IMutablePen with a ToImmutable() method (similar to ImutableBrush interface);
  • Modify the ToImmutable(this IPen pen) extension method in BrushExtensions.cs to create an actual ImmutablePen object using the IPen properties.

In my opinion, the 3rd option would be the most reasonable, so I went ahead and wrote a possible fix for this issue with the code below (requires C# 8 or newer):

public static ImmutablePen ToImmutable(this IPen pen)
{
    return pen switch
    {
        null => throw new ArgumentNullException(nameof(pen)),
        ImmutablePen penImpl => penImpl,
        Pen penImpl => penImpl.ToImmutable(),
        _ => new ImmutablePen(pen.Brush?.ToImmutable(), pen.Thickness, pen.DashStyle?.ToImmutable(), pen.LineCap, pen.LineJoin, pen.MiterLimit)
    };
}

A similar implementation can be done with C# 7.3 using a chain of if’s and is operators.

Important: I should note that the same issue is applicable to the ToImmutable(IDashStyle) extension method, also located in BrushExtensions.cs file. It accepts an interface but actually restrict the type to only two implementations. Therefore, I suggest the equivalent fix for ToImmutable(IDashStyle style). This would be done by replacing this code:

public static ImmutableDashStyle ToImmutable(this IDashStyle style)
{
    _ = style ?? throw new ArgumentNullException(nameof(style));

    return style as ImmutableDashStyle ?? ((DashStyle)style).ToImmutable();
}

By this one:

public static ImmutableDashStyle ToImmutable(this IDashStyle style)
{
    return style switch
    {
        null => throw new ArgumentNullException(nameof(style)),
        ImmutableDashStyle styleImpl => styleImpl,
        DashStyle styleImpl => styleImpl.ToImmutable(),
        _ => new ImmutableDashStyle(style.Dashes, style.Offset)
    };
}

This way, the exception would be vanished and any implementation would be accepted.

There was also the remaining method in the BrushExtensions.cs file, ToImmutable(IMutableBrush), however this one has no issues since its interface already has a ToImmutable() declaration to be implemented. I think that the problem is only with the overloads for IPen and IDashStyle.

In any case, thank you for providing us such an amazing framework. I’ll provide more feedback if I find any other issue.

Desktop:

  • OS: Windows 10
  • Visual Studio 2022
  • NET 6.0
  • C# 10 with preview features enabled

Issue Analytics

  • State:closed
  • Created a year ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
kekekekscommented, Apr 20, 2022

Moreover, is there a reason for such decision

The rendering system internals are considered to be an internal implementation detail, so we could change them to improve the rendering subsystem without breaking compatibility with existing code. And there is a lot of changes to the rendering that are currently planned. That limits the level of extensibility we can allow in that area.

BTW, everything with Impl suffix is considered to be an unstable API, including IDrawingContextImpl. The custom drawing operation API was added mostly for exposing GPU-accelerated Skia context, exposing the rest of the IDrawingContextImpl was just a bad decision and needs to be replaced with a better asynchronous immediate mode API that won’t throw exceptions because it technically possible to pass a normal brush or pen to its methods. That’s something we are planning for 11.0 if possible.

0reactions
grokyscommented, Apr 26, 2022

Opened a PR which might be the first step in clarifying this https://github.com/AvaloniaUI/Avalonia/pull/8049

Read more comments on GitHub >

github_iconTop Results From Across the Web

java - Failed to open implementation for method added ...
I'm using Eclipse Neon. Basically I'm creating a new interface and adding to it an existing method found in different base class. I...
Read more >
Can't find pen in microsoft blend 2022
Your open channel to Microsoft engineering teams. Select a page ... Can't find pen in microsoft blend 2022Closed - Lower Priority...
Read more >
WhiteBoard - Support - ViewSonic ME
[Bug 16804]: Fixed -"Cast - Cannot show the cast function window content successfully when open the window at the first time." [Bug]: Fixed...
Read more >
How To Build A Weather App In JavaScript Without ... - YouTube
... To The API 28:10 - Transforming API Data 39:10 - Rendering The Data 52:52 - Getting The User's Location #WeatherApp #WDS #JavaScript....
Read more >
How To Add A Drop Shadow In Illustrator - YouTube
Adding drop shadows to your designs is a great way to give them some depth and make them appear as if they're jumping...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found