/
TypeResolver.cs
156 lines (131 loc) · 5.68 KB
/
TypeResolver.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
namespace SmartAnnotations.Internal
{
// Not yet sure what the final design will be.
[ExcludeFromCodeCoverage]
internal class TypeResolver : MarshalByRefObject
{
private readonly CSharpCompilation compilation;
private readonly List<MetadataReference> metadataReferences;
internal TypeResolver(CSharpCompilation compilation)
{
this.compilation = compilation;
this.metadataReferences = compilation.References.ToList();
}
internal IEnumerable<AnnotationContext> GetAnnotationContextInstances()
{
var annotatorTypes = GetAllTypes().Where(x => IsSubclassOfRawGeneric(x, typeof(Annotator<>)));
return annotatorTypes.Select(x => (AnnotationContext)Activator.CreateInstance(x));
}
internal void UnloadAssemblies()
{
// There is no way to unload assemblies.
// We have to create a separate domain, but that will break on .NET Core, it supports single application domain only.
// Also CreateInstanceAndUnwrap is not available on .NET Standard 2.0
// This sucks :)
// AppDomain.Unload(this.appDomain);
}
private Type[] GetAllTypes()
{
Type[] output = Array.Empty<Type>();
//var appDomain = AppDomain.CurrentDomain;
//appDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
var assembly = GetAssembly();
if (assembly != null)
{
LoadReferencedAssemblies();
output = GetLoadableTypes(assembly);
}
return output;
}
private Type[] GetLoadableTypes(Assembly assembly)
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null).ToArray();
}
}
private Assembly? GetAssembly()
{
Assembly? assembly = null;
using (var memoryStream = new MemoryStream())
{
EmitResult result = this.compilation.Emit(memoryStream);
if (result.Success)
{
memoryStream.Seek(0, SeekOrigin.Begin);
assembly = Assembly.Load(memoryStream.ToArray());
}
}
return assembly;
}
// Switched to eager loading the missing compilation references.
// Handling it though AssemblyResolve event is not a smart idea.
// If there are several open solutions in VS, they all operate in the same app domain, and the handler will be serving all AssemblyResolve events.
private void LoadReferencedAssemblies()
{
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().Select(x=>x.GetName().Name.ToUpper()).ToList();
foreach (var reference in this.metadataReferences)
{
if (reference.Display != null && !loadedAssemblies.Any(x => reference.Display.ToUpper().Contains($"{x}.DLL")))
{
try
{
// Some of the assemblies can be loaded for reflection only.
// Also, while running through VS, if the project is .NET5 it will fail to load the runtime.
Assembly.LoadFrom(reference.Display);
}
catch (Exception)
{
}
}
}
}
// This is a hack, but it's the easiest way to handle this and load referenced assemblies.
// Any other approach just gets insane (much stuff not available on .NET Standard 2.0).
// Compilation.References contain all the referenced assemblies, but only the path to them, not the assembly name. So we're trying to match from args.Name.
// Also, doing it this way, we don't have to care for the assemblies' TFMs, since the compilation already has handled that part and contains the correct path.
// EDIT: Shouldn't be used. It will break VisualStudio since everything runs in CurrentDomain, and this will handle all AssemblyResolve events!
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.FullName.Equals(args.Name));
if (loadedAssembly != null) return loadedAssembly;
var name = args.Name?.Split(',').FirstOrDefault()?.ToUpper();
if (name != null)
{
var path = this.metadataReferences.FirstOrDefault(x => x.Display != null && x.Display.ToUpper().Contains($"{name}.DLL"))?.Display;
if (path != null)
{
var assembly = Assembly.LoadFrom(path);
return assembly;
}
}
return Assembly.Load(args.Name);
}
private bool IsSubclassOfRawGeneric(Type toCheck, Type baseType)
{
while (toCheck != typeof(object))
{
Type cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
if (baseType == cur)
{
return true;
}
toCheck = toCheck.BaseType;
}
return false;
}
}
}