0

This is freaking me out, and I'm guessing it's because I'm severely misunderstanding something basic about how assemblies get loaded. I was not expecting this to work, can someone explain why it does?

Projects:

  • Plugins has the definition of a plugin class
  • Lib1 References Plugins and defines a plugin class
  • Lib2 References Plugins and defines a plugin class
  • Console references Plugins and looks for dlls near itself to load

Lib1 and Lib2 share a code file via symlink:

namespace Shared
{
    public class SharedClass
    {
        public static string Key { get; set; }
    }
}

Lib1 Plugin:

namespace Lib1
{
    public class Lib1Plugin : Plugin
    {
        public override void Load()
        {
            SharedClass.Key = "Lib1 Key";

            Console.WriteLine(SharedClass.Key);
        }

        public override void Run()
        {
            Console.WriteLine(SharedClass.Key);
        }
    }
}

Lib2 Plugin:

namespace Lib2
{
    public class Lib2Plugin : Plugin
    {
        public override void Load()
        {
            SharedClass.Key = "Lib2 Key";

            Console.WriteLine(SharedClass.Key);
        }

        public override void Run()
        {
            Console.WriteLine(SharedClass.Key);
        }
    }
}

Console:

    static class Functions
    {
        public static IEnumerable<Type> FindDerivied(Assembly asm, Type baseType)
        {
            try
            {
                return asm.GetTypes().Where(t => baseType.IsAssignableFrom(t) && t != baseType);
            }
            catch (Exception e)
            {
                return new List<Type>();
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var di = new DirectoryInfo("Plugins");

            var bos = new List<Plugin>();
            if (di.Exists)
            {
                var dlls = di.EnumerateFiles();
                foreach (var dll in dlls)
                {
                    var asm = Assembly.LoadFrom(dll.FullName);

                    var builders = Functions.FindDerivied(asm, typeof(Plugin));
                    foreach (var builder in builders)
                    {
                        var bo = (Plugin)Activator.CreateInstance(builder);

                        bo.Load();
                        bos.Add(bo);
                    }
                }

                foreach (var bo in bos)
                {
                    bo.Run();
                }

                var asms = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var asm in asms)
                {
                    var exports =
                        asm.GetExportedTypes().Where(type => type.Name == "SharedClass")
                        .ToList();

                    foreach (var export in exports)
                    {
                        Console.WriteLine(export.FullName);
                    }
                }
            }
        }
    }

Output:

Lib1 Key
Lib2 Key
Lib1 Key
Lib2 Key
Shared.SharedClass
Shared.SharedClass

How does it know the difference!?

Thadeux
  • 146
  • 1
  • 8
  • namespace, unless I'm missing something. – Mike_G Jan 10 '17 at 20:04
  • The last thing the console app does is print out the full names of the two classes called "SharedClass" and they're both "Shared.SharedClass." Unless .Net add some kind of smart namespaces to assemblies? I know vbnet does something like that... there's a default namespace given to assemblies or something? If it's doing that, it's not reflected in the type's FullName. – Thadeux Jan 10 '17 at 20:11
  • My current thinking is that this works because everything the two assemblies need is contained within themselves. If you reference both assemblies in the same parent project, you can't compile, but I'm guessing that is the reason for the extern alias: http://stackoverflow.com/questions/2347260/when-must-we-use-extern-alias-keyword-in-c Can anyone verify that? So, this kind of thing would "work" in production code, but an issue would be if the two assemblies expected their respective "SharedClass" to be the same class, that's not true at all. – Thadeux Jan 11 '17 at 04:13

2 Answers2

2

There is nothing preventing two assemblies from declaring types with identical fully-qualified names. Whether those types are similar or completely different (or here, are actually defined in the same source file) is irrelevant.

Although the page discussing extern alias uses "two versions of the same assembly" as it's motivating example, it's describing the general mechanism that would allow any consuming application to consume two (or more) libraries that declare types with identical fully-qualified type names.

You might have to reference two versions of assemblies that have the same fully-qualified type names. For example, you might have to use two or more versions of an assembly in the same application. By using an external assembly alias, the namespaces from each assembly can be wrapped inside root-level namespaces named by the alias, which enables them to be used in the same file.

And further, what this comes down to is that a fully-qualified type name, by itself, does not uniquely identify a specific type. A type's identity includes not just its name but also its assembly.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
0

You did not share compiled code, but the file SharedClass.cs file. So the libraries don't know about each other's SharedClass, and hence there is nothing which should "know the difference". At compile time, each plugin gets linked to the SharedClass contained in the same assembly, and at runtime there are two SharedClasses which do not know anything about each other.

Bernhard Hiller
  • 2,163
  • 2
  • 18
  • 33