在VS中引用类库时有多种方法,其中用的最多的就是在引用时选择项目选项卡引用本解决方案下的类库项目和选择浏览选项卡直接引用类库DLL文件,实际上这两种引用方式略有不同,今天就为大家总结下。
C#本地项目(控制台,winform)
- 引用类库项目,这时对引用DLL类库做出任何修改会立刻生效,不用保存和生成类库。如果删除类库项目,则本项目中引用的DLL也被删除。
- 引用类库DLL,这时对类库的修改,需要先生成类库项目,再生成本项目才会生效。 如果删除或改变类库DLL的目录,则本项目中的引用会指向本项目bin\debug目录下生成的DLL(但是前提是在删除或改变类库DLL的目录后,不要立即重新生成本解决方案,因为重新生成解决方案和生成解决方案的原理不一样,重新生成解决方案时VS会先清空本项目bin\debug目录下原先生成的DLL类库文件也就是先清空解决方案,再生成本解决方案,而这时本项目bin\debug目录中已经没有原先生成的DLL类库文件了,生成解决方案时当然会报错,你必须先生成解决方案或运行本项目,这时本项目中的引用才会指向本项目bin\debug目录下原先生成的DLL类库文件,并将这个新的引用信息保存到项目文件,这时你再重新生成解决方案VS就不会去删除bin\debug目录下原先生成的DLL类库文件了,但是如果该DLL类库又引用了其它类库项目(即本项目和这些其它的类库项目之间是间接引用关系,关于间接引用详情请见后面的附加内容),那么这时对本项目重新生成解决方案,VS还是会去将bin\debug目录下该DLL类库文件引用的其它类库项目的DLL文件给删除掉,所以这时如果本项目引用的DLL类库调用了这些其它类库项目中的代码可能会报错,所以请不要轻易去删除或改变项目中所引用DLL类库文件的目录)。但是如果还原该类库DLL到原目录,则本项目中的引用又会指向该类库DLL。
- 以上无论哪种方式都要将引用的DLL文件的属性设置为复制到本地,否则运行本项目时会报错。
ASP.NET Web应用程序
- 引用类库项目,这时对引用DLL类库做出任何修改会立刻生效,不用保存和生成类库。如果删除类库项目,则本项目中引用的DLL也被删除。
- 引用类库DLL,这时对类库的修改,需要先生成类库项目,再生成本项目才会生效。 如果删除或改变类库DLL的目录,则本项目中的引用会指向本项目bin目录下生成的DLL(但是前提是在删除或改变类库DLL的目录后,不要立即重新生成本解决方案,因为重新生成解决方案和生成解决方案的原理不一样,重新生成解决方案时VS会先清空本项目bin目录下原先生成的DLL类库文件也就是先清空解决方案,再生成本解决方案,而这时本项目bin目录中已经没有原先生成的DLL类库文件了,生成解决方案时当然会报错,你必须先生成解决方案或运行本项目,这时本项目中的引用才会指向本项目bin目录下原先生成的DLL类库文件,并将这个新的引用信息保存到项目文件,这时你再重新生成解决方案VS就不会去删除bin目录下原先生成的DLL类库文件了,但是如果该DLL类库又引用了其它类库项目(即本项目和这些其它的类库项目之间是间接引用关系,关于间接引用详情请见后面的附加内容),那么这时对本项目重新生成解决方案,VS还是会去将bin\debug目录下该DLL类库文件引用的其它类库项目的DLL文件给删除掉,所以这时如果本项目引用的DLL类库调用了这些其它类库项目中的代码可能会报错,所以请不要轻易去删除或改变项目中所引用DLL类库文件的目录)。但是如果还原该类库DLL到原目录,则本项目中的引用又会指向该类库DLL。
- 以上无论哪种方式都要将引用的DLL文件的属性设置为复制到本地,否则运行本项目时会报错。
ASP.NET 网站
- 引用类库项目,这时对引用DLL类库做出任何修改会立刻生效,不用保存和生成类库。如果删除类库项目,则本项目中引用的DLL也被删除。
- 引用类库DLL,这时对类库的修改,需要先生成类库项目,再生成本网站项目才会生效。 如果删除或改变类库DLL的目录,本项目中的引用还是会指向原先DLL类库文件的目录(不过ASP.NET网站是通过refresh文件指向该DLL文件的地址),不会指向本项目Bin目录下生成的DLL,但是在对本项目生成解决方案时,会产生一个警告指示找不到所引用DLL类库项目的原始文件,如果还原该DLL类库到原目录,警告就会消失。此外对ASP.NET网站项目重新生成解决方案时,VS不会去删除本项目Bin目录下的任何文件,所以即便是删除或改变本项目所引用DLL类库文件的目录,由于本项目的Bin目录下的所有DLL文件都还在,因此本项目的所有代码还是可以正确运行。
- ASP.NET 网站引用的DLL文件都会复制到本网站的Bin目录。
附加内容:这里顺便谈谈类库引用链中的直接引用和间接引用的一个小问题
首先来谈谈什么叫引用链,大家知道C#项目中的类库还可以引用其它的类库,那么假如现在有3个类库:LibA、LibB、LibC,这三个类库存在引用关系:LibA<-LibB<-LibC(其中A<-B表示类库B引用类库A生成的DLL文件),那么我们就说这三个类库LibA<-LibB<-LibC是一条引用链(LibA是引用链的头,LibC是引用链的末尾)。
- 直接引用:直接引用表示两个类库在引用链中的位置是相邻的,比如上面的LibA和LibB以及LibB和LibC。
- 间接引用:间接引用表示两个类库在引用链中的位置是不相邻的,比如上面的LibA和LibC。
在引用链的直接引用关系中,比如LibA<-LibB,我们都知道,类库项目LibB在生成后会将类库项目LibA生成的DLL文件,复制到LibB项目自己的bin\Debug目录下,这没有什么问题。在引用链的间接引用中,比如LibA<-LibB<-LibC中,我们知道生成该引用链中的三个类库项目后,LibC的bin\Debug目录下肯定有LibB.DLL,因为LibC和LibB是直接引用关系,LibB的bin\Debug目录下肯定有LibA.DLL,因为LibB和LibA是直接引用关系。但是问题是LibC的bin\Debug目录下是否也有和其存在间接引用关系的类库项目LibA的DLL文件LibA.DLL?
.NET Framework
经过多次试验,我发现如果类库项目LibB中使用了类库项目LibA中的成员时(即LibB不仅引用了LibA的DLL文件,还使用了LibA的DLL文件,比如调用了LibA的方法,声明了LibA中类的对象等,但切记只用using导入LibA中的命名空间不叫使用),那么在引用链LibA<-LibB<-LibC生成后,类库项目LibC的bin\Debug目录下会同时生成LibA.DLL和LibB.DLL。但是如果类库项目LibB中没有使用类库项目LibA中的成员时(即LibB只引用了LibA,但是LibB中完全没有使用LibA),类库项目LibC的bin\Debug目录下就只有LibB.DLL。
所以你会发现其实VS在生成引用链时很聪明,因为在生成LibA<-LibB<-LibC中的LibC时,VS会去查看与LibC存在直接引用关系的类库项目LibB的生成目录bin\Debug下有几个DLL文件,那么在本例中由于LibA和LibB是直接引用关系,那么LibB的bin\Debug下肯定有两个DLL文件LibA.DLL和LibB.DLL。那么这时VS就会去探查LibB.DLL中是否使用了LibA.DLL。如果使用了VS就会把LibA.DLL、LibB.DLL同时Copy到LibC的bin\Debug目录下。如果没有使用LibA.DLL,那么VS会认为LibA.DLL是多余的,只会把LibB.DLL Copy到LibC的bin\Debug目录下。
但是上面这段话不是绝对的,因为我发现如果将引用链末端的类库项目LibC换成Asp.net网站或Asp.net应用程序,其生成DLL文件的原理是不一样的,说明.NET Framework的每种项目引用DLL文件时都有自己的一套生成规则,上面只讨论了类库项目的生成规则。
.NET Core
在.NET Core中项目间DLL的引用变得非常简单和明朗,假设还是上面3个类库项目:LibA、LibB、LibC,引用关系还是: LibA<-LibB<-LibC 。只要类库项目LibC引用了类库项目LibB,不管LibB里面有没有使用LibA中的成员,类库项目LibC的bin\Debug\netcoreappX.X目录下会同时生成LibA.dll和LibB.dll文件。
我们可以在Visual Studio的解决方案资源管理器窗口中看看类库项目LibC的项目引用显示结构:
可以看到在.NET Core项目中类库的引用方式和传统的.NET Framework项目发生了改变,我们在LibC类库项目中只引用了LibB类库项目,但是在上图的项目引用节点下逐层展开,我们会看到LibB类库项目引用了LibA类库项目,所以在.NET Core项目LibC中引用类库项目LibB,就相当于还引用了类库项目LibA,.NET Core会回溯类库项目的引用链,将引用链上所有类库项目的dll文件都复制到引用链末尾项目的bin\Debug\netcoreappX.X目录下(GAC中的.NET Core类库dll文件除外)。例如我们可以看到.NET Core类库项目LibC的bin\Debug\netcoreappX.X目录下就同时存在类库项目LibA和LibB的dll文件:
甚至我们可以在类库项目LibC间接引用类库项目LibA时,直接在类库项目LibC中声明和构造类库项目LibA的类LibAObject:
如果类库项目LibA引用了Nuget包,那么我们也可以在类库项目LibC间接引用类库项目LibA时,直接使用类库项目LibA中的Nuget包。
例如下图中我们可以看到,在类库项目LibA中引用了Nuget包Newtonsoft.Json,在类库项目LibC中没有直接引用任何Nuget包。但是在类库项目LibC的引用节点下逐层展开,我们可以看到类库项目LibA引用的Nuget包Newtonsoft.Json,也就相当于类库项目LibC也引用了Nuget包Newtonsoft.Json
我们可以直接在类库项目LibC中使用Newtonsoft.Json的类和方法,但是类库项目LibC并没有直接引用Nuget包Newtonsoft.Json
.NET Core项目会优先使用版本更高的程序集
如果我们在一个.NET Core项目中,引用了两个相同的NuGet包,但是两个NuGet包的版本不一样,.NET Core项目会自动使用更高版本NuGet包的程序集。
我们来看个例子,新建一个.NET Core控制台项目TestingApp,然后引用NuGet包Microsoft.EntityFrameworkCore.SqlServer(2.1.4),如下图所示:
我们可以看到,由于NuGet包Microsoft.EntityFrameworkCore.SqlServer(2.1.4)内部还引用了NuGet包System.Data.SqlClient(4.5.1),所以相当于我们的.NET Core控制台项目TestingApp也引用了NuGet包System.Data.SqlClient(4.5.1)。
我们可以在Program类的Main方法中,声明一个SqlConnection类的对象,然后通过鼠标右键菜单点击"Go To Definition"查看其程序集版本:
可以看到当前项目中System.Data.SqlClient程序集的版本是4.4.0.0
然后我们在项目中再单独引用NuGet包System.Data.SqlClient(4.6.0)
我们可以看到当我们引用NuGet包System.Data.SqlClient(4.6.0)后,在NuGet包Microsoft.EntityFrameworkCore.SqlServer(2.1.4)内部引用的System.Data.SqlClient也变为了4.6.0版本:
此时我们再选中SqlConnection类,然后通过鼠标右键菜单点击"Go To Definition"查看其程序集版本:
可以看到这时当前项目中System.Data.SqlClient程序集的版本是4.5.0.0,所以由于我们在项目中引用了版本更高的NuGet包System.Data.SqlClient(4.6.0),所以此时整个.NET Core项目实际上使用的是版本更高的NuGet包程序集,可见.NET Core项目在程序集引用机制上的确做了很大的改进。