Unable to cast an IPen implementation to Pen in Render method
See original GitHub issueDescribe 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 aToImmutable()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:
- Created a year ago
- Comments:5 (3 by maintainers)

Top Related StackOverflow Question
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
Implsuffix 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.Opened a PR which might be the first step in clarifying this https://github.com/AvaloniaUI/Avalonia/pull/8049