Consider the following code:
using System;
static class Program
{
public static void Main()
{
Test test = new Test();
test.Method(()=>{});
}
}
public sealed class Test
{
public void Method(Delegate _)
{
Console.WriteLine("Test.Method(Delegate)");
}
}
public static class TestExt
{
public static void Method(this Test self, Action _)
{
Console.WriteLine("TestExt.Method(Action)");
}
}
In C# 9.0 targeting net48, this outputs TestExt.Method(Action)
In C# 10.0 targeting net6.0, this outputs Test.Method(Delegate)
Obviously this is a breaking change because the extension method may execute different code.
My question is: Is there a way to detect this sort of breaking change in the code without inspecting all the code? For example, is there a code analysis rule that we can enable to detect this sort of code?
This change has actually caused a genuine bug in our code base.
Note that this difference in behaviour is only occurring because of changes to the extension method resolution. If instead of an extension method, the method is in the Test class itself then both language versions call Test.Method(Action):
static class Program
{
public static void Main()
{
Test test = new Test();
test.Method(()=>{});
}
}
public sealed class Test
{
public void Method(Delegate _)
{
Console.WriteLine("Test.Method(Delegate)"); // Not called by either version.
}
public void Method(Action _)
{
Console.WriteLine("TestExt.Method(Action)"); // Called by C#9 and C#10.
}
}
Background information:
This breaking change has bitten us because we have (rightly or wrongly) an extension method for Control.BeginInvoke() along these lines (error handling omitted):
public static void BeginInvoke(this Control control, Action action)
{
if (control.IsHandleCreated)
tryBeginInvoke(control, action);
}
This is no longer called for code like:
this.BeginInvoke(() => someMenu.Visible = somethingIsAvailable());
Instead, Control.BeginInvoke(Delegate) is called, which omits the check for control.IsHandleCreated and the tryBeginInvoke() error handling method.