🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 代码分析 - 借助与 NuGet 集成的 Roslyn 代码分析来生成和部署库 作者: [Alessandro Del Sole](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Alessandro+Del+Sole) | 2015 年 10 月 | 获取代码: [C#](https://msdn.microsoft.com/zh-cn/magazine/mt573715.aspx#)[VB](https://msdn.microsoft.com/zh-cn/magazine/mt573715.aspx#) Microsoft .NET 编译器平台(也称为“Roslyn”代码库)提供了大量代码分析 API 的开放式源 C# 和 Visual Basic 编译器,使你可以生成要集成到 Visual Studio 2015 代码编辑器的实时分析规则。有了 .NET 编译器平台,你可以编写自定义、特定于域的代码分析器和重构功能,使 Visual Studio 可以检测你键入的代码问题并报告警告和错误消息。.NET 编译器平台的一大优势是可实现代码分析器与 API 的捆绑。譬如说,如果要生成库或可重用的用户控件,可以将分析器与库一起提供,并为开发人员提供良好的编码体验。 在本文中,我将介绍如何将库和分析器捆绑到 NuGet 包以用于在线部署,并向你演示如何为 API 提供集成 Roslyn 代码分析。你至少需要对 .NET 编译器平台的概念以及代码分析器的编写稍微了解一些。有关这两个主题的文章之前在 MSDN 杂志上发表过,文章的作者是 Alex Turner,文章名为 “C# 和 Visual Basic: 使用 Roslyn 编写 API 的实时代码分析器” ([msdn.microsoft.com/magazine/dn879356](https://msdn.microsoft.com/magazine/dn879356)) 和“将代码修补程序添加到 Roslyn 分析器” ([msdn.microsoft.com/magazine/dn904670](https://msdn.microsoft.com/magazine/dn904670)),我们强烈建议你先阅读这两篇文章,然后再继续。 ## 准备一个示例库来模拟自定义 API 你首先需要一个类库用来模拟自定义 API。本文借助于示例库向大家展示一个简单的公用方法,即从 RSS 源检索公共信息并返回源项的集合。在 Visual Studio 2015 中,使用 C# 或 Visual Basic 创建一个新的可移植类库,名为 FeedLibrary,并确保最低目标是适用于 Windows 8.1、Windows Phone 8.1 和 Microsoft .NET Framework 4.5.1。有了这个目标,该库也可以利用 Async/Await 模式,没有任何其他要求。 将 Class1.vb 或 Class1.cs 生成的文件重命名为 FeedItem.vb/.cs。该类的 C# 代码如图 1 所示,Visual Basic 代码如图 2 所示。 图 1 在 C# 中实现从 RSS 源检索公共项的类 ~~~ using System.Net.Http; using System.Threading.Tasks; using System.Xml.Linq; namespace FeedLibrary {   // Represent a single content in the RSS feed   public class FeedItem   {     // Properties representing information,     // which is common to any RSS feed     public string Title { get; set; }     public string Author { get; set; }     public string Description { get; set; }     public DateTimeOffset PubDate { get; set; }     public Uri Link { get; set; }     // Return a collection of FeedItem objects from a RSS feed     public static async Task<IEnumerable<FeedItem>> ParseFeedAsync(       string feedUrl)     {       var client = new HttpClient();       // Download the feed content as a string       var result = await client.GetStringAsync(new Uri(feedUrl));       // If no result, throw an exception       if (result == null)       {         throw new InvalidOperationException(           "The specified URL returned a null result");       }       else       {         // LINQ to XML: Convert the returned string into an XDocument object         var doc = XDocument.Parse(result);         var dc = XNamespace.Get("http://purl.org/dc/elements/1.1/");         // Execute a LINQ query over the XML document         // and return a collection of FeedItem objects         var query = (from entry in doc.Descendants("item")                      select new FeedItem                      {                          Title = entry.Element("title").Value,                          Link = new Uri(entry.Element("link").Value),                          Author = entry.Element(dc + "creator").Value,                          Description = entry.Element("description").Value,                          PubDate = DateTimeOffset.Parse(                            entry.Element("pubDate").Value,                      System.Globalization.CultureInfo.InvariantCulture)                      });         return query;       }     }   } } ~~~ 图 2 在 Visual Basic 中实现从 RSS 源检索公共项的类 ~~~ Imports System.Net.Http Imports <xmlns:dc="http://purl.org/dc/elements/1.1/"> 'Represent a single content in the RSS feed Public Class FeedItem   'Properties representing information   'which is common to any RSS feed   Public Property Title As String = String.Empty   Public Property Author As String = String.Empty   Public Property Description As String = String.Empty   Public Property PubDate As DateTimeOffset   Public Property Link As Uri   'Return a collection of FeedItem objects from a RSS feed   Public Shared Async Function ParseFeedAsync(feedUrl As String) As _     Task(Of IEnumerable(Of FeedItem))     Dim client As New HttpClient     'Download the feed content as a string     Dim result = Await client.GetStringAsync(New Uri(feedUrl, UriKind.Absolute))     'If no result, throw an exception     If result Is Nothing Then        Throw New InvalidOperationException(          "The specified URL returned a null result")     Else       'LINQ to XML: Convert the returned string       'into an XDocument object       Dim document = XDocument.Parse(result)       'Execute a LINQ query over the XML document       'and return a collection of FeedItem objects       Dim query = From item In document...<item>                   Select New FeedItem With {                   .Title = item.<title>.Value,                   .Author = item.<dc:creator>.Value,                   .Description = item.<description>.Value,                   .PubDate = DateTimeOffset.Parse(item.<pubDate>.Value),                   .Link = New Uri(item.<link>.Value)}       Return query     End If   End Function End Class ~~~ 这段代码很简单: 它从指定 RSS 源的 URL 下载整合的内容、为每个源项创建 Feed­Item 类的实例并最终返回一组新项。若要使用该库,只需对于 C# 调用静态 ParseFeedAsyncAsync 方法即可,如下所示: ~~~ // Replace the argument with a valid URL var items = await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss"); ~~~ 而对于 Visual Basic,调用方法如下所示: ~~~ 'Replace the argument with a valid URL Dim items = Await FeedItem.ParseFeedAsyncAsync("http://sampleurl.com/rss") ~~~ 该调用返回 IEnumerable,你随后可以根据需要使用它。选择发布配置并生成项目;此时,Visual Studio 2015 将生成一个名为 FeedLibrary.dll 的库,以供后续使用。 ## 编写 Roslyn 分析器 下一步是创建 Roslyn 分析器,为自定义 API 提供特定于域的实时分析规则。该分析器将使用 Uri.IsWellFormedUriString 方法检测作为 ParseFeedAsyncAsync 方法自变量提供的 URL 是否正确;如果不正确,分析器在你键入内容时将报告警告消息。当然,检测 URL 是否无效的方法还有很多,但是这种方法较简单。此外,出于同样原因,分析器将提供实时分析报告警告,但是它不提供任何代码修复,这部分内容留给你自己练习。然后执行以下步骤: 1. 在 Solution Explorer(解决方案资源管理器)中,右键单击解决方案名称,然后选择“Add | New Project”(添加 | 新建项目)。 2. 在项目模板列表的“Extensibility”(可扩展性)节点中,选择“Analyzer with Code Fix”(带有代码修补程序的分析器)(NuGet + VSIX)。注意,Extensibility(可扩展性)节点在默认情况下不显示。你首先需要下载 .NET 编译器平台 SDK 才能提取 Analyzer with Code Fix(带有代码修补程序的分析器)模板项目。在“New Project”(新建项目)对话框中搜索分析器,然后你会看到要下载此 SDK 的模板项目。 3. 调用新分析器 FeedLibraryAnalyzer 并单击“OK”(确定)。 4. 当新项目就绪时,删除 CodeFix­Provider.cs(或 .vb)文件。 在 DiagnosticAnalyzer.cs(或 .vb)文件中,首先要做的是根据编码经验提供用来标识分析器的字符串。为了简化分析器的实现,在当前示例中,我使用常规字符串替代 LocalizableString 对象和资源文件,而且假设分析器不需要本地化。对于 C#,重写诊断 ID、标题、消息、描述和类别,如下所示: ~~~ public const string DiagnosticId = "RSS001"; internal static readonly string Title = "RSS URL analysis"; internal static readonly string MessageFormat = "URL is invalid"; internal static readonly string Description =   "Provides live analysis for the FeedLibrary APIs"; internal const string Category = "Syntax"; ~~~ 对于 Visual Basic,如下所示: ~~~ Public Const DiagnosticId = "RSS001" Friend Shared ReadOnly Title As String = "RSS URL analysis" Friend Shared ReadOnly MessageFormat As String = "URL is invalid" Friend Shared ReadOnly Description As String =   "Provides live analysis for the FeedLibrary APIs" Friend Const Category = "Syntax" ~~~ 不要更改诊断的严重性,它在默认情况下为“警告”,而且适用于当前示例。现在,让我们把重点放在分析逻辑上。分析器必须检查代码是否正在调用名为 ParseFeedAsync 的方法。如果是,分析器随后将检查所提供的 URL 是否正确。借助于 Syntax Visualizer(语法可视化工具),可以看到 Invocation­Expression 如何调用 ParseFeedAsync 方法,这里 Invocation­Expression 的类型应该为 InvocationExpressionSyntax,如图 3 所示。 ![](https://box.kancloud.cn/2016-01-08_568f4ae787c05.png) 图 3 Syntax Visualizer(语法可视化工具)可以找到正确的语法节点表示法 所以,分析器仅关注类型为 Invocation­ExpressionSyntax 的对象;发现此类对象时,会将关联的表达式转换为类型为 MemberAccessExpressionSyntax 的对象,它包含有关方法调用的信息。如果转换成功,分析器将检查方法是否为 ParseFeedAsync,然后检索第一个自变量,并对其值执行实时分析。这是通过名为 AnalyzeMethod 的新方法实现的,并在 SyntaxNode 级别工作,图 4 所示适用于 C#,图 5 所示适用于 Visual Basic。 图 4 在 C# 中检测 ParseFeedAsync 自变量的问题 ~~~ private static void AnalyzeMethodInvocation(SyntaxNodeAnalysisContext context) {   // Convert the current syntax node into an InvocationExpressionSyntax,   // which represents a method call   var invocationExpr = (InvocationExpressionSyntax)context.Node;   // Convert the associated expression into a MemberAccessExpressionSyntax,   // which represents a method's information   // If the expression is not a MemberAccessExpressionSyntax, return   if (!(invocationExpr.Expression is MemberAccessExpressionSyntax))   {     return;   }   var memberAccessExpr = (MemberAccessExpressionSyntax)invocationExpr.Expression;   // If the method name is not ParseFeedAsync, return   if (memberAccessExpr?.Name.ToString() != "ParseFeedAsync") { return; }   // If the method name is ParseFeedAsync, check for the symbol   // info and see if the return type matches   var memberSymbol = context.SemanticModel.                      GetSymbolInfo(memberAccessExpr).                      Symbol as IMethodSymbol;   if (memberSymbol == null) { return; }   var result = memberSymbol.ToString();   if (memberSymbol?.ReturnType.ToString() !=   "System.Threading.Tasks.Task<     System.Collections.Generic.IEnumerable<FeedLibrary.FeedItem>>")   {       return;   }   // Check if the method call has the required argument number   var argumentList = invocationExpr.ArgumentList;   if (argumentList?.Arguments.Count != 1) {       return; }   // Convert the expression for the first method argument into   // a LiteralExpressionSyntax. If null, return   var urlLiteral = (LiteralExpressionSyntax)invocationExpr.ArgumentList.       Arguments[0].Expression;   if (urlLiteral == null) { return; }   // Convert the actual value for the method argument into string   // If null, return   var urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral);   var urlValue = (string)urlLiteralOpt.Value;   if (urlValue == null) { return; }   // If the URL is not well-formed, create a diagnostic   if (Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) == false)   {     var diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation(),       "The specified parameter Is Not a valid RSS feed");     context.ReportDiagnostic(diagn);   } } ~~~ 图 5 在 Visual Basic 中检测 ParseFeedAsync 自变量的问题 ~~~ Private Sub Shared AnalyzeMethodInvocation(context As SyntaxNodeAnalysisContext)   'Convert the current syntax node into an InvocationExpressionSyntax   'which represents a method call   Dim invocationExpr = CType(context.Node, InvocationExpressionSyntax)   'Convert the associated expression into a MemberAccessExpressionSyntax   'which represents a method's information   'If the expression Is Not a MemberAccessExpressionSyntax, return   If TypeOf invocationExpr.Expression IsNot MemberAccessExpressionSyntax Then Return   Dim memberAccessExpr = DirectCast(invocationExpr.Expression,     MemberAccessExpressionSyntax)   'If the method name Is Not ParseFeedAsync, return   If memberAccessExpr?.Name.ToString <> "ParseFeedAsync" Then Return   'If the method name is ParseFeedAsync, check for the symbol info   'and see if the return type matches   Dim memberSymbol = TryCast(context.SemanticModel.       GetSymbolInfo(memberAccessExpr).Symbol, IMethodSymbol)   If memberSymbol Is Nothing Then Return   Dim result = memberSymbol.ToString   If Not memberSymbol?.ReturnType.ToString =     "System.Threading.Tasks.Task(Of System.Collections.Generic.IEnumerable(     Of FeedLibrary.FeedItem))"     Then Return   'Check if the method call has the required argument number   Dim argumentList = invocationExpr.ArgumentList   If argumentList?.Arguments.Count <> 1 Then Return   'Convert the expression for the first method argument into   'a LiteralExpressionSyntax. If null, return   Dim urlLiteral =     DirectCast(invocationExpr.ArgumentList.Arguments(0).GetExpression,       LiteralExpressionSyntax)   If urlLiteral Is Nothing Then Return   'Convert the actual value for the method argument into string   'If null, return   Dim urlLiteralOpt = context.SemanticModel.GetConstantValue(urlLiteral)   Dim urlValue = DirectCast(urlLiteralOpt.Value, String)   If urlValue Is Nothing Then Return   'If the URL Is Not well-formed, create a diagnostic   If Uri.IsWellFormedUriString(urlValue, UriKind.Absolute) = False Then      Dim diagn = Diagnostic.Create(Rule, urlLiteral.GetLocation,        "The specified parameter Is Not a valid RSS feed")      context.ReportDiagnostic(diagn)   End If End Sub ~~~ 此时,你需要编辑 Initialize 方法使其调用新添加的 AnalyzeMethodInvocation 方法,图 6所示适用于 C#,图 7所示适用于 Visual Basic。 图 6 在 C# 中编辑 Initialize 方法 ~~~ public override void Initialize(AnalysisContext context) {   // Register an action when compilation starts   context.     RegisterCompilationStartAction((CompilationStartAnalysisContext ctx) =>   {     // Detect if the type metadata     // exists in the compilation context     var myLibraryType =     ctx.Compilation.     GetTypeByMetadataName("FeedLibrary.FeedItem");     // If not, return     if (myLibraryType == null)         return;     // Register an action against an InvocationExpression     ctx.RegisterSyntaxNodeAction(AnalyzeMethodInvocation,       SyntaxKind.InvocationExpression);   }); } ~~~ 图 7 在 Visual Basic 中编辑 Initialize 方法 ~~~ Public Overrides Sub Initialize(context As AnalysisContext)   ' Register an action when compilation starts   context.     RegisterCompilationStartAction     (Sub(ctx As CompilationStartAnalysisContext)       'Detect if the type metadata       'exists in the compilation context       Dim myLibraryType =         ctx.Compilation.           GetTypeByMetadataName("FeedLibrary.FeedItem")         'If not, return         '(no reference to the library)         If myLibraryType Is Nothing Then Return         'Register an action against         'an InvocationExpression         ctx.RegisterSyntaxNodeAction(           AddressOf AnalyzeMethodInvocation,           SyntaxKind.InvocationExpression)      End Sub) End Sub ~~~ 观察代码最开始的检查方式以判断项目中是否存在对库的调用,这需要通过调用 Compilation.GetTypeMetadataName 方法来实现,而且为了确保引用已经添加,其自变量必须是当前环境中已经存在的类型的名称。如果该调用返回 null,则说明类型不存在,尚未向库添加任何引用。所以,无需注册代码分析操作即可提高分析器的性能。现在,如果按下 F5 在 Visual Studio 2015 的实验实例中测试分析程序并创建具有 FeedLibrary 库引用的新项目,那么你每次提供无效 URL 时,它都会正确报告警告消息,如图 8 所示。 ![](https://box.kancloud.cn/2016-01-08_568f4ae7aff2e.png) 图 8 如果 URL 不正确,分析器将报告警告消息 到目前为止,你已经生成了 API,以及相关的特定于域的代码分析规则。现在,让我们看看如何将两者绑定到一个 NuGet 包。 ## 将 API 和分析器绑定到一个 NuGet 包 Analyzer with Code Fix(带有代码修补程序的分析器)项目模板的 MSBuild 规则是自动生成包含编译分析器的 NuGet 包,你可将其发布到 NuGet 存储库与其他开发人员共享。事实上,每次你按下 F5 调试分析器或者每次你生成分析器项目时,Visual Studio 2015 都会重新生成分析器 .dll 文件(在当前示例中是 Feed­LibraryAnalyzer.dll),以及一个包含该分析器的可再发行的 NuGet 包。 在生成过程中同时生成的还有一个 VSIX 包,也可发布到 Visual Studio 库,它也能够在 Visual Studio 2015 的实验实例内调试分析器,但是此内容超出本文范围,在这里不过多讲述。 若想与集成的 Roslyn 分析共享库,则需要向生成项目时 Visual Studio 2015 生成的 NuGet 包添加库。执行此操作之前,你需要了解分析器的 NuGet 包的生成方式。事实上,NuGet 包是一个 .zip 存档,扩展名为 .nupkg。因此,你可以使用 .zip 存档工具轻松查看 NuGet 包的内容和结构,可用工具有 Windows 资源管理器压缩文件夹工具、WinZip 或 WinRar。下面总结了 NuGet 包中部署分析器时需要注意的重要事项: * .nuspec 文件: 该文件包含包元数据,并且包含发布所需的信息,比如包名称、版本、描述、作者以及许可 URL 等。.nuspec 文件绑定到 NuGet 包,它基于 Solution Explorer(解决方案资源管理器)中分析器项目内的 Diagnostic.nuspec 文件。你很快就可以在 Visual Studio 2015 中编辑 Diagnostic.nuspec。 * 工具文件夹: 该文件夹包含的 Windows PowerShell 脚本可由 Visual Studio 用来安装 (Install.ps1) 和卸载 (Uninstall.ps1) 指定项目的分析器。 * 分析器文件夹: 该文件夹包含可整理到特殊子文件夹中的分析器.dll 文件。无关的分析器库(面向所有语言)保存在子文件夹 dotnet 中。面向 C# 的分析器保存在 dotnet\cs 子文件中,而面向 Visual Basic 的分析器保存在 dotnet\vb 文件夹中。有必要提一下,dotnet 代表 .NET Core 的配置文件,支持通用 Windows 应用和 ASP.NET 5 项目之类的项目类型。 可以绑定到 NuGet 包的其他项目还有很多,但是这里我关注的是为 Roslyn 分析器生成的典型包,所以只讨论所需的项。 任何可以从 Visual Studio 项目引用的库都必须整理到 lib 文件夹中。由于库可以面向不同平台,比如不同版本的 .NET Framework、Windows 运行时、不同版本的 Windows Phone 甚至是可移植子集(包括 Xamarin 库),所以 lib 文件夹必须包含每个目标平台的子文件夹,而且每个子文件夹还必须包含要部署的库的副本。每个子文件夹的名称必须与表示特定平台的配置文件的名称匹配。例如,你有一个面向 .NET Framework 4.5.1 和 Windows 8.1 的库,那么你的结构如下:net451 表示 .NET Framework 4.5.1 的配置文件名称,而 netcore451 表示 Windows 8.1 中 Windows 运行时的配置文件名称: ~~~ lib\net451\mylib.dll lib\netcore451\mylib.dll ~~~ 值得一提的是,面向通用 Windows 平台 (UWP) 的 uap10.0 配置文件用来生成 Windows 10 应用。受支持配置文件的完整列表在 NuGet 文档中提供。之前创建的示例库是面向 .NET Framework 4.5.1、Windows 8.1 和 Windows Phone 8.1 的可移植库。这类目标的配置文件名称是 portable-net451+netcore451+wpa81,将在 NuGet 包中包含库的子文件夹必须使用此名称来命名。你无需手动创建子文件夹并复制库;你只需在 Visual Studio 内编辑 NuGet 包元数据(Diagnostic.nuspec 文件)即可。图 9 显示了更新后的元数据,其中包含有关发布的正确信息(ID、标题、作者、描述、许可等),还在文件节点中显示了一个新文件元素,用来指定源文件和目标子文件夹。 图 9 编辑 NuGet 包元数据 ~~~ <?xml version="1.0"?> <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">   <metadata>     <id>RSSFeedLibrary</id>     <version>1.0.0.0</version>     <title>RSS Feed Library with Roslyn analysis</title>     <authors>Alessandro Del Sole</authors>     <owners>Alessandro Del Sole</owners>     <licenseUrl>http://opensource.org/licenses/MIT</licenseUrl>     <!-- Removing these lines as they are not needed     <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl>     <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>-->     <requireLicenseAcceptance>true</requireLicenseAcceptance>     <description>Companion sample for the "Build and Deploy Libraries     with Integrated Roslyn Code Analysis to NuGet" article     on MSDN Magazine</description>     <releaseNotes>First release.</releaseNotes>     <copyright>Copyright 2015, Alessandro Del Sole</copyright>     <tags>RSS, analyzers, Roslyn</tags>     <frameworkAssemblies>       <frameworkAssembly assemblyName="System" targetFramework="" />     </frameworkAssemblies>   </metadata>   <files>     <file src="*.dll" target="analyzers\dotnet\cs"           exclude="**\Microsoft.CodeAnalysis.*;           **\System.Collections.Immutable.*;**\System.Reflection.Metadata.*;           **\System.Composition.*" />     <file src="tools\*.ps1" target="tools\" />     <file src="lib\FeedLibrary.dll" target="lib\portable-net451+netcore451+wpa81\"/>   </files> </package> ~~~ src 属性指示库的源位置,而 target 指定目标文件夹,它们都基于正确的配置文件名称。在这种情况下,Visual Studio 将在 lib 文件夹内搜索库 FeedLibrary.dll,你需要在当前目录中创建该文件夹。当前文件夹中包含编译分析器,通常是 Release 或 Debug 文件夹,根据所选生成配置的不同而不同。根据当前示例,你需要在 Release 文件夹内创建文件夹 lib,然后将 FeedLibrary.dll(通常在开始编译示例库时生成)复制到 lib 文件夹中。一旦完成此操作,你只需重新生成解决方案即可,Visual Studio 将生成更新的 NuGet 包,同时将库和 Roslyn 分析器包含在内。 值得注意的是,当你重新生成项目时,Visual Studio 2015 将自动更新 NuGet 包的内部版本号和修订版本号,不考虑版本标记提供的号。使用 .zip 存档工具打开更新的、最高版本的 NuGet 包,即可轻松查看 NuGet 包的更新内容。请记住: 如图 9 所示,每次你更改 id 元素的值时,Visual Studio 2015 都会根据新 id 生成一个不同名的 NuGet 包。在这种情况下,更改 RSSFeedLibrary 的 id 值会生成名为 RSSFeedLibrary.1.0.xxx.yyy.NuPkg 的 NuGet 包,其中 xxx 指内部版本号,yyy 指修订版本号,两个号都是生成时自动提供的。此时,你已经实现了第一个目标,即借助集成式 Roslyn 分析将自定义 API 封装到 NuGet 包。 或者,你也可以为分析器和库各创建(和发布)一个单独的包,另外再创建一个空 NuGet 包,将它们视为依赖项而存放在一起。通过这种方式,你可以选择仅使用 API 而不使用分析器以便减小安装大小,但是你需要熟悉 NuGet 约定才能从头开始手动创建包。向在线 NuGet 存储库发布新生成的包之前,最好本地测试一下。 ## 本地测试 NuGet 包 Visual Studio 2015 支持从本地存储库选取 NuGet 包。尤其当你缓存经常使用的包或者可能在脱机工作时使用的包时,这非常有用。注意,如果本地文件夹中的包很少,这种方法非常适用;但是如果有数百个包,则处理起来会非常复杂。为了测试之前生成的 NuGet 包,在磁盘上创建一个名为 LocalPackages 的新文件夹,然后将最新版本的 RSSFeed­Library.1.0.xxx.yyy.nupkg 文件复制到该文件夹中。下一步是让 Visual Studio 2015 从指定的本地文件夹选取包,如图 10 所示;在“Available package sources”(可用包源)框中,选择“Tools | Options | NuGet Package Manager | Package Sources”(工具 | 选项 | NuGet 包管理器 | 包源),然后单击“Add”(添加)按钮(绿色的加号)。在“Name”(名称)和“Source”(源)文本框中,分别键入 Local packages 和 C:\LocalPackages。最后,单击“Update”(更新)刷新包源的列表。 ![](https://box.kancloud.cn/2016-01-08_568f4ae7cb6ef.png) 图 10 将 Local Repository 添加为 NuGet Package Source 现在你有了自己的本地 NuGet 存储库,在 Visual Studio 2015 中创建新的控制台应用程序来测试库以及 Roslyn 分析包。保存项目,然后选择“Project | Manage NuGet Packages”(项目 | 管理 NuGet 包)。当“NuGet Package Manager”(NuGet 包管理)窗口出现时,在“Package source”(包源)组合框中选中“Local packages source”。此时,包管理器将显示指定存储库内可用包的列表,在本示例中只有示例包。 单击“安装”。在线推出 NuGet 包后,Visual Studio 将显示所选包的摘要信息,并且要求你接受许可协议。安装完成时,你可以在 Solution Explorer(解决方案资源管理器)中看到该库和 Roslyn 分析器,如图 11 所示。 ![](https://box.kancloud.cn/2016-01-08_568f4ae7e6cc3.png) 图 11 库和 Roslyn 分析器都通过 NuGet 包安装 如果你只是编写一些代码指定 FeedItem.ParseFeed­Async 方法将无效 URL 传递为自变量,则实时分析引擎将按预期报告警告消息(请参阅图 8 获取更多信息)。 安装分析器时,无论它是由你还是其他开发人员生成的,只要在 Solution Explorer (解决方案资源管理器)中展开“References”(引用)、“Analyzers”(分析器)以及分析器的名称,即可看到每条规则的详细信息。在本示例中,可以展开 FeedLibraryAnalyzer 的名称并查看 RSS URL 分析规则,如图 11所示。当你单击某个规则时,如果它在默认情况下启用,则 Properties (属性)窗口将显示默认值和有效严重性之类的详细信息,以及完整的规则描述。此外,可以使用 Ruleset Editor(规则集编辑器)查看适用于项目的所有规则、查看或更改规则的严重性,以及禁用或启用分析器和规则。在 Solution Explorer(解决方案资源管理器)中打开“Ruleset Editor”(规则集编辑器),双击“Properties”(属性),然后在项目的“Properties”(属性)窗口中选择“Code Analysis”(代码分析)选项卡,不更改默认规则集。 如图 11 所示,可以通过取消选中/选中规则代码旁边的复选框来禁用/启用规则;可以通过单击当前严重性级别右侧的黑色向下箭头来更改默认的严重性,也可通过右键单击 Solution Explorer(解决方案资源管理器)中的规则并从上下文菜单中选择“Set Rule Set Severity”(设置规则集严重性)来实现。如果你对本地测试满意,可以在线发布 NuGet 包。 ## 在线测试包 在 NuGet 上,开发人员希望获得更高质量、更专业的包。为此,向在线 NuGet 库发布包之前,应该通过在线服务来测试工作内容,它支持你创建私有 NuGet 存储库和源,并且在包稳定后升级到正式 NuGet 存储库。MyGet ([myget.org](http://myget.org/)) 提供了一项非常好的选择,其在线服务支持创建个人和企业 NuGet 源以及 VSIX、npm 和 Bower 源。MyGet 提供了免费使用计划,NuGet 包发布和使用所需的大部分功能都是免费的;这个免费计划不支持创建私有源(你需要为此付费),但是你可以利用它从在线存储库测试你的包是否如预期运行。当你注册时,你可以选择创建 NuGet 源。例如,我在 MyGet 上的公有源存放在 myget.org/F/­aledelsole/api/v2。MyGet 的用法不在本文范围内,但是本文详细说明了如何配置 NuGet 源。一旦创建了源并发布了包,只需让 Visual Studio 2015 从 MyGet 源选取 NuGet 包即可。为此,可以遵循上一节中描述的步骤,并参考图 10 来提供 MyGet 源的 URL。要下载并在 Visual Studio 中测试包,仍然需要遵循上一节中描述的步骤,在“NuGet Package Manager”(NuGet 包管理器)窗口中选择 MyGet 源作为源。 ## 将包发布到在线 NuGet 库 要将包发布到在线 NuGet 库,你需要打开 [nuget.org](http://nuget.org/),然而使用帐户登录。如果你还没有帐户,则单击页面右上角的“Register/Sign In”(注册/登录)超链接。可以使用 Microsoft 帐户(推荐)或用户名/密码凭据登录。注册后,单击“Upload Package”(上载包)。首先会要求你指定要上载的 NuGet 包,所以单击“Browse”(浏览)、从磁盘选择最新版本的 RSSFeed­Library.1.0.xxx.yyy.nupkg,然后单击“Upload”(上载)。 接下来会提示你输入包的元数据信息。此时,你可以先检查包详细信息,之后再向库发布,如图 12 所示。检查好后,单击“Submit”(提交)。这样,包含 API 和集成式 Roslyn 分析器的包就会发布到 NuGet 库。注意,这通常会占用 15 - 20 分钟时间,之后你才能在 Visual Studio 2015 的 NuGet Package Manager(NuGet 包管理器)窗口中看到包。 ![](https://box.kancloud.cn/2016-01-08_568f4ae814466.png) 图 12 发布前检查包详细信息 当包在库中列出后,你可以从在线 NuGet 存储库将它安装到项目中,如图 13 所示。安装后,你可以如上一节中所述对库执行 .NET 编译器平台的集成式、实时、特定于域的代码分析。 ![](https://box.kancloud.cn/2016-01-08_568f4ae82eb0c.png) 图 13 NuGet 包已在在线存储库中公开推出 ## 包更新 对于任何 NuGet 包,你都可以改善库并向 NuGet 发布更新的包版本。要创建更新的包,你只需重新生成解决方案即可,Visual Studio 2015 将自动更新包版本号。然后,重新生成项目并重复上一节中描述的步骤来发布在线 NuGet 包。NuGet 将管理每个包的可用版本的列表,它允许开发人员选择所需版本。 ## 总结 .NET 编译器平台的一大优势是你可以通过它为自己的 API 创建特定于域的代码分析规则。在本章中,我首先介绍了如何创建示例库,然后介绍了如何创建检测键入时代码问题的 Roslyn 分析器,具体针对库的成员。接下来,我介绍了如何将库和分析器绑定到一个 NuGet 包,这是本文的核心部分。学会了这部分内容,开发人员即可在下载 NuGet 包后在 API 上实现集成式 Roslyn 实时分析。然后,我介绍了如何在在线发布之前先本地测试 NuGet 包。这一步非常重要,因为你可以在将信息公布之前验证库和分析器是否正确运行。但是其他开发人员何时可以使用你的工作成果?我在本文的最后一部分讲述了如何将包发布到在线 NuGet 存储库,以及它后续如何从 Visual Studio 安装到项目中。可以肯定的说,部署 Roslyn 分析器和库能够提高你的工作价值,而且可以为其他开发人员提供更丰富的编码经验。 * * * Alessandro Del Sole *自 2008 年起被评为 Microsoft MVP。他已经 5 次获得年度 MVP 这一殊荣,发表过很多关于 Visual Studio .NET 开发的书籍、电子书、指导视频和文章。你可以关注他的 Twitter[@progalex](http://twitter.com/@progalex)。*