症状:
请看下面三个代码以及对应的编译步骤,能看出什么问题出来吗?
ITest.cs:
1. using System; | 2. | 3. public interface ITest | 4. { | 5. void TestMethod(); | 6. } | TestClass.cs:
1. using System; | 2. | 3. public class TestClass : ITest | 4. { | 5. public void TestMethod() | 6. { | 7. Console.WriteLine("TestClass.TestMethod"); | 8. } | 9. } | Program.cs
1. using System; | 2. using System.Reflection; | 3. | 4. public class Program | 5. { | 6. public static void Main() | 7. { | 8. Assembly assembly = Assembly.LoadFrom("TestClass.dll"); | 9. Type type = assembly.GetType("TestClass"); | 10. object instance = Activator.CreateInstance(type); | 11. ((ITest)instance).TestMethod(); | 12. } | 13. } | 编译命令:
csc /t:library /out:TestClass.dll TestClass.cs ITest.cs | csc Program.cs ITest.cs | |
代码很简单,我们定义了一个公开的接口ITest,提供了一个实现该接口的类TestClass,然后在Program.cs通过发射创建TestClass的一个实例,然后调用ITest接口里面的一些方法。这是一个实现插件系统的最基本的操作, 但是当你执行编译生成出来的Program.exe的时候,程序抛出一个InvalidCastException:
Unhandled Exception: System.InvalidCastException: Unable to cast object of type 'TestClass' to type 'ITest'. at Program.Main() |
类的全名(或者说强签名)实际上包含Assembly完整名称的。
10. object instance = Activator.CreateInstance(type); |
11. ((ITest)instance).TestMethod(); |
这两行,在CLR看来其实等于(分解成几步伪码):
[TestClass,Version=0.0.0.0]ITest instance = Activator.CreateInstance(type);
[Program, Version=0.0.0.0]ITest temp = ([Program, Version=0.0.0.0]ITest)instance;
Temp.TestMethod();
所以说,两个接口不是同一个接口。
但是如果在程序里面使用到两个不同的Assembly,并且这两个Assembly都包含名称相同的类,如果你的Winform程序里面需要寄宿WPF控件,或者反过来,你就经常会碰到这种问题,例如两种程序框架里面都包含TextBox类。当然啦,你可以说WPF和Winform的类的命名空间不一样,只要在用的时候指定完整的命名空间就可以了,但是如果你碰到连命名空间都一样的情况下,或者说,如果你的程序同时引用到同一个Assembly的两个不同版本的文件,又如何解决呢?C#没有提供语法支持你在类名前面加上Assembly的名字—虽然IL里面提供了对应的语法。
幸好,C#编译器csc.exe为我们提供了一个解决方案,csc.exe提供了一个参数/reference:<alias>=<file>可以让你为你程序所引用的一个Assembly指定别名,然后在源代码里面使用这个别名将名称重复的类改名,具体做法如下:
ITest.cs:
1. using System; | 2. | 3. public interface ITest | 4. { | 5. void TestMethod(); | 6. } | TestClass.cs:
1. using System; | 2. | 3. public class TestClass : ITest | 4. { | 5. public void TestMethod() | 6. { | 7. Console.WriteLine("TestClass.TestMethod"); | 8. } | 9. } | Program.cs
1. extern alias ITestDll; | 2. using System; | 3. using System.Reflection; | 4. | 5. public interface ITest | 6. { | 7. void TestMethod(); | 8. } | 9. | 10. public class Program | 11. { | 12. public static void Main() | 13. { | 14. Assembly assembly = Assembly.LoadFrom("TestClass.dll"); | 15. Type type = assembly.GetType("TestClass"); | 16. object instance = Activator.CreateInstance(type); | 17. ((ITestDll::ITest)instance).TestMethod(); | 18. } | 19. } | 编译命令:
csc /t:library ITest.cs | csc /t:library /r:ITest.dll /out:TestClass.dll TestClass.cs | csc /r:ITestDll=ITest.dll Program.cs | |
这样就解决了类名重复的问题了。