1

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;
}
Community
  • 1
  • 1
Jeroen Vannevel
  • 43,651
  • 22
  • 107
  • 170
  • It is a "file not found" error, the most common error there is. It is very unclear why you think the CLR should be able to locate the file. If it is not in the GAC and not in the probing path then that's the end of it, Fuslogvw showed you where it looked. Your AssemblyResolve event doesn't accomplish anything, it can only find assemblies that have been loaded before and the CLR already knows how to do that by itself. – Hans Passant Apr 27 '14 at 15:37
  • I have looked into these concepts but I have a few questions left. I don't think the GAC is a solution considering that would force the user to add his assemblies to it manually. Furthermore: the `RoslynTester` project is simply a Console Application and the diagnostic information (which relies on getting the type from that assembly) should be available before building the project. How can I point `ResolveAssemblies` to the new assembly when it is unknown to me at compiletime in what directory to find it in? I have updated the `ResolveAssemblies` code. – Jeroen Vannevel Apr 27 '14 at 16:52

1 Answers1

2

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().

andyp
  • 6,229
  • 3
  • 38
  • 55
  • This approach looks very promising, thanks for pointing me towards it! After [implementing your code](https://gist.github.com/Vannevelj/11351301) I get `One or more errors occurred` exceptions and when I turn on the `AggregateException` I see that the stacktrace leads deep into `System.Threading`. Have I overlooked something in my implementation that could cause this? – Jeroen Vannevel Apr 27 '14 at 17:45
  • Hi, glad I could help. I couldn't immediately see a problem by reading your gist. Please have a look at the `InnerException(s)` of the `AggregateException` you encounter. If that doesn't help you to fix it, please post the exceptions together with a stack trace and the (minimal) piece of code that causes them. – andyp Apr 27 '14 at 18:26
  • I managed to resolve it (although I forgot what it was exactly, my code went through 3 refactors since). I have entertained the idea of the SyntaxWalker but eventually found out that it wasn't needed: `TypeInfo.Type.GetMembers()` contains all the members (and thus methods) declared on that level and has an explicit `.IsOverride` property. So I pretty much threw away a big bunch of code and replaced it with these few lines. From the initial checks it seems to work just fine, I'll add a CW answer tomorrow after I had the chance to look it all through. I appreciate your input! – Jeroen Vannevel Apr 27 '14 at 21:23