dotnet / core

.NET news, announcements, release notes, and more!
https://dot.net
MIT License
20.88k stars 4.89k forks source link

Different behavior between dotnet 2.1 and 3.1 when accessing a field from IL #4219

Closed fluffynuts closed 4 years ago

fluffynuts commented 4 years ago

This problem arose out of seeing an exception whilst using one of my libraries (PeanutButter.DuckTyping) after upgrading a project to dotnet core 3.1. I've simplified the problem code as much as I think is possible to reproduce and put it here: https://github.com/fluffynuts/ildifferences

Preface: I am, by no means, an IL master. I picked up enough to solve a problem I've had and it's been working from 2016, so it's been generally "good enough". I am wholly open to learning better ways to write IL though. PeanutButter.DuckTyping facilitates "duck-typing" (more facading, but it feels like duck-typing) any type onto a conformant, non-related interface so that one can, assuming duck-typing is possible, do:

interface IHasAnId
{
    int Id { get; }
}

// some time later
var data = new { Id = 123 };
var ducked = data.DuckAs<IHasAnId>();
// ducked now implements the IHasAnId interface and facilitates
//  property reads

The symptoms:

In my generated IL code, I reference a field within a generated type, calling a method on that field. Under dotnet core 2.1 and on net framework, this is all good. (PeanutButter.DuckTyping targets netstandard2.0 and net452/net462). The field has been declared with the type of an interface which two possible run-time implementations implement. Under dotnet core 3.1, I find that the same generated IL fails with either "bad format" or "execution error" when the field is declared as the interface type, but the code works fine if declared as one of the concrete types (as demonstrated with the linked project)

Now, I could quite possibly be making use of non-standard functionality for which there is no guarantee of future behavior (and I can think of a workaround, but it comes at a cost to my consuming code). The other difficulty is that, targeting netstandard2.0, I can't tell that the consumer is netcoreapp3.1 (or the working netcoreapp2.1), so I can't easily #if the less performant (workaround) code.

In addition, I'm not entirely sure that the generated IL should be breaking on netcoreapp3.1 -- though one of the reasons for raising this issue is to determine if that is indeed the case (ie should I expect it to break, and should I be working around it).

In the linked project, there is a netcoreapp2.1 target and a netcoreapp3.1 target. The compiled former runs fine where the latter does not, both compiled with the same (3.1) dotnet command, but obviously targeting different runtimes.

Assistance appreciated.

leppie commented 4 years ago

Fro ma quick look, you need to use CallVirt on an interface call.

fluffynuts commented 4 years ago

@leppie thank you so much -- this has blocked me much of today, and it turns out I've been Doing It Wrong all this time, and the underlying CLR has just been tolerating my bs (: