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.

Generic Blazor inheritance doesn't compile with the C# 9 Nullable setting enabled

See original GitHub issue

Describe the bug

Generic Razor components that themselves inherit from a generic base class can’t have any constraints (except struct) if the new and great .Net5 / C# 9 Nullable setting is enabled. The compiler errors you get are very puzzling but clearly incorrect:

Error CS0263 Partial declarations of ‘TestComponent<T>’ must not specify different base classes Error CS0115 ‘TestComponent<T>.BuildRenderTree(RenderTreeBuilder)’: no suitable method found to override

  • Removing or setting the #nullable option to disable makes the project compile
  • Removing all generic constraints in the codebehind makes the project compile
  • Changing the generic constraints in the codebehind to struct makes the project compile

Note that this only happens if the component itself has a @typeparam and inherits from a generic base class, while the .Net 5 / C# 9 Nullable feature is activated.

To Reproduce

Here’s a very minimal repro repo: https://github.com/kaleidocore/BlazorGenericsBug

Further technical details

.NET SDK (reflecting any global.json): Version: 5.0.103 Commit: 72dec52dbd

Runtime Environment: OS Name: Windows OS Version: 10.0.18363 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\5.0.103\

Host (useful for support): Version: 5.0.3 Commit: c636bbdc8a

.NET SDKs installed: 2.0.2 [C:\Program Files\dotnet\sdk] 2.1.202 [C:\Program Files\dotnet\sdk] 2.1.402 [C:\Program Files\dotnet\sdk] 2.1.520 [C:\Program Files\dotnet\sdk] 2.1.812 [C:\Program Files\dotnet\sdk] 3.1.405 [C:\Program Files\dotnet\sdk] 5.0.102 [C:\Program Files\dotnet\sdk] 5.0.103 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.All 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.All 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 2.1.25 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.12 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 5.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Visual Studio 2019, Version 16.8.5

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
captainsafiacommented, Sep 2, 2021

@MithrilMan Not quite. We debugged this down to an issue in the compiler so it will depend on what version of the compiler you are using. See https://github.com/dotnet/roslyn/issues/45960 for more info.

1reaction
captainsafiacommented, Aug 4, 2021

Spent some time looking into this and I’m 100% sure its a compiler bug related to https://github.com/dotnet/roslyn/issues/45960.

In particular, the issue seems to be related to have multiple partial declarations of a generic class within an enabled nullability context.

I tried changing the code generation for the Razor component in the following ways to see if the issue was related to anything we had control over:

First, adding a generic constraint to the generated code.

-    public partial class TestComponent<T> : TestComponentBase<T>
+    public partial class TestComponent<T> : TestComponentBase<T> where T : class 

No luck here. Also tried disabling nullability around the class declaration here.

-    public partial class TestComponent<T> : TestComponentBase<T>
+#nullable disable
+    public partial class TestComponent<T> : TestComponentBase<T>

But as mentioned above, this doesn’t really do anything as long as nullability is enabled in the partial implementation in the C# file that the Razor compiler doesn’t touch.

With this in mind, I stepped back and decided to play with generic base classes + partial implementations + nullability in a simple console app.

The following code sample compiles fine and captures some of the properties that I think exist in this problem:

  • An abstract class definition
  • An generic class implementation of the abstract class
  • Two partial classes that extend the generic class
public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

public class TestComponentBase<T> : Foo
        where T : class
{
        public int BaseVal { get; set; }

        public override void Bar() {
            return;
        }
}

public abstract class Foo
{
    public abstract void Bar();
}

The issue doesn’t repro in this scenario. At this point, I chased another hunch. Maybe the issue only repros with abstract classes that implement an interface?

I tried the following but no dice:


public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

public class TestComponentBase<T> : Foo
        where T : class
{
        public int BaseVal { get; set; }

        public override void Bar() {
            return;
        }
}

public abstract class Foo : IFoo
{
    public int Baz { get; set; }
    public abstract void Bar();
}

public interface IFoo
{
    int Baz { get; set; }
}

At this point, I came across https://github.com/dotnet/roslyn/issues/45960 which reminded me to put tickering with the nullability context at the forefront of my investigation. So I tried the following:

#nullable disable
public partial class TestComponent<T> : TestComponentBase<T>
{
    public TestComponent()
    {
        TestVal = 0;
    }
}

#nullable enable

public partial class TestComponent<T> : TestComponentBase<T>
        where T : class  
{
        public int TestVal { get; set; }
}

And reproed the bug. OK. So what we’re dealing with is exactly related to #45960.

Then I realized that I missed something when I was trying:

No luck here. Also tried disabling nullability around the class declaration here.

-    public partial class TestComponent<T> : TestComponentBase<T>
+#nullable disable
+    public partial class TestComponent<T> : TestComponentBase<T>

And realized that I missed something important here. Since the issue is related to nullability mismatch, we want to enable nullability around the class declaration, not disable it, to match the nullability context in the other class declaration. Updating the code generation to produce the following:

#nullable enable
public partial class TestComponent<T> : TestComponentBase<T>

resolves the issue.

This is just a workaround for the compiler bug referenced above but I think we can safely enable nullability around the class declaration for the meantime. I’ll have to see if it is possible for us to strictly limit this to partial generic classes.

Side note, I also spotted https://github.com/dotnet/aspnetcore/issues/27218 and https://github.com/dotnet/aspnetcore/issues/26971 as possible dupes of this issue in the repo.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Using Generic Types with Inheritance in Blazor Component
A workaround is to add the TypeParam explicitly in the component usage; so the same code as in the example in the original...
Read more >
ASP.NET Core Blazor dependency injection
This article explains how Blazor apps can inject services into components. Dependency injection (DI) is a technique for accessing services ...
Read more >
Working with null values | C# 9 and .NET 5
C# has the concept of a null value, which can be used to indicate that a variable has not been set. Making a...
Read more >
Generic Constraints in C# with Examples
It will give you a compile-time error if you try to substitute a generic type using a type that is not allowed by...
Read more >
Building Web Applications in .NET 6 and Beyond
For example, in Objective-C, when a pointer is null, the compiler would not invoke a method on it. And it would do this...
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