Please find the updated version of this post here: https://piotr.westfalewicz.com/blog/2016/07/the-performance-of-setting-t-vs.-list-by-index/
Given the following method:
private static void CompareTypes(Type type1, Type type2) { Console.WriteLine($"type1.FullName = {type1.FullName}"); Console.WriteLine($"type2.FullName = {type2.FullName}"); Console.WriteLine($"type1.FullName {(type1.FullName == type2.FullName ? '=' : '!')}= type2.Fullname"); Console.WriteLine($"type1.AssemblyQualifiedName = {type1.AssemblyQualifiedName}"); Console.WriteLine($"type2.AssemblyQualifiedName = {type2.AssemblyQualifiedName}"); Console.WriteLine($"type1.AssemblyQualifiedName {(type1.AssemblyQualifiedName == type2.AssemblyQualifiedName ? '=' : '!')}= type2.AssemblyQualifiedName"); Console.WriteLine($"type1.GUID = {type1.GUID}"); Console.WriteLine($"type2.GUID = {type2.GUID}"); Console.WriteLine($"type1.GUID {(type1.GUID == type2.GUID ? '=' : '!')}= type2.GUID"); Console.WriteLine("o1 = Activator.CreateInstance(type1)"); Console.WriteLine("o2 = Activator.CreateInstance(type2)"); var o1 = Activator.CreateInstance(type1); var o2 = Activator.CreateInstance(type2); Console.WriteLine($"o1 == {o1}"); Console.WriteLine($"o2 == {o2}"); Console.WriteLine(); Console.WriteLine($"but... type1 {(type1 == type2 ? '=' : '!')}= type2"); }Is it possible to get the following result?
type1.FullName = MyLibrary.MyPrecious type2.FullName = MyLibrary.MyPrecious type1.FullName == type2.Fullname type1.AssemblyQualifiedName = MyLibrary.MyPrecious, MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null type2.AssemblyQualifiedName = MyLibrary.MyPrecious, MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null type1.AssemblyQualifiedName == type2.AssemblyQualifiedName type1.GUID = cacf8c0d-b903-3da6-808f-024a3070ab9d type2.GUID = cacf8c0d-b903-3da6-808f-024a3070ab9d type1.GUID == type2.GUID o1 = Activator.CreateInstance(type1) o2 = Activator.CreateInstance(type2) o1 == MyLibrary.MyPrecious o2 == MyLibrary.MyPrecious but... type1 != type2As it turns out, it is. Doing such a hell is relatively easy:
private static Assembly LoadAssemblyByName(string name) { var myPreciousAssemblyLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), name); using (var fs = new FileStream(myPreciousAssemblyLocation, FileMode.Open, FileAccess.Read)) { var data = new byte[fs.Length]; fs.Read(data, 0, data.Length); fs.Close(); var assembly = Assembly.Load(data); return assembly; } } static void Main() { var type1 = typeof (MyPrecious); var myLibraryAssembly = LoadAssemblyByName("MyLibrary.dll"); var type2 = myLibraryAssembly.GetType("MyLibrary.MyPrecious", true); CompareTypes(type1, type2); }The code above compares type1 from referenced project to type2 from the same assembly, but loaded again through Assembly.Load(byte[]). That makes the library loaded twice in the AppDomain. Now when a call to AppDomain.CurrentDomain.GetAssemblies() is made, the assemblies are:
AppDomain.CurrentDomain.GetAssemblies: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Microsoft.VisualStudio.HostingProcess.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a Microsoft.VisualStudio.HostingProcess.Utilities.Sync, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a Microsoft.VisualStudio.Debugger.Runtime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a vshost32, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null Accessibility, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3Even in such a small, console application it is quite confusing. So, let's make it more confusing... What's the output of the following code?
var myPreciousAssemblyLocation = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "MyLibrary.dll"); var myLibraryAssemblyLoadFrom = Assembly.LoadFrom(myPreciousAssemblyLocation); var type3 = myLibraryAssemblyLoadFrom.GetType("MyLibrary.MyPrecious", true); CompareTypes(type1, type3);Now, surprisingly, its:
type1.FullName = MyLibrary.MyPrecious type2.FullName = MyLibrary.MyPrecious type1.FullName == type2.Fullname type1.AssemblyQualifiedName = MyLibrary.MyPrecious, MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null type2.AssemblyQualifiedName = MyLibrary.MyPrecious, MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null type1.AssemblyQualifiedName == type2.AssemblyQualifiedName type1.GUID = cacf8c0d-b903-3da6-808f-024a3070ab9d type2.GUID = cacf8c0d-b903-3da6-808f-024a3070ab9d type1.GUID == type2.GUID o1 = Activator.CreateInstance(type1) o2 = Activator.CreateInstance(type2) o1 == MyLibrary.MyPrecious o2 == MyLibrary.MyPrecious but... type1 == type2
Hint
A nice hint is shown, when you try to execute the following code:
var o1 = Activator.CreateInstance(type1); var o2 = Activator.CreateInstance(type2); MyPrecious p1 = (MyPrecious) o1; try { MyPrecious p2 = (MyPrecious)o2; } catch (Exception e) { Console.WriteLine(e); }
System.InvalidCastException: [A]MyLibrary.MyPrecious cannot be cast to [B]MyLibrary.MyPrecious. Type A originates from 'MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither' in a byte array. Type B originates from 'MyLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'c:\users\pwdev\documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\bin\Debug\MyLibrary.dll'. at ConsoleApplication1.Program.Main() in c:\users\pwdev\documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:line 49
Explanation
Yes, it's all about the load contexts. There are three different assembly load contexts: Load, LoadFrom, Neither. Usually there is no need to load the same library twice and get the strange behavior written above, but sometimes there might be. There are many advantages and disadvantages of using different Assembly.Load(From/File) methods. Take a look: Choosing a Binding Context. Furthermore, consider what's happening to assembly dependencies when you load an assembly. There are best practices described on MDSN for loading assemblies: Best Practices for Assembly Loading. I have to say, in my whole career I've been loading assemblies by hand twice, and from time perspective, both two cases were wrong.
TypeHandle
Instead of comparing the types in the examples above by == operator, there is a possibility to compare them by the TypeHandle:
TypeHandle encapsulates a pointer to an internal data structure that represents the type. This handle is unique during the process lifetime. The handle is valid only in the application domain in which it was obtained.Source: MDSN. Well, I can't think of an interesting usage for the TypeHandles for now, but it's good to know.
No comments:
Post a Comment