Setup
I am working on a C# project without external dependencies, named "Hopper". I have split it up into a couple of different modules, each contained within a corresponding sub-folder. The relevant ones for the problem are the following:
Utils/Hopper.Utils.csproj
Shared/Hopper.Shared.csproj
Core/Hopper.Core.csproj
, which referencesUtils
andShared
, more details laterMine/Hopper.Mine.csproj
, which referencesCore
All of these target .NET 4.8, including Hopper.Core
.
I have set the AssemblyName
property for each of the projects to the corresponding string. For example, the Hopper.Utils.csproj
:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net4.8</TargetFramework>
<AssemblyName>Hopper.Utils</AssemblyName>
</PropertyGroup>
</Project>
Hopper.Core.csproj
references the other projects via ProjectReference
, like so:
<ItemGroup>
<ProjectReference Include="..\Utils\Hopper.Utils.csproj" />
<ProjectReference Include="..\Shared\Hopper.Shared.csproj" />
</ItemGroup>
Hopper.Mine.csproj
, created for testing purposes, references core like this:
<ItemGroup>
<ProjectReference Include="..\Core\Hopper.Core.csproj" />
</ItemGroup>
The Hopper.Core
project contains some types, both classes and structs. Some source files have been autogenerated by a tool and contain #pragma warning disable
at the top. (Removing it did not help).
The two projects referenced by Hopper.Core
just contain some types and attributes, used by Core
, so nothing of interest to the problem. They do not reference any other projects or dll's.
The problem
I am able to compile Hopper.Mine
by running dotnet build
in the sub-folder with the project file. I am able to see the output dll's of the referenced sub-projects in bin/Debug/net4.8/Hopper.Whatever.dll
under each project's folder. Intellisense in VSCode also works correctly and does not report any errors.
Hopper.Mine
has a single class with a Main function, created for a test. It lists the types of Hopper.Core
that have loaded successfully, and reports the ones that have not been loaded:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly lib = typeof(Hopper.Core.Action).Assembly;
Type[] types;
try
{
types = lib.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
Running this code, I can see a bunch of problematic types, some of which repeat, and the rest of types that have loaded successfully:
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Dig' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Move' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Push' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
Could not load type 'Hopper.Core.Stat.Attack' from assembly 'Hopper.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
... more errors here ...
Hopper.Core.DirectedPredict
Hopper.Core.UndirectedPredict
Hopper.Core.DirectedDo
... more types here ...
Hopper.Core.Stat.Attack+Source+Resistance
Hopper.Core.Stat.Push+Source+Resistance
To be noted 4 things:
- The types that failed to load are all structs and derive from
IStat
. - The source files with these types have been autogenerated by a tool (other files generated by the tool load without errors).
- The types do not reference any types from
Hopper.Shared
orHopper.Utils
. - Some of the types have nested structs, up to 3 level deep, some of the deeper ones seem to have loaded successfully.
I am pretty sure that the dll output from the Hopper.Core
's output folder is identical to the one in the output folder of Hopper.Mine
. I am also pretty sure that the right dll is linked against when I start the program (I've tried debugging with VS, where I can see which dll is being linked against and the paths matched).
I have tried removing AssemblyName
properties, renaming csproj files from Hopper.Whatever.csproj
to Hopper_Whatever.csproj
, I've obviously tried cleaning up any previous output dll's and building from scratch, none of which helped. I have also been able to reproduce this on a different machine with the exact same results.
I have tried looking at the Hopper.Core
dll with a decompiler, ILSpy. I am able to see the "missing" types as they are in code.
I have searched on google for hours for a problem like mine, to no avail. I have found many cases of people setting up their project files incorrectly, due to which they unexpectedly link against a different version of the dll than they expected. For some, such dependency showed up in requirements of one of the referenced third party projects. Some blame GAC for providing incorrect version of the dll. However, I'm pretty sure that my projects are set up right (+ the set up is pretty simple and no third party projects are being referenced) and I am referencing the right dll's (since I can see my types in it). It kind of seems like the types are 'declared' in the dll, but not 'defined' (sort of), so the dll's turn out corrupted. But then why would the decompilation not fail? So I'm currently completely stuck on this.
Loading the assembly manually
I have tried linking against the dll's dynamically, by their names. Listing the types imported from Hopper.Shared
and Hopper.Utils
works, however, when I try it with Hopper.Core
, I get the same output as the example above.
To achieve this, I have copied the previous dll's from the output over to a new folder, removed the reference from Hopper.Mine
, compiled the new code with dotnet build
, after which moved the executable to the new folder and finally ran it in the console. The code:
using System;
using System.Reflection;
using System.Text;
namespace Hopper.Mine
{
public class Program
{
public static void Main(string[] args)
{
Assembly shared = Assembly.LoadFrom("Hopper.Shared.dll");
Assembly utils = Assembly.LoadFrom("Hopper.Utils.dll");
Assembly core = Assembly.LoadFrom("Hopper.Core.dll");
Type[] types;
try
{
types = core.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
Console.WriteLine(exSub.Message);
}
types = ex.Types;
}
foreach (Type type in types)
{
if (type != null)
Console.WriteLine(type.FullName);
}
}
}
}
To reproduce
If it helps, you may try running my code. Here's the github link to the current state of the project. In order to run it, you need:
- .NET 4.8 installed.
- Clone the repository from the link above.
- Some code is still missing. You need to run the project in the folder
Meta
to generate it. Just rundotnet run
in theMeta
folder. - Now you can run the
Mine
subproject.