使用 C# 和 Blazor 进行全栈开发

Blazor 是将 C# 引入浏览器的 Microsoft 试验框架,正好可以填补欠缺的 C# 一环。如今,C# 程序员可以编写桌面、服务器端 Web、云、电话、平板电脑、手表、电视和 IoT 应用程序。


Blazor 填补了欠缺的一环,C# 开发人员现在可以直接在用户浏览器中共享代码和业务逻辑。对于 C# 开发人员来说,这是一项十分强大的功能,可显著提升工作效率。


本文将展示常见的代码共享用例。我将展示如何在 Blazor 客户端和 WebAPI 服务器应用程序之间共享验证逻辑。目前,你不仅要在服务器中验证输入,还要在客户端浏览器中验证输入。新式 Web 应用程序的用户希望获得准实时反馈。在填写长窗体并单击“提交”后仅看到红色错误返回的日子已经一去不复返了。


在浏览器中运行的 Blazor Web 应用程序可以与 C# 后端服务器共享代码。可以将逻辑放入共享库中,并在前端和后端使用它。这会带来很多好处。


可以将所有规则都集中放置在一处,并知道只需在一处更新它们。它们的工作方式确实相同,因为它们是相同的代码。


在客户端和服务器逻辑并不总是完全相同的情况下,可以节省大量测试和故障排除时间。


也许最值得一提的是,可以在客户端和服务器上使用一个库进行验证。


以前,JavaScript 前端强制开发人员编写两个版本的验证规则:一个是用适用于前端的 JavaScript 编写,另一个是用适用于后端的语言编写。若要尝试解决这种不匹配问题,需要涉及复杂的规则框架和额外的抽象层。使用 Blazor,可以在客户端和服务器上运行同一.NET Core 库。


虽然 Blazor 仍是试验框架,但它的进展迅速。生成此示例前,请先确保已安装正确版本的 Visual Studio、.NET Core SDK 和 Blazor 语言服务。有关入门步骤,请访问 blazor.net。

新建 Blazor 应用程序

首先,新建 Blazor 应用程序。


在“新建项目”对话框中,依次单击“ASP.NET Core Web 应用程序”和“确定”,再选择图 1 所示对话框中的“Blazor”图标。单击“确定”。


这会创建默认的 Blazor 示例应用程序。如果已试用过 Blazer,便会对此默认应用程序很熟悉。


640?wx_fmt=png

图 1:选择 Blazor 应用程序



新的注册窗体将展示验证业务规则的共享逻辑。图 2 展示了包含“名字”、“姓氏”、“电子邮件地址”和“电话”字段的简单窗体。


在此示例中,它会验证所有字段是否都为必填、姓名字段是否有长度上限,以及电子邮件地址和电话字段的格式是否正确。它会在每个字段下显示错误消息,这些消息会在用户键入内容的同时更新。最后,只有在没有错误的情况下,“注册”按钮才处于启用状态。


640?wx_fmt=png

图 2:注册窗体

共享库

所有需要在服务器和 Blazor 客户端之间共享的代码都位于一个独立的共享库项目中。共享库包含模型类和非常简单的验证引擎。模型类保留注册窗体中的数据字段。该命令如下所示:


public class RegistrationData : ModelBase
{
[RequiredRule]
[MaxLengthRule(50)]
public String FirstName { get; set; }
[RequiredRule]
[MaxLengthRule(50)]
public String LastName { get; set; }
[EmailRule]
public String Email { get; set; }
[PhoneRule]
public String Phone { get; set; }
}



RegistrationData 类继承自 ModelBase 类,后者包含所有可用于验证规则并返回绑定到 Blazor 页面的错误消息的逻辑。每个字段都使用映射到验证规则的属性进行修饰。我选择了创建非常简单的模型,它很像实体框架 (EF) 数据注释模型。此模型的所有逻辑都包含在共享库中。


ModelBase 类包含 Blazor 客户端应用程序或服务器应用程序可用来确定是否有任何验证错误的方法。它还会在此模型更改时触发事件,以便客户端能够更新 UI。任何模型类都可以继承自它,并自动获取所有验证引擎逻辑。


首先,我将在 SharedLibrary 项目中新建 ModelBase 类,如下所示:


public class ModelBase
{
}

错误和规则

现在,我将向 ModelBase 类添加包含验证错误列表的专用字典。_errors 字典先以字段名称为键,再以规则名称为键。值是要显示的实际错误消息。


通过此设置,可以轻松确定特定字段是否有验证错误,并快速检索错误消息。


代码如下:


private Dictionary<String, Dictionary<String, String>> _errors =
new Dictionary<string, Dictionary<string, string>>();



现在,我将添加 AddError 方法,以将错误输入内部错误字典。


AddError 有 fieldName、ruleName 和 errorText 参数。它先搜索内部错误字典,并删除已有条目,再添加新的错误条目,如下面的代码所示:


private void AddError(String fieldName, String ruleName, String errorText)
{
if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,
new Dictionary<string, string>()); }
if (_errors[fieldName].ContainsKey(ruleName))
{ _errors[fieldName].Remove(ruleName); }
_errors[fieldName].Add(ruleName, errorText);
OnModelChanged();
}



最后,我将添加 RemoveError 方法,它接受 fieldName 和 ruleName 参数,并在内部错误字典中搜索并删除匹配的错误。代码如下:


private void RemoveError(String fieldName, String ruleName)
{
if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,
new Dictionary<string, string>()); }
if (_errors[fieldName].ContainsKey(ruleName))
{ _errors[fieldName].Remove(ruleName);
OnModelChanged();
}
}



下一步是添加 CheckRules 函数,这些函数负责查找并执行附加到此模型的验证规则。有两种不同的 CheckRules 函数:一种是缺少参数,但对所有字段验证全部规则;另一种有 fieldName 参数,并仅验证特定字段。在字段更新时,使用的是第二种函数,并立即对此字段验证规则。


CheckRules 函数使用反射来查找附加到字段的属性列表。然后,它测试每个属性,以确定属性类型是否为 IModelRule。找到 IModelRule 后,它调用 Validate 方法,并返回结果,如图 3 所示。


图 3:CheckRules 函数


public void CheckRules(String fieldName)
{
var propertyInfo = this.GetType().GetProperty(fieldName);
var attrInfos = propertyInfo.GetCustomAttributes(true);
foreach (var attrInfo in attrInfos)
{
if (attrInfo is IModelRule modelrule)
{
var value = propertyInfo.GetValue(this);
var result = modelrule.Validate(fieldName, value);
if (result.IsValid)
{
RemoveError(fieldName, attrInfo.GetType().Name);
}
else
{
AddError(fieldName, attrInfo.GetType().Name, result.Message);
}
}
}
}
public bool CheckRules()
{
foreach (var propInfo in this.GetType().GetProperties(
System.Reflection.BindingFlags.Public |
System.Reflection.BindingFlags.Instance))
CheckRules(propInfo.Name);
return HasErrors();
}



接下来,我将添加 Errors 函数。此函数需要使用 fieldname 参数,并返回包含相应字段的错误列表的字符串。它使用内部 _errors 字典来确定相应字段是否有任何错误,如下所示:


public String Errors(String fieldName)
{
if (!_errors.ContainsKey(fieldName)) { _errors.Add(fieldName,
new Dictionary<string, string>()); }
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (var value in _errors[fieldName].Values)
sb.AppendLine(value);
return sb.ToString();
}



现在,我需要添加 HasErrors 函数,它会在此模型的任意字段有任何错误时返回 true。客户端使用此方法来确定是否应启用“注册”按钮。


另外,WebAPI 服务器也使用此方法来确定传入的模型数据是否有错误。此函数的代码如下:


public bool HasErrors()
{
foreach (var key in _errors.Keys)
if (_errors[key].Keys.Count > 0) { return true; }
return false;
}

值和事件

是时候添加 GetValue 方法了,它需要使用 fieldname 参数,并使用反射来查找此模型中的字段并返回字段值。Blazor 客户端使用此方法来检索当前值,并在输入框中显示它,如下所示:


public String GetValue(String fieldName)
{
var propertyInfo = this.GetType().GetProperty(fieldName);
var value = propertyInfo.GetValue(this);
if (value != null) { return value.ToString(); }
return String.Empty;
}



现在,添加 SetValue 方法。它使用反射来查找此模型中的字段,并更新字段值。然后,它触发 CheckRules 方法,以对相应字段验证所有规则。Blazor 客户端使用此方法,以在用户在输入文本框中键入内容的同时更新值。代码如下:


public void SetValue(String fieldName, object value)
{
var propertyInfo = this.GetType().GetProperty(fieldName);
propertyInfo.SetValue(this, value);
CheckRules(fieldName);
}



最后,我添加 ModelChanged 事件。如果此模型中的值已更改或在内部错误字典中添加或删除了验证规则,便会触发这个事件。Blazor 客户端侦听此事件,并在事件触发时更新 UI。正因为此,显示的错误会更新,如下面的代码所示:


public event EventHandler<EventArgs> ModelChanged;
protected void OnModelChanged()
{
  ModelChanged?.Invoke(this, new EventArgs());
}



得承认此验证引擎的设计非常简单,还有很多改进机会。在生产业务应用程序中,设置错误的严重性级别(如“信息”、“警告”和“错误”)会很有用。在某些情况下,如果无需修改代码,即可从配置文件动态加载规则,将会很有帮助。我不是在提倡创建你自己的验证引擎;只是有很多选择。此验证引擎既要足够好,以便演示实际示例;又要足够简单,以适应本文且易于理解。

创建规则

此时,有包含窗体字段的 RegistrationData 类。


此类中的字段使用 RequiredRule 和 EmailRule 等属性进行修饰。


RegistrationData 类继承自 ModelBase 类,后者包含所有用于验证规则并向客户端通知更改的逻辑。验证引擎的最后一部分是规则逻辑本身。接下来,我将对此进行探索。


首先,我在 SharedLibrary 中新建 IModelRule 类。


此规则由一个返回 ValidationResult 的 Validate 方法组成。每个规则都必须实现 IModelRule 接口,如下所示:


public interface IModelRule
{
ValidationResult Validate(String fieldName, object fieldValue);
}



接下来,我在 SharedLibrary 中新建 ValidationResult 类,它由两个字段组成。IsValid 字段指明规则是否有效,而 Message 字段则包含要在规则无效时显示的错误消息。代码如下所示:


public class ValidationResult
{
public bool IsValid { get; set; }
public String Message { get; set; }
}



示例应用程序使用四个不同的规则,所有规则都是继承自 Attribute 类并实现 IModelRule 接口的公共类。


现在,是时候创建规则了。请注意,所有验证规则都只是继承自 Attribute 类并实现 IModelRule 接口的 Validate 方法的类。如果输入的文本超过指定的长度上限,图 4 中的长度上限规则返回错误。其他用于验证必填字段、电话和电子邮件地址字段格式的规则的工作方式类似,区别在于它们对要验证的数据类型采用不同的逻辑。


图 4:MaxLengthRule 类


public class MaxLengthRule : Attribute, IModelRule
{
private int _maxLength = 0;
public MaxLengthRule(int maxLength) { _maxLength = maxLength; }
public ValidationResult Validate(string fieldName, object fieldValue)
{
var message = $"Cannot be longer than {_maxLength} characters";
if (fieldValue == null) { return new ValidationResult() { IsValid = true }; }
var stringvalue = fieldValue.ToString();
if (stringvalue.Length > _maxLength )
{
return new ValidationResult() { IsValid = false, Message = message };
}
else
{
return new ValidationResult() { IsValid = true };
}
}
}

创建 Blazor 注册窗体

至此,验证引擎已在共享库中完成,它可以应用于 Blazor 应用程序中的新注册窗体。首先,我在 Blazor 应用程序中添加对共享库项目的引用。为此,可使用“引用管理器”对话框的“解决方案”窗口,如图 5 所示。


640?wx_fmt=png

图 5:添加对共享库的引用


接下来,我向应用程序的 NavMenu 添加新导航链接。

打开SharedNavMenu.cshtml 文件,并向列表添加新注册窗体链接如图 6 所示。


图 6:添加注册窗体链接


<div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="" Match=NavLinkMatch.All>
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="registrationform">
<span class="oi oi-list-rich" aria-hidden="true"></span> Registration Form
</NavLink>
</li>
</ul>
</div>



最后,我在 Pages 文件夹中添加新 RegistrationForm.cshtml 文件。为此,可使用图 7 中的代码。


图 7 中的 cshtml 代码在 <form> 标记内有四个 <TextInput> 字段。<TextInput> 标记是自定义 Blazor 组件,用于处理字段的数据绑定和错误显示逻辑。此组件只需要三个参数即可正常运行:


  • Model 字段:标识数据要绑定到的类。

  • FieldName:标识数据要绑定到的数据成员。

  • DisplayName 字段:让组件可以显示易记消息。


图 7:添加 RegistrationForm.cshtml 文件


@page "/registrationform"
@inject HttpClient Http
@using SharedLibrary
<h1>Registration Form</h1>
@if (!registrationComplete)
{
<form>
<div class="form-group">
<TextInput Model="model" FieldName="FirstName" DisplayName="First Name" />
</div>
<div class="form-group">

<TextInput Model="model" FieldName="LastName" DisplayName="Last Name" />

</div>
<div class="form-group">
<TextInput Model="model" FieldName="Email" DisplayName="Email" />
</div>
<div class="form-group">
<TextInput Model="model" FieldName="Phone" DisplayName="Phone" />
</div>
<button type="button" class="btn btn-primary" onclick="@Register"
disabled="@model.HasErrors()">Register</button>
</form>
}
else
{
  <h2>Registration Complete!</h2>
}
@functions {
bool registrationComplete = false;
RegistrationData model { get; set; }
protected override void OnInit()
{
base.OnInit();
model = new RegistrationData() { FirstName =
"test", LastName = "test", Email = "test@test.com", Phone = "1234567890" };
model.ModelChanged += ModelChanged;
model.CheckRules();
}
private void ModelChanged(object sender, EventArgs e)
{
base.StateHasChanged();
}
async Task Register()
{
await Http.PostJsonAsync<RegistrationData>(
"https://localhost:44332/api/Registration", model);
registrationComplete = true;
}
}



在页面的 @functions 块内,代码只有一点点。OnInit 方法使用其中的一些测试数据来初始化模型类。


它绑定到 ModelChanged 事件,并调用 CheckRules 方法来验证规则。


ModelChanged 处理程序调用 base.StateHasChanged 方法,以强制执行 UI 刷新。Register 方法在“注册”按钮获得单击时调用,并将注册数据发送到后端WebAPI 服务。


TextInput 组件包含输入标签、输入文本框、验证错误消息,以及在用户键入内容的同时更新模型的逻辑。Blazor 组件非常易于编写,并提供了将接口分解为可重用部分的强大方法。参数成员使用 Parameter 属性进行修饰,以便让 Blazor 知道它们是组件参数。


输入文本框的 oninput 事件连接到 OnFieldChanged 处理程序。每当输入更改,都会触发此事件。然后,OnFieldChanged 处理程序调用 SetValue 方法,以对相应字段执行规则,并在用户键入内容的同时实时更新错误消息。图 8 展示了代码。


图 8:更新错误消息


@using SharedLibrary
<label>@DisplayName</label>
<input type="text" class="form-control" placeholder="@DisplayName"
oninput="@(e => OnFieldChanged(e.Value))"
value="@Model.GetValue(FieldName)" />
<small class="form-text" style="color:darkred;">@Model.Errors(FieldName)
</small>
@functions {
[Parameter]
ModelBase Model { get; set; }
[Parameter]
String FieldName { get; set; }
[Parameter]
String DisplayName { get; set; }
public void OnFieldChanged(object value)
{
Model.SetValue(FieldName, value);
}
}

服务器上的验证

验证引擎现已开始在客户端上运行。下一步是在服务器上使用共享库和验证引擎。为此,我先向解决方案添加另一个 ASP.NET Core Web 应用程序项目。这次,我在图 1 所示的“新建 ASP.NET Core Web 应用程序”对话框中选择的是“API”,而不是“Blazor”。


新建 API 项目后,我就添加对共享项目的引用,就像在 Blazor 客户端应用程序中(见图 5)一样。接下来,我向 API 项目添加新控制器。


新控制器接受来自 Blazor 客户端的 RegistrationData 调用,如图 9 所示。注册控制器在服务器上运行,并且是后端 API 服务器的典型特征。区别在于,它现在运行在客户端上运行的相同验证规则。


图 9:注册控制器


[Route("api/Registration")]
[ApiController]
public class RegistrationController : ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] RegistrationData value)
{
if (value.HasErrors())
{
return BadRequest();
}
// TODO: Save data to database
return Created("api/registration", value);
}
}



注册控制器有一个 POST 方法,它接受 RegistrationData 作为自己的值。它调用 HasErrors 方法,以验证所有规则并返回布尔值。


若有错误,控制器返回 BadRequest 响应;否则,它返回成功响应。我特意省略掉了将注册数据保存到数据库的代码,这样我就可以验证方案为重点了。


现在,共享验证逻辑在客户端和服务器上运行。

此简单示例展示了如何在浏览器和后端之间共享验证逻辑,仅仅触及全栈 C# 环境强大功能的皮毛。


Blazor 的神奇之处在于,使用它,现有 C# 开发人员大军可以生成功能强大的新式响应式单页应用程序,且最大限度地缩短启动时间。使用它,企业可以重用和重新打包现有代码,以便能够直接在浏览器中运行现有代码。


能够在浏览器、桌面、服务器、云和移动平台之间共享 C# 代码,将大大提升开发人员的工作效率。它还便于开发人员更快地向客户交付更多功能和更多业务价值。

原文地址:https://msdn.microsoft.com/zh-cn/magazine/mt833288

 
 

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 
640?wx_fmt=jpeg

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CLR.via.C#.(中文第3版)(自制详细书签)Part2 CLR via C#(第3版) Jeffrey Richter 著 周靖 译 出版时间:2010年09月 页数:800 介绍 享有全球盛誉的编程专家Jeffrey Richter,这位与Microsoft .NET开发团队合作长达8年时间的资深顾问,在本书中和读者分享他编程生涯中积累的所有丰富经验和心得,他的独到、睿智的见解,他的远见卓识,为开发人员构建健壮、可靠和具有良好响应能力的应用程序与组件奠定了良好的基础。 《CLR via C#(第3版) 》针对.NET Framework 4.0和多核编程进行了全面更新和修订,是帮助读者深入探索和掌握公共语言运行时、C#和.NET开发的重要参考,同时也是帮助开发人员构建任何一种应用程序(如Microsoft Silverlight、ASP.NET、Windows Prensentation Foundation、Web服务和控制台应用程序)的良师益友。 本书涵括以下主题: · 构建、部署应用程序、组件和共享程序集,并对它们进行版本管理 · 理解基元类型、值类型和引用类型的行为,从而最高效地定义和使用它们 · 使用泛型和接口来定义可重用的算法 · 高效使用特定的CLR类型——委托、枚举、定制attribute、数组和字符串 · 理解垃圾回收器是如何管理内存资源的 · 使用线程池、任务、取消、计时器和异步I/O操作来设计响应性强、稳定性高和伸缩性大的解决方案 · 借助于异常处理来进行状态管理 · 使用CLR寄宿、AppDomain、程序集加载、反射和C#的dynamic类型来构造具有动态扩展能力的应用程序 本书作者作者Jeffrey Richter,.NET和Windows编程领域当之无愧的大师和权威,以著述清楚明了,行文流水,言简意赅著称,在国内具有相当高的知名度,他的著作之一《Windows核心编程(第5版)》屡获殊荣,在国内外都享有盛誉,在国内因年销量过万而获得中国书刊业发行协会“2009年度全行业畅销书品种”称号。 目录 第1章 CLR的执行模型 1.1 将源代码编译成托管模块 1.2 将托管模块合并成程序集 1.3 加载公共语言运行时 1.4 执行程序集的代码 1.4.1 IL和验证 1.4.2 不安全的代码 1.5 本地代码生成器:NGen.exe 1.6 Framework类库 1.7 通用类型系统 1.8 公共语言规范 1.9 与非托管代码的互操作性 第2章 生成、打包、部署和管理应用程序及类型 2.1 .NET Framework部署目标 2.2 将类型生成到模块中 2.2.1 响应文件 2.3 元数据概述 2.4 将模块合并成程序集 2.4.1 使用Visual Studio IDE将程序集添加到项目中 2.4.2 使用程序集链接器 2.4.3 为程序集添加资源文件 2.5 程序集版本资源信息 2.5.1 版本号 2.6 语言文化 2.7 简单应用程序部署(私有部署的程序集) 2.8 简单管理控制(配置) 第3章 共享程序集和强命名程序集 3.1 两种程序集,两种部署 3.2 为程序集分配强名称 3.3 全局程序集缓存 3.4 在生成的程序集中引用一个强命名程序集 3.5 强命名程序集能防范篡改 3.6 延迟签名 3.7 私有部署强命名程序集 3.8 “运行时”如何解析类型引用 3.9 高级管理控制(配置) 3.9.1 发布者策略控制 第4章 类 型 基 础 4.1 所有类型都从System.Object派生 4.2 类型转换 4.2.1 使用C#的is和as操作符来转型 4.3 命名空间和程序集 4.4 运行时的相互联系 第5章 基元类型、引用类型和值类型 5.1 编程语言的基元类型 5.1.1 checked和unchecked基元类型操作 5.2 引用类型和值类型 5.3 值类型的装箱和拆箱 5.3.1 使用接口更改已装箱值类型中的字段(以及为什么不应该这样做) 5.3.2 对象相等性和同一性 5.4 对象哈希码 5.5 dynamic基元类型 第6章 类型和成员基础 6.1 类型的各种成员 6.2 类型的可见性 6.2.1 友元程序集 6.3 成员的可访问性 6.4 静态类 6.5 分部类、结构和接口 6.6 组件、多态和版本控制 6.6.1 CLR如何调用虚方法、属性和事件 6.6.2 合理使用类型的可见性和成员的可访问性 6.6.3 对类型进行版本控制时的虚方法的处理 第7章 常量和字段 7.1 常量 7.2 字段 第8章 方法 8.1 实例构造器和类(引用类型) 8.2 实例构造器和结构(值类型) 8.3 类型构造器 8.3.1 类型构造器的性能 8.4 操作符重载方法 8.4.1 操作符和编程语言互操作性 8.5 转换操作符方法 8.6 扩展方法 8.6.1 规则和原则 8.6.2 用扩展方法扩展各种类型 8.6.3 ExtensionAttribute 8.7 分部方法 8.7.1 规则和原则 第9章 参 数 9.1 可选参数和命名参数 9.1.1 规则和原则 9.1.2 DefaultParameterValueAttribute和OptionalAttribute 9.2 隐式类型的局部变量 9.3 以传引用的方式向方法传递参数 9.4 向方法传递可变数量的参数 9.5 参数和返回类型的指导原则 9.6 常量性 第10章 属性 10.1 无参属性 10.1.1 自动实现的属性 10.1.2 合理定义属性 10.1.3 对象和集合初始化器 10.1.4 匿名类型 10.1.5 System.Tuple类型 10.2 有参属性 10.3 调用属性访问器方法时的性能 10.4 属性访问器的可访问性 10.5 泛型属性访问器方法 第11章 事件 11.1 设计要公开事件的类型 11.1.1 第一步:定义类型来容纳所有需要发送给事件通知接收者的附加信息 11.1.2 第二步:定义事件成员 11.1.3 第三步:定义负责引发事件的方法来通知事件的登记对象 11.1.4 第四步:定义方法将输入转化为期望事件 11.2 编译器如何实现事件 11.3 设计侦听事件的类型 11.4 显式实现事件 第12章 泛型 12.1 Framework类库中的泛型 12.2 Wintellect的Power Collections库 12.3 泛型基础结构 12.3.1 开放和封闭类型 12.3.2 泛型类型和继承 12.3.3 泛型类型同一性 12.3.4 代码爆炸 12.4 泛型接口 12.5 泛型委托 12.6 委托和接口的逆变和协变泛型类型实参 12.7 泛型方法 12.7.1 泛型方法和类型推断 12.8 泛型和其他成员 12.9 可验证性和约束 12.9.1 主要约束 12.9.2 次要约束 12.9.3 构造器约束 12.9.4 其他可验证性问题 第13章 接口 13.1 类和接口继承 13.2 定义接口 13.3 继承接口 13.4 关于调用接口方法的更多探讨 13.5 隐式和显式接口方法实现(幕后发生的事情) 13.6 泛型接口 13.7 泛型和接口约束 13.8 实现多个具有相同方法名和签名的接口 13.9 用显式接口方法实现来增强编译时类型安全性 13.10 谨慎使用显式接口方法实现 13.11 设计:基类还是接口? 第14章 字符、字符串和文本处理 14.1 字符 14.2 System.String类型 14.2.1 构造字符串 14.2.2 字符串是不可变的 14.2.3 比较字符串 14.2.4 字符串留用 14.2.5 字符串池 14.2.6 检查字符串中的字符和文本元素 14.2.7 其他字符串操作 14.3 高效率构造字符串 14.3.1 构造StringBuilder对象 14.3.2 StringBuilder的成员 14.4 获取对象的字符串表示:ToString 14.4.1 指定具体的格式和语言文化 14.4.2 将多个对象格式成一个字符串 14.4.3 提供定制格式化器 14.5 解析字符串来获取对象:Parse 14.6 编码:字符和字节的相互转换 14.6.1 字符和字节流的编码和解码 14.6.2 Base-64字符串编码和解码 14.7 安全字符串 第15章 枚举类型和位标志 15.1 枚举类型 15.2 位标志 15.3 向枚举类型添加方法 第16章 数组 16.1 初始化数组元素 16.2 数组转型 16.3 所有数组都隐式派生自System.Array 16.4 所有数组都隐式实现IEnumerable,Icollection和IList 16.5 数组的传递和返回 16.6 创建下限非0的数组 16.7 数组的访问性能 16.8 不安全的数组访问和固定大小的数组 第17章 委托 17.1 初识委托 17.2 用委托回调静态方法 17.3 用委托回调实例方法 17.4 委托揭秘 17.5 用委托回调许多方法(委托链) 17.5.1 C#对委托链的支持 17.5.2 取得对委托链调用的更多控制 17.6 委托定义太多啦(泛型委托) 17.7 C#为委托提供的简化语法 17.7.1 简化语法1:不需要构造委托对象 17.7.2 简化语法2:不需要定义回调方法 17.7.3 简化语法3:局部变量不需要手动包装到类中即可传给回调方法 17.8 委托和反射 第18章 定制attribute 18.1 使用定制attribute 18.2 定义自己的attribute类 18.3 attribute的构造器和字段/属性的数据类型 18.4 检测定制attribute 18.5 两个attribute实例的相互匹配 18.6 检测定制attribute时不创建从Attribute派生的对象 18.7 条件attribute类 第19章 可空值类型 19.1 C#对可空值类型的支持 19.2 C#的空接合操作符 19.3 CLR对可空值类型的特殊支持 19.3.1 可空值类型的装箱 19.3.2 可空值类型的拆箱 19.3.3 通过可空值类型调用GetType 19.3.4 通过可空值类型调用接口方法 第20章 异常和状态管理 20.1 定义“异常” 20.2 异常处理机制 20.2.1 try块 20.2.2 catch块 20.2.3 finally块 20.3 System.Exception类 20.4 FCL定义的异常类 20.5 抛出异常 20.6 定义自己的异常类 20.7 用可靠性换取开发效率 20.8 指导原则和最佳实践 20.8.1 善用finally块 20.8.2 不要什么都捕捉 20.8.3 得体地从异常中恢复 20.8.4 发生不可恢复的异常时回滚部分完成的操作——维持状态 20.8.5 隐藏实现细节来维系契约 20.9 未处理的异常 20.10 对异常进行调试 20.11 异常处理的性能问题 20.12 约束执行区域(CER) 20.13 代码契约 第21章 自动内存管理(垃圾回收) 21.1 理解垃圾回收平台的基本工作原理 21.1.1 从托管堆分配资源 21.2 垃圾回收算法 21.3 垃圾回收与调试 21.4 使用终结操作来释放本地资源 21.4.1 使用CriticalFinalizerObject类型确保终结 21.4.2 SafeHandle类型及其派生类型 21.4.3 使用SafeHandle类型与非托管代码进行互操作 21.5 对托管资源使用终结操作 21.6 什么会导致调用Finalize方法 21.7 终结揭秘 21.8 Dispose模式:强制对象清理资源 21.9 使用实现了Dispose模式的类型 21.10 C#的using语句 21.11 一个有趣的依赖性问题 21.12 手动监视和控制对象的生存期 21.13 对象复活 21.14 代 21.15 用于本地资源的其他垃圾回收功能 21.16 预测需求大量内存的操作能否成功 21.17 编程控制垃圾回收器 21.18 线程劫持 21.19 垃圾回收模式 21.20 大对象 21.21 监视垃圾回收 第22章 CLR寄宿和AppDomain 22.1 CLR寄宿 22.2 AppDomain 22.2.1 跨越AppDomain边界访问对象 22.3 卸载AppDomain 22.4 监视AppDomain 22.5 AppDomain FirstChance异常通知 22.6 宿主如何使用AppDomain 22.6.1 可执行应用程序 22.6.2 Microsoft Silverlight富Internet应用程序 22.6.3 Microsoft ASP.NET Web窗体和XML Web服务应用程序 22.6.4 Microsoft SQL Server 22.6.5 更多的用法只局限于你自己的想象力 22.7 高级宿主控制 22.7.1 使用托管代码管理CLR 22.7.2 编写健壮的宿主应用程序 22.7.3 宿主如何拿回它的线程 第23章 程序集加载和反射 23.1 程序集加载 23.2 使用反射构建动态可扩展应用程序 23.3 反射的性能 23.3.1 发现程序集中定义的类型 23.3.2 类型对象的准确含义 23.3.3 构建Exception派生类型的一个层次结构 23.3.4 构造类型的实例 23.4 设计支持加载项的应用程序 23.5 使用反射发现类型的成员 23.5.1 发现类型成员 23.5.2 BindingFlags:筛选返回的成员种类 23.5.3 发现类型的接口 23.5.4 调用类型的成员 23.5.5 一次绑定、多次调用 23.5.6 使用绑定句柄来减少进程的内存耗用 第24章 运行时序列化 24.1 序列化/反序列化快速入门 24.2 使类型可序列化 24.3 控制序列化和反序列化 24.4 格式化器如何序列化类型实例 24.5 控制序列化/反序列化的数据 24.5.1 如何在基类没有实现ISerializable的前提下定义一个实现它的类型 24.6 流上下文 24.7 将类型序列化为不同的类型以及将对象反序列化为不同的对象 24.8 序列化代理 24.8.1 代理选择器链 24.9 反序列化对象时重写程序集和/或类型 第25章 线程基础 25.1 Windows为什么要支持线程 25.2 线程开销 25.3 停止疯狂 25.4 CPU发展趋势 25.5 NUMA架构的机器 25.6 CLR线程和Windows线程 25.7 使用专用线程执行异步的计算限制操作 25.8 使用线程的理由 25.9 线程调度和优先级 25.10 前台线程和后台线程 25.11 继续学习 第26章 计算限制的异步操作 26.1 CLR线程池基础 26.2 执行简单的计算限制操作 26.3 执行上下文 26.4 协作式取消 26.5 任务 26.5.1 等待任务完成并获取它的结果 26.5.2 取消任务 26.5.3 一个任务完成时自动启动一个新任务 26.5.4 任务可以启动子任务 26.5.5 任务内部揭秘 26.5.6 任务工厂 26.5.7 任务调度器 26.6 Parallel的静态For,ForEach和Invoke方法 26.7 并行语言集成查询(PLINQ) 26.8 执行定时计算限制操作 26.8.1 太多的计时器,太少的时间 26.9 线程池如何管理线程 26.9.1 设置线程池限制 26.9.2 如何管理工作者线程 26.10 缓存线和伪共享 第27章 I/O限制的异步操作 27.1 Windows如何执行I/O操作 27.2 CLR的异步编程模型(APM) 27.3 AsyncEnumerator类 27.4 APM和异常 27.5 应用程序及其线程处理模型 27.6 异步实现服务器 27.7 APM和计算限制操作 27.8 APM的注意事项 27.8.1 在没有线程池的前提下使用APM 27.8.2 总是调用EndXxx方法,而且只调用一次 27.8.3 调用EndXxx方法时总是使用相同的对象 27.8.4 为BeginXxx和EndXxx方法使用ref,out和params实参 27.8.5 不能取消异步I/O限制操作 27.8.6 内存消耗 27.8.7 有的I/O操作必须同步完成 27.8.8 FileStream特有的问题 27.9 I/O请求优先级 27.10 将IAsyncResult APM转换为Task 27.11 基于事件的异步模式 27.11.1 将EAP转换为Task 27.11.2 APM和EAP的对比 27.12 编程模型的泥沼 第28章 基元线程同步构造 28.1 类库和线程安全 28.2 基元用户模式和内核模式构造 28.3 用户模式构造 28.3.1 易失构造 28.3.2 互锁构造 28.3.3 实现简单的Spin Lock 28.3.4 Interlocked Anything模式 28.4 内核模式构造 28.4.1 Event构造 28.4.2 Semaphore构造 28.4.3 Mutex构造 28.4.4 在一个内核构造可用时调用一个方法 第29章 混合线程同步构造 29.1 一个简单的混合锁 29.2 自旋、线程所有权和递归 29.3 混合构造的大杂烩 29.3.1 ManualResetEventSlim和SemaphoreSlim类 29.3.2 Monitor类和同步块 29.3.3 ReaderWriterLockSlim类 29.3.4 OneManyLock类 29.3.5 CountdownEvent类 29.3.6 Barrier类 29.3.7 线程同步构造小结 29.4 著名的双检锁技术 29.5 条件变量模式 29.6 用集合防止占有锁太长的时间 29.7 并发集合类

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值