Saturday, February 9, 2008

 

Unable to cast object of type 'Y' to type 'Z'

The other day, I had a very strange exception. I had an object that very clearly implemented an interface. Yet, when I tried to cast the object to the interface using as, the cast always returned null.



interface IMyInterface
{
void MyMethod();
}

class MyClass : IMyInterface
{
void MyMethod()
{
}
}

MyClass myClass = new MyClass();
IMyInterface myInterface = myClass as IMyInterface();

if (myInterface == null)
{
Debug.Assert(false, "What the heck?);
}

 

Since my application uses interfaces extensively, this seemed like a break-down in the very fabric of the .NET space-time continuum. All of the other casts of objects to interfaces were working fine, except for this one object. If there's one thing my years of enigneering and computer programming has taught me, it's that there is always a reason why something really weird happened.

I tried everything, and even verified that the object instance did indeed support the interface by using .GetType().GetInterface() and .GetType().GetInterfaces(). Since casts using as "fail gracefully" and return null, I tried casting using (IMyInterface) instead to see if I could get an exception. Sure enough, I got an exception:

"The type initializer for 'X' threw an exception. Unable to cast object of type 'Y' to type 'Z'"

This "type initializer" exception did, of course, clue me in that there was assembly version issue. But where? I double-checked all of our make files (we build at the command line instead of in Visual Studio), deleted all DLLs to make sure there was not some kind of circular reference of the assemblies, closed Visual Studio and all other apps to make sure nothing was keeping a file locked, rebooted the computer, etc. I spent hours trying to figure out what was happening. Then, by accident, I started the release version of our software instead of the bug version, and it worked!

That was new information -- it works in release mode by not in debug mode. Our build process generates two separate sets of binaries: release and debug. And, the filenames for the debug binaries all end in the letter "D".

With this new information, I was eventually able to find the culprit. Although the application is a WinForms app, it uses XamlReader and XamlWriter to serialize and deseialize some custom WPF objects that get displayed in an ElementHost control. And, in the assembly that contained our custom controls, I had accidentally omitted the XmlnsDefinition assembly attribute that maps an XML namespace to a .NET namespace. As a result, the XamlWriter was putting the assembly name into the XAML file.

Since our debug assemblies end in "D" but the release assemblies do not, the XAML files that were generated from release software could only be loaded in release software, and files generated by debug software could only be loaded by debug software. It just so happes that, during my hours of frustration, I was using the debug version of our software but trying to load XAML files that had apparently been created from our release version.

Once I found the problem, the solution was simple: adding the XmlnsDefinition made the problem go away because the XML namespace was written into the file instead of the assembly. But, it really got me thinking. Anytime an assembly is loaded by name, this problem could exist. For example, the the Type.GetType() method allows you to load a type from a "type,assembly" string.



// Create and instance of type MyNameSpace.MyType from assembly MyAssembly
Type myType = Type.GetType("MyNameSpace.MyType,MyAssembly");
MyType myType = (MyType)Activator.CreateInstance(type);

 

Comments:
This type of problem also is frustrating when developing "plug-ins" since an interface that is compiled from the exact same code in one assembly is different than that code in another assembly. You'll get the same frustration of trying to cast an object into an "IFeature" and it won't work even though the debugger shows that it is an "IFeature." Under the covers, they're coming from two different assemblies that have different hashes.

That's why System.Addin in .net 3.5 makes things simpler.
 
Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]