jbevain / cecil

Cecil is a library to inspect, modify and create .NET programs and libraries.
MIT License
2.76k stars 627 forks source link

Discussion on type forwarding and its impact on assembly references (.NET Standard 2.0) #439

Closed lextm closed 7 years ago

lextm commented 7 years ago

A simple .NET Standard 2.0 class library from Visual Studio 2017 would contain a single assembly reference to netstandard.dll. This assembly forwards all necessary types to individual assemblies behind the scene (System.Runtime for example).

After commits such as this Cecil can correctly resolves the type references to definitions in correct assemblies, but one side effect can be seen.

For example, I load the simple class library and attempt to inject some MSIL, and very likely a method and its type need to be imported,

                var encoding = new TypeReference("System.Text", "Encoding", library.MainModule,
                    library.MainModule.TypeSystem.CoreLibrary).Resolve();
                var method1 =
                    library.MainModule.ImportReference(encoding.Methods.FirstOrDefault(method => method.Name == "get_UTF8"));
                var method2 = library.MainModule.ImportReference(encoding.Methods.FirstOrDefault(method =>
                    method.FullName ==
                    "System.String System.Text.Encoding::GetString(System.Byte[],System.Int32,System.Int32)"));

Then System.Runtime would be added by Cecil as a new assembly reference.

I wonder if there is a way to avoid the new reference, because netstandard.dll already has the type forwarder.

jbevain commented 7 years ago

It can certainly be avoided. Please create a small repro project and I'll have a look at it.

Thanks!

lextm commented 7 years ago

CecilTest.zip

Attached is a simple project. NetStandard20.dll is compiled from the default .NET Standard 2.0 class library template. CecilTest loads it into memory, and adds an assembly attribute SuppressIldasmAttribute.

You can see the assembly reference count increases from 1 to 2.

jbevain commented 7 years ago

Thanks, I'll look into it!

jbevain commented 7 years ago

Ok looking at the sample, the current behavior makes sense: you ask Cecil to create a MethodReference from the System.Runtime assembly, you get a reference to System.Runtime.

You'd need to build something on top of the existing DefaultMetadataImporter that understands netstandard. There's nothing builtin that allows you to navigate a type forwarder backwards. So from a type in System.Runtime, we don't know if there's a type forwarder for it in netstandard. And we don't know which System.Runtime goes with which netstandard.dll. So I'm reluctant to make it a default behavior in Cecil.

But right now, nothing prevents you to customize the Importer to do what you need.

lextm commented 7 years ago

Thanks for pointing out the usage of importer. I will try to build a custom one to see if it can remove the extra assembly reference.