Getting the System.Type from an assembly loaded at

2019-07-25 06:12发布

问题:

As a followup to this question I have now come to the problem of being able to get the Type of a type that is defined by the user in his own solution. Using the standard mscorlib types, everything works.

The question is very easy: how can I get this type from an assembly that I will only know at runtime?

As described in the comments here:

Also, what do you mean by "extracting the type"? You mean getting the Reflection type? There's no good helper, partly because (typically) you can never assume the type you're compiling against is creatable at runtime. There's a strong (and often overlooked) distinction between "compile time" and "run time", and it's rare to bridge them.

Or here on the previous question:

Well, so getting a Type for TypeInfo, naming issues aside, is a tricky problem. It assumes you have an assembly that can be loaded and found. When you do a build, the compiler might be loading reference assemblies that themselves can't be loaded as "normal" assemblies. Even if they are, you might have to hook AppDomain.AssemblyResolve to locate your references, and whatever assembly you built.

"Build" and "runtime" are really different domains, and crossing from one to the other is poorly defined, at best. I assume here that you really need a System.Type because you're using some other reflection API, or trying to then load that type and execute code from it.

I have followed the approach as laid out here and implemented it as such in my Analyzer:

private static Dictionary<string, Assembly> _assemblies = new Dictionary<string, Assembly>();

var containingAssembly = semanticModel.GetSymbolInfo(argument)
                                      .Symbol
                                      .ContainingAssembly;

if (!_assemblies.TryGetValue(containingAssembly.ToString(), out Assembly result))
{
    var newAssembly = Assembly.Load(containingAssembly.ToString());
    _assemblies.Add(containingAssembly.ToString(), newAssembly);
}

var currentDomain = AppDomain.CurrentDomain;
currentDomain.AssemblyResolve += ResolveAssemblies;

private Assembly ResolveAssemblies(object sender, ResolveEventArgs args)
{
    _assemblies.TryGetValue(args.Name, out Assembly result);
    return result;
}

But this hasn't made a difference, I keep getting

The User Diagnostic Analyzer 'DiagnosticTools.Collections.ElementaryMethodsNotOverriden.ElementaryMethodsNotOverridenAnalyzer' threw an exception with message 'Could not load file or assembly 'RoslynTester, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.'.

Using fuslogvw.exe gives me this log information which boils down to

LOG: All probing URLs attempted and failed.

After searching for the .dll and .exe version in a few subfolders of /Common7/IDE/.

As context to clarify why I'm doing this: I want to check every type that is used in a collection and verify that it overrides both Equals and GetHashCode. To determine this I have a "classic" reflection extension method that checks this for me:

public static bool IsOverridden(this MethodInfo method)
{
    return method.GetBaseDefinition().DeclaringType != method.DeclaringType;
}

So should Roslyn have a way to verify this itself that would make it so that I don't have to use classic reflection at all, then this would also be fine.

Update:

When I use this code as provided by MSDN, I get an "invalid parameter" exception inside Visual Studio but fuslogvw still shows a "file not found" error message. What causes this discrepancy?

private Assembly ResolveAssemblies(object sender, ResolveEventArgs args)
{
    Assembly MyAssembly, objExecutingAssemblies;
    string strTempAssmbPath = "";

    objExecutingAssemblies = Assembly.GetExecutingAssembly();
    AssemblyName[] arrReferencedAssmbNames = objExecutingAssemblies.GetReferencedAssemblies();

    foreach (AssemblyName strAssmbName in arrReferencedAssmbNames)
    {
        if (strAssmbName.FullName.Substring(0, strAssmbName.FullName.IndexOf(",")) == args.Name.Substring(0, args.Name.IndexOf(",")))
        {               
            strTempAssmbPath = @"C:\Users\Jeroen\Documents\Visual Studio 2013\Projects\RoslynTester\RoslynTester\bin\Debug\" + args.Name.Substring(0, args.Name.IndexOf(",")) + ".exe";
            break;
        }
    }                   
    MyAssembly = Assembly.LoadFrom(strTempAssmbPath);

    return MyAssembly;
}

回答1:

I'm assuming you've already found out (programmatically) which class are contained in your collection. You don't really need reflection to accomplish what you want. With Roslyn you can check if a class overrides the Equals method with this SyntaxWalker:

public class FindOverrides : CSharpSyntaxWalker
{
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        base.VisitMethodDeclaration(node);

        if (node.Identifier.Text == "Equals" 
            && node.Modifiers.Any(m => m.Text == "override"))
        {
            // found an override of Equals()    
        }
    }
}

To (blindly) check every method of every type in a given solution it can be used like this:

var syntaxRoots =
    from project in solution.Projects
    from document in project.Documents
    select document.GetSyntaxRootAsync().Result;

foreach (var root in syntaxRoots)
    new FindOverrides().Visit(root);

There is at least one omission (if that's a valid case for you): my code above won't find if a base class of a given type is overriding Equals().