I think I may have found an issue with either the C# 8.0 compiler or the .NET Core run-time regarding default interface member implementations and generic type parameter constraints.
The general gist of it is that I implemented a very simple design which you can use to reproduce the run-time VerificiationException I get when running a piece of code that compiles just fine and actually should be fine.
So let's get to the code. I created a blank solution with two projects: one C# library targeting .NETStandard 2.1 and one C# test project targeting .NET Core 3.1, where the test-project references the library.
Then in the library project I added the following code:
// In library project
namespace SomeLibrary
{
public interface IMessageHandler<TMessage> { }
public interface ISomeInterface<TMessage>
{
void DoSomething<TMessageHandler>() where TMessageHandler : class, IMessageHandler<TMessage> =>
DoSomething<TMessageHandler>("Something");
void DoSomething<TMessageHandler>(string value) where TMessageHandler : class, IMessageHandler<TMessage>;
}
public sealed class SomeClass<TMessage> : ISomeInterface<TMessage>
{
public void DoSomething<TMessageHandler>(string value) where TMessageHandler : class, IMessageHandler<TMessage> { }
}
}
Note how the DoSomething<TMessageHandler>
-methods declare a generic type constraint on TMessageHandler
that also references the interface's generic type parameter TMessage
.
In the test-project, I added a stub-implementation of the IMessageHandler<TMessage>
interface (SomeHandler
) to have some type that satisfies the generic type parameter constraint. Then I implemented the following simple test that invokes the ISomeInterface<object>.DoSomething<SomeHandler>
's overload that has the default implementation (note: I use MS Test):
// In test-project.
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SomeLibrary.Tests
{
[TestClass]
public sealed class SomeClassTest
{
[TestMethod]
public void DoSomething_DoesSomething()
{
CreateSomeClass<object>().DoSomething<SomeHandler>();
}
private static ISomeInterface<TMessage> CreateSomeClass<TMessage>() =>
new SomeClass<TMessage>();
}
public sealed class SomeHandler : IMessageHandler<object> { }
}
As you would expect, this all compiles just fine.
However, when you run this test, the CLR throws a VerificationException
at the point of invoking the DoSomething<...>
-method:
System.Security.VerificationException: Method ISomeInterface`1[System.Object].DoSomething: type argument 'TMessageHandler' violates the constraint of type parameter 'TMessageHandler'.
It's as if the run-time cannot see that class SomeHandler
actually does satisfy this constraint - which is already checked by the compiler.
After experimenting a bit, I noticed the issue goes away if I change the type parameter constraint to something that doesn't depend on/use the interface's type parameter TMessage
. For example, if I simply omit the requirement that TMessageHandler
implements IMessageHandler<TMessage>
, the code runs just fine:
public interface ISomeInterface<TMessage>
{
void DoSomething<TMessageHandler>() where TMessageHandler : class =>
DoSomething<TMessageHandler>("Something");
void DoSomething<TMessageHandler>(string value) where TMessageHandler : class;
}
It's also possible to add other constraints, as long as they don't use TMessage
.
Also note that if I keep the generic type parameter constraint intact but move the method's implementation to SomeClass<TMessage>
- which is what you would do before C# 8.0 - then the code also runs fine, so it's this particular combination of constraint and default interface method implementation that makes the system crack.
Is this a bug in the compiler or CLR, or am I missing a vital step in my thought-process?