Runtime: Type forwarding makes compiling agains runtime assemblies harder

Created on 11 Sep 2016  路  3Comments  路  Source: dotnet/runtime

When compiling code using Roslyn as a library, it's fairly common to compile against framework runtime assemblies, since doing so is much easier than figuring out where reference assemblies are. For example:

``` c#
var compilation = CSharpCompilation.Create(assemblyName)
.AddReferences(MetadataReference.CreateFromFile(typeof(object).Assembly.Location));

If you need additional assemblies, you find some type from each assembly and use that. Specifically, using `dynamic` requires `Microsoft.CSharp` and `System.Dynamic.Runtime`, so you would add:

``` c#
.AddReferences(MetadataReference.CreateFromFile(typeof(Binder).Assembly.Location))
.AddReferences(MetadataReference.CreateFromFile(typeof(DynamicAttribute).Assembly.Location))

This works fine on .Net Framework, but not on .Net Core.

The first issue is that on .Net Core, some types are in separate assemblies. Specifically, using dynamic also requires System.Linq.Expressions:

``` c#
.AddReferences(MetadataReference.CreateFromFile(typeof(Expression).GetTypeInfo().Assembly.Location))

This is understandable. What is less understandable is that the assembly that contains `object` and other base types, `System.Private.CorLib` is not enough, you also need to explicitly reference two assemblies that mostly just forward types: `System.Runtime` and `mscorlib`:

``` c#
.AddReferences(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location))
.AddReferences(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("mscorlib")).Location))

This is necessary, because Microsoft.CSharp references System.Runtime, which forwards to mscorlib, which forwards to System.Private.CorLib.

Would it be possible for the Microsoft.CSharp runtime assembly (and similar assemblies) to directly reference the assembly that actually contains the types it uses, without any forwarding? It would simplify this kind of code.


For reference, here is a complete project showing what I'm talking about.

See also https://github.com/dotnet/roslyn/issues/12045, which inspired this request.

area-Meta enhancement untriaged

Most helpful comment

It's possible to simply collect all assemblies which netstandard assembly references:
```C#
private static List CollectReferences()
{
// first, collect all assemblies
var assemblies = new HashSet();

Collect(Assembly.Load(new AssemblyName("netstandard")));

//// add extra assemblies which are not part of netstandard.dll, for example:
//Collect(typeof(Uri).Assembly);

// second, build metadata references for these assemblies
var result = new List<MetadataReference>(assemblies.Count);
foreach (var assembly in assemblies)
{
    result.Add(MetadataReference.CreateFromFile(assembly.Location));
}

return result;

// helper local function - add assembly and its referenced assemblies
void Collect(Assembly assembly)
{
    if (!assemblies.Add(assembly))
    {
        // already added
        return;
    }

    var referencedAssemblyNames = assembly.GetReferencedAssemblies();

    foreach (var assemblyName in referencedAssemblyNames)
    {
        var loadedAssembly = Assembly.Load(assemblyName);
        assemblies.Add(loadedAssembly);
    }
}

}
```

Not sure if this is applicable for every case, but for me it resolved all the issues after migration to .NET Core 2.0 from .NET Framework 4.7 for Roslyn compiler.

All 3 comments

It's possible to simply collect all assemblies which netstandard assembly references:
```C#
private static List CollectReferences()
{
// first, collect all assemblies
var assemblies = new HashSet();

Collect(Assembly.Load(new AssemblyName("netstandard")));

//// add extra assemblies which are not part of netstandard.dll, for example:
//Collect(typeof(Uri).Assembly);

// second, build metadata references for these assemblies
var result = new List<MetadataReference>(assemblies.Count);
foreach (var assembly in assemblies)
{
    result.Add(MetadataReference.CreateFromFile(assembly.Location));
}

return result;

// helper local function - add assembly and its referenced assemblies
void Collect(Assembly assembly)
{
    if (!assemblies.Add(assembly))
    {
        // already added
        return;
    }

    var referencedAssemblyNames = assembly.GetReferencedAssemblies();

    foreach (var assemblyName in referencedAssemblyNames)
    {
        var loadedAssembly = Assembly.Load(assemblyName);
        assemblies.Add(loadedAssembly);
    }
}

}
```

Not sure if this is applicable for every case, but for me it resolved all the issues after migration to .NET Core 2.0 from .NET Framework 4.7 for Roslyn compiler.

Sorry it took a long time to respond, but unfortunately we have to keep the type forwards like they are for compatibility reasons with .NEt Standard, other versions of .NET Core, and .NET Framework. Also, it is not safe to depend on where types live in the runtime, as we can't guarantee that a type won't move into a different assembly across .NET Core versions, unless this is already at the bottom of the stack (meaning it is already in System.Private.CoreLib). One alternative you could do, is to try to reference everything inside the shared framework, so add some code that finds where System.Private.CoreLib.dll lives, and then just reference all dlls on that directory.

Closing this for now as we can't really fix this, but feel free to reopen if you have any follow up comments.

Was this page helpful?
0 / 5 - 0 ratings