12
7

本文要点
WebAssembly 是一种新的客户端技术,可以在所有现代浏览器(包括移动浏览器)中实现近乎原生的性能,而且不需要插件。
许多语言,包括 C、C#、Go 和 Rust,都可以编译成面向基于栈的 WebAssembly 虚拟机的代码。
.NET 代码可以在任何地方运行,包括浏览器内部。
Blazor 是一个客户端库,它在 WebAssembly 上使用.NET 来支持借助 Razor 模板使用 C# 编写的单页应用程序。
Blazor 支持代码重用和将遗留代码移植到现代 Web 应用程序的能力。
在 2019 年 4 月中旬,微软悄悄地推出了一个年轻的框架,从“一切皆有可能”的实验阶段过渡到“我们致力于实现这一目标”的预览版。这个框架名为Blazor,因为它在浏览器中运行,并利用了一个名为 Razor 的模板系统或“视图引擎”,促成了这个.NET 开发人员几乎放弃了的场景。它不仅允许开发人员使用 C# 构建客户端代码(不需要 JavaScript),还允许开发人员在没有插件的情况下在浏览器中运行现有的.NET 标准 DLL。

Blazor 有两种托管模式。本文主要关注客户端版本。你可以阅读“Blazor 服务器端托管模型”了解更多关于服务器端版本的信息。

Silverlight 的希望
在任何地方运行.NET 的梦想始于 2006 年,当时有一个名为“Windows Presentation Foundation/Everywhere(WPF/E)”的应用程序框架以 Silverlight 的形式向公众发布。第一个版本支持通过 WPF 引入的声明性用户界面,即可扩展应用程序标记语言(Extensible Application Markup Language,简称 XAML)。该平台提供了对 UI 元素的细粒度控制,并提供了自己的文档对象模型(DOM),可以通过 JavaScript 访问。

当 Silverlight 2 在 2008 年发布时,它通过一个作为浏览器插件运行的公共语言运行时(CLR)实现.NET 的完全支持,从而加快了采用速度。开发人员可以使用任何.NET 语言来构建 Web 应用程序,利用成熟的数据绑定模式,如 Model-View-ViewMode(MVVM),并使用 REST 或 Windows Communication Foundation(WCF)客户端与 Web API 通信。看起来,.NET 开发人员可以摆脱 JavaScript 的束缚,不用再担心跨浏览器测试,而是专注于一个具有公共代码库的平台来交付他们的应用程序。

Silverlight 开发者不知道的是,2007 年对于这个平台来说是艰难的一年。两个看似不相干的事件发生了,最终导致了它的灭亡。首先,Web 超文本应用技术工作组(WHATWG)和万维网联盟(W3C)之间开始着手合作编写将于 2008 年发布的 HTML5 规范初稿。

第二,2007 年 6 月 29 日,苹果发布了 iPhone。

每隔一段时间,我们就会有一件革命性的产品横空出世,并彻底改变一切。
——史蒂夫•乔布斯

比赛开始了。手机几乎是在一夜之间从带有联系人列表的翻盖手机发展到带有游戏和内置网络浏览器的便携式电脑。在很短的一段时间内,Silverlight 的未来似乎充满了希望。微软对 iPhone 的回应是 Windows Phone 7,支持以 Silverlight 作为开发平台。Chrome 支持即将到来。如果微软能够找到一种将 Silverlight 应用到 iPhone 和 Android 手机上的方法,那么“一次编写,到处运行”的圣杯将最终被发现。

只是,它没有找到。

出于许多原因,包括运行“浏览器中的虚拟机”的安全性考虑,以及潜在的电池消耗,通向浏览器插件的大门砰地关上了,特别是在移动设备上。业界开始期待 HTML5 在打造移动体验方面的前景。微软改变了自己的关注点,到 2011 年 Silverlight 5 发布时,大多数开发人员已经看到了不祥之兆:不会再有新版本了。

HTML5 和 JavaScript 继续赢得 Web 开发人员的青睐。jQuery 等工具对 DOM 进行了标准化,使构建多浏览器应用程序变得更加容易,同时,浏览器引擎开始采用通用的 DOM 标准,使“一次构建,到处运行”变得更加容易。Angular、React 和 Vue.js 等前端框架的爆炸式增长使单页应用程序(SPA)成为主流,并巩固了 JavaScript 作为浏览器操作系统首选语言的地位。

JavaScript 即平台
2013 年 3 月,asm.js正式推出。其文档把它描述为JavaScript 的一个严格子集,可以用作编译器的一种低级、高效的目标语言。该规范本质上定义了一组 JavaScript 约定,这些约定使通过提前编译优化代码成为可能,并提供了强类型(JavaScript 本身是一种动态语言)和基于堆的内存模型。

Asm.js 的推出使将 C/C++ 代码编译成 JavaScript 成为可能,从而开启了一个新的可能性领域。对约定的限制使得“支持 asm.js”的引擎可以有效地将 JavaScript 编译成高性能的本地代码。为了更好地理解这是如何实现的,请考虑下面的 C 代码片段:

int find(char *buf, char test) {
char *cur = buf;
while (*cur != 0 && *cur != test) {
cur++;
}
if (*cur == 0) {
return -1;
}
return (cur - buf);
}
该代码有效地扫描一个字符串,寻找标记其结束的测试字符或零字节,并计算偏移量。C++ 已经可以使用一个名为Clang的工具编译成字节码,该工具与LLVM 工具链兼容。LLVM 是一组支持快速跨平台编译代码的技术。一个名为Emscripten的项目利用该工具链来生成 asm.js。

使用 Emscripten 编译 C++ 代码可以生成几十行高度优化的 JavaScript。以下代码经过简化,用于说明生成的内容:

function find(buf, test) {
buf = buf|0;
var cur = buf|0;
var result = -1|0;
while (1) {
var check = HEAP8[cur>>0]|0;
var foundZero = (check) === (0);
if (foundZero) {
break;
}
var foundTest = (check) === (test|0);
if (foundTest) {
result = (cur - buf)|0;
break;
}
}
return result|0;
}
生成的 JavaScript 与所有浏览器兼容并且运行良好。带零操作的异或(|0)可以很容易地将任何数字转换为带符号整数。在较老的浏览器中,这可以确保数字没有小数部分。在现代浏览器中,该约定可以提前通知编译器使用 32 位整数(使数学运算更快),而不是默认的 64 位浮点值。0 右移(>>0)可以防止溢出,它还声明了一个“索引”整数类型,该类型在 HEAP8 上迭代,而 HEAP8 是一个可以在 asm.js 中使用的类型化的字节缓冲区。

在 asm.js 中没有定义 for 循环。所有内容都被转换为 while(1) 循环。这使得应用编译器优化变得更容易。这些优化非常有效,以至于一个团队能够将 Unreal 4 引擎移植到 Web 浏览器中,以近乎原生的性能直接运行 3D 第一人称视角游戏。

WebAssembly:新希望
时间很快来到 2017 年,WebAssembly发布了,这是一种基于栈的虚拟机的二进制指令格式。WebAssembly 提供了一个可移植的编译目标(简称 Wasm),与 asm.js 相比,它有几个优点:

作为字节码格式,不需要解析脚本和预编译来进行优化。代码可以直接翻译成本机指令。与 asm.js 相比,加载和开始执行代码的启动时间要快几个数量级。
字节码格式是一种更紧凑的代码交付方式。
Wasm 实现了自己的指令集,因此不受 JavaScript 语言的限制。
任何编译成 asm.js 的代码都可以把 WebAssembly 作为目标。对于前面的示例,编译器标志的一个简单修改就会生成一个扩展名为.wasm 的文件。该文件只有 116 字节长。尽管该文件包含字节码,但也存在代码的标准化文本表示形式,名为WebAssembly 文本格式。这是 WebAssembly 中 find 模块的文本表示:

(module
(type $t0 (func (param i32 i32) (result i32)))
(import "env" "memory" (memory $env.memory 256 256))
(func $a (type $t0) (param $p0 i32) (param $p1 i32) (result i32)
(local $l0 i32) (local $l1 i32) (local $l2 i32) (local $l3 i32)
get_local $p0
set_local $l0
loop $L0
get_local $l0
i32.load8_s
tee_local $l2
i32.eqz
set_local $l1
get_local $l0
i32.const 1
i32.add
set_local $l3
get_local $l1
i32.const 1
i32.xor
get_local $p1
i32.const 24
i32.shl
i32.const 24
i32.shr_s
get_local $l2
i32.ne
i32.and
if $I1
get_local $l3
set_local $l0
br $L0
end
end
i32.const -1
get_local $l0
get_local $p0
i32.sub
get_local $l1
select)
代码大小进行了优化,因此函数被重命名为了 a。

WebAssembly 现在是 1.x 稳定版,支持所有的现代浏览器,包括手机。若干语言都以 Wasm 作为有效的编译目标。你可以使用 C、C++、Go、Rust、TypeScript 和许多其他语言来构建 WebAssembly 程序。它已经应用于计算机视觉、音频混合、视频编解码器支持、数字信号处理、医学成像、物理模拟、加密、压缩等解决方案中。

但是 C# 呢?

在 WebAssembly 推出之后,将. NET 框架的工作版本(包括它的公共语言运行时)移植到 WebAssembly 上运行的工作就立即开始了。

这一努力取得了成功。

浏览器和 Razor 视图引擎
2017 年底,微软软件工程师 Steve Sanderson 在他的个人博客上宣布了 Blazor 的消息。当时,它“只是一个实验”,并不是正式产品。它始于这样一个问题:“我们如何让.NET 在 WebAssembly 中运行?”第一个答案是比较老的简化版.NET 运行时,他能够在几个小时内将其编译成 Wasm 二进制文件。.NET 本身在浏览器中并不是非常有用:你需要一个 UI 和某种与用户交互的方式。Razor 文件将标记和 C# 结合起来创建 Web 模板,基于这项扎实的工作,Blazor 添加了大量的服务,从数据绑定和依赖注入到可重用组件、布局,以及调用 JavaScript 和从 JavaScript 调用。所有这些服务结合使得使用.NET 和 C# 构建单页面应用程序(SPA)成为可能。

为什么会有人在意呢?开发人员最初对 Blazor 的反应非常积极,这主要是因为:

它允许开发人员使用他们已经熟悉的语言(C#)和框架(.NET)来构建以前深深扎根在 JavaScript 中的客户端应用程序。
它可以在所有现代浏览器中运行,包括移动浏览器,而且不需要插件。
它使开发人员能够进入.NET 生态系统并“按原样”使用现有的库。例如,如果你正在构建一个使用 Markdown 的博客引擎,那么你可以为现有的 Markdown 引擎安装 NuGet 包,并将 Markdown 直接转换为 HTML,以便在浏览器中预览。
.NET 的性能会随着时间的推移不断提高,因此在浏览器的 Wasm 上运行已经足够了。
Blazor 是一个真正的单页应用程序,它从一组静态资产运行,可以使用Azure Storage 静态网站等服务以非常低的成本托管这些静态资产。
现在,你已经了解了 Blazor 背后的历史和动机,让我们来研究一些技术细节。

本文中的所有代码示例都可以在Blazor WebAssembly GitHub 存储库中找到。

安装 Blazor 并开始使用所需要了解的所有内容都可以在Blazor 入门这篇文章中找到。在安装了 Blazor 之后,你可以选择只创建客户端或带有ASP.NET Core后端的客户端。对于现有的基于 MVC 的服务器端项目来说,该项目看起来非常熟悉。但是,生成的 DLL 直接加载到浏览器中,并由.NET 的 WebAssembly 版本运行。

 

Mono.js JavaScript 动态加载 mono.wasm 并开始在浏览器中运行.NET。其余的加载是组成应用程序的实际 DLL 文件。

浏览器中的 C#(带依赖注入)
默认模板包含一个获取模拟天气信息的页面。这是 Razor 视图,完全由 Wasm 在客户端上渲染。

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<tableclass="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
在模板的上部,一组指令决定了页面的路径,声明了一个 using 指令,并使用依赖注入来获取.NET Framework 的 HttpClient 副本,该副本可以在浏览器中使用。
@page "/fetchdata"
@using GetStarted.Shared
@inject HttpClient Http
最后,页面上的一小段代码嵌入到 @functions 块中。这里需要注意的是,代码完全是 C# 的。你可以使用熟悉的 HttpClient 执行网络操作,并且支持 async/wait。

WeatherForecast[] forecasts;

protected override async Task OnInitAsync()
{
forecasts = await Http.GetJsonAsync<WeatherForecast[]>("api/SampleData/WeatherForecasts");
}
视图模板渲染一个页面,但是控件呢?

可重用组件
Blazor 基于分层组件的可组合 UI。天气预报组件与其他组件的唯一区别是提供路由的页面指令。下面是一个名为 LabelSlider.razor 的组件的模板,使用一个显示当前值的 span 扩展内置的 HTML Input Range。

<inputtype="range"min="@Min"max="@Max"bind-value-oninput="@CurrentValue"/>
<span>@CurrentValue</span>
绑定语法的格式为 bind-{property}-{event}。事件是可选的,触发事件时绑定会更新。如果没有这个,滑块只会在用户停止移动滚动条时更新 span。通过连接到 oninput,该值将随着滑块的移动而刷新。

关联的代码暴露了参数,这些参数允许父组件设置最小和最大范围值,并将数据绑定到当前值。Action 属性暴露一个与 CurrentValue 关联的事件,并按照约定将其命名为 CurrentValueChanged,以方便双向数据绑定(父组件可以“侦听”更改事件并相应更新绑定值)。

[Parameter]
int Min { get; set; }

[Parameter]
int Max { get; set; }

private int _currentValue;

[Parameter]
int CurrentValue
{
get => _currentValue;
set
{
if (value != _currentValue)
{
_currentValue = value;
CurrentValueChanged?.Invoke(value);
}
}
}

[Parameter]
Action<int> CurrentValueChanged { get; set; }
注意,没有使用 Parameter 属性标记的属性只对组件可见。组件重用非常简单,只需把组件名置入标签并提供必要的参数,用法如下:

<LabelSlider Min="0" Max="99" bind-CurrentValue="@currentCount"/>
在本例中,一个与父组件上的 currentCount 属性之间的双向绑定建立起来了。

使用现有的库
Blazor 的一个非常强大的优点是能够“按原样”集成现有的类库。例如,考虑一个使用Markdown的博客引擎,它能够在浏览器中预览生成的 HTML。在 Blazor 中构建它就像安装一个 NuGet 包一样简单,在本例中是开源的MarkDig处理器。然后,可以直接调用库:

var html = Markdig.Markdown.ToHtml(SourceText);
NuGet DLL 像其他项目引用一样被导入浏览器,并且可以从客户端应用程序调用。

调用 JavaScript/ 从 JavaScript 调用
Blazor 提供的一个重要服务是能够从.NET 调用 JavaScript,反之亦然。你希望从 Blazor 调用的任何 JavaScript 方法都必须能够从全局 window 对象访问,调用方法如下:
window.jsAlert = msg => alert(msg);
该互操作功能的使用方式如下:
await JsRuntime.InvokeAsync<object>("jsAlert", "Wow!");
InvokeAsync 方法支持传递和返回值,这些值将自动在 JavaScript 与.NET 之间转换并由 Blazor 运行时编组。使用 JsInvokable 属性来暴露 C# 方法,以便可以从 JavaScript 调用它。下面是一个例子,它封装了 Markdown 转换调用:
public static class Markdown
{
[JSInvokable]
public static string Convert(string src)
{
return Markdig.Markdown.ToHtml(src);
}
}
从 JavaScript 调用 DotNet.invoke 方法并传递程序集名称、暴露的方法名称和任何参数。

这使得扩展遗留应用程序和使用现有的 JavaScript 库成为可能。你甚至可以从 Blazor 应用程序调用其他 WebAssembly 模块。

Blazor 在发展
微软将 Blazor 移出了实验阶段,进入了官方预览版。使用组件模型进行服务器端渲染的 Blazor 版本将与.NET Core 3 的最终版本一起发布(请参阅.NET Core 路线图),客户端版本将在随后不久发布。还有工作要完成:调试体验极其有限,必须改进;有机会通过提前编译生成本机 Wasm 来优化代码性能;在将未使用的代码库发送到浏览器之前,需要从库中删除未使用的代码,从而降低总体大小(这个过程称为树抖动)。对 WebAsssembly 的兴趣和采用与日俱增,借助 Blazor,编写可以在任何地方运行的 C# 和.NET 代码的梦想终于实现了。

关于作者
Jeremy Likness是微软 Azure 的云推广专员。Jeremy 在 1982 年编写了他的第一个程序,并且已经开发企业应用程序 25 年了。他是四本科技书籍的作者,曾做过 8 年微软 MVP,是国际性的主题演讲家。Jeremy 的饮食以植物为主,在大部分空闲时间里,他都在太平洋西北部他的家附近跑步、徒步旅行和露营。关注Jeremy 的博客。

28
5

.NET Core 3.0 Preview 5发布

0
归档:2019年5月分类:C#和.NET

今天我们非常高兴发布 .NET Core 3.0 Preview 5,它包含了新的Json serializer,支持发布单执行文件,并且更新到新的runtime roll-forward,BCL也做了一些改动。

目前可以下载.NET Core 3.0 Preview 5安装到Windows, macOS and Linux.另外,ASP.NET Core和EF Core也发布了。

具体英文地址:https://devblogs.microsoft.com/dotnet/announcing-net-core-3-0-preview-5/

20
5

领域驱动设计实现之路

0
归档:2019年5月分类:C#和.NET

2004年,Eric Evans出版《领域驱动设计——软件核心复杂性应对之道》(简称《领域驱动设计》),10年之后,我们有了《实现领域驱动设计》。DDD将一个软件系统的核心业务功能集中在一个核心域里面,其中包含了实体、值对象、领域服务、资源库和聚合等概念。在此基础上,DDD提出了一套完整的支撑这样的核心领域的基础设施。此时,DDD已经不再是“面向对象进阶”那么简单了,而是演变成了一个系统工程。

领域,即是一个组织的业务开展方式,业务价值便体现在其中。长久以来,我们程序员都是很好的技术型思考者,我们总是擅长从技术的角度来解决项目问题。但是,一个软件系统是否真正可用是通过它所提供的业务价值体现出来的。因此,与其每天钻在那些永远也学不完的技术中,何不将我们的关注点向软件系统所提供的业务价值方向思考思考,这也正是DDD所试图解决的问题。在DDD中,代码就是设计本身,你不再需要那些繁文缛节的并且永远也无法得到实时更新的设计文档。编码者与领域专家再也不需要翻译才能理解对方所表达的意思。DDD有战略设计和战术设计之分。战略设计主要从高层“俯视”我们的软件系统,帮助我们精准地划分领域以及处理各个领域之间的关系;而战术设计则从技术实现的层面教会我们如何具体地实施DDD。

DDD之战略设计

DDD的战略设计主要包括领域/子域、通用语言、限界上下文和架构风格等概念。

1、领域和子域(Domain/Subdomain)

领域驱动设计关注点应该放在如何设计领域模型上,以及对领域模型的划分。

领域的概念:一个电商网站的领域包含了产品名录、订单、发票、库存和物流的概念。领域的划分是将一个大的领域划分成若干个子域。通常会将一个大型的软件系统拆分成若干个子系统。这种划分有可能是基于架构方面的考虑,也有可能是基于基础设施的。DDD对系统的划分是基于领域的,即基于业务。第一个问题,哪些概念应该建模在哪些子系统里面?第二个问题是,各个子系统之间的应该如何集成?如何解决?答案是:限界上下文和上下文映射图。

2、限界上下文(Bounded Context)

在一个领域/子域中,我们会创建一个概念上的领域边界,在这个边界中,任何领域对象都只表示特定于该边界内部的确切含义。这样边界便称为限界上下文。限界上下文和领域具有一对一的关系。将一个限界上下文中的所有概念,包括名词、动词和形容词全部集中在一起,我们便为该限界上下文创建了一套通用语言。通用语言是一个团队所有成员交流时所使用的语言,业务分析人员、编码人员和测试人员都应该直接通过通用语言进行交流。

上文中的各个子域之间的集成问题,是限界上下文之间的集成问题。防腐层负责与外部服务提供方打交道,还负责将外部概念翻译成自己的核心领域能够理解的概念。当然,防腐层只是限界上下文之间众多集成方式的一种,另外还有共享内核、开放主机服务等,具体细节请参考 《实现领域驱动设计》原书。限界上下文之间的集成关系也可以理解为是领域概念在不同上下文之间的映射关系,因此,限界上下文之间的集成也称为上下文映射图。

3、架构风格(Architecture)

DDD并不要求采用特定的架构风格,因为它是对架构中立的。可以采用传统的三层式架构,也可以采用REST架构和事件驱动架构等。但是在《实现领域驱动设计》中,作者比较推崇事件驱动架构和六边形(Hexagonal)架构。在六边形架构中,已经不存在分层的概念,所有组件都是平等的。这主要得益于软件抽象的好处,即各个组件的之间的交互完全通过接口完成,而不是具体的实现细节。Robert C. Martin:抽象不应该依赖于细节,细节应该依赖于抽象。

采用六边形架构的系统中存在着很多端口和适配器的组合。端口表示的是一个软件系统的输入和输出,而适配器则是对每一个端口的访问方式。

DDD之战术设计

战略设计为我们提供一种高层视野来审视我们的软件系统,而战术设计则将战略设计进行具体化和细节化,它主要关注的是技术层面的实施,也是对我们程序员来得最实在的地方。

1、领域对象

领域对象能够准确地表达出业务意图,但多数时候却是充满getter和setter的领域对象,此时的领域对象已经不是领域对象了,而是Martin Fowler所称之为的贫血对象。.NET里面很好地支持非贫血对象。

2、实体vs值对象(Entity vs Value Object)

软件系统中实体表示那些具有生命周期并且会在其生命周期中发生改变的东西;而值对象则表示起描述性作用的并且可以相互替换的概念。同一个概念,在一个软件系统中被建模成了实体,但是在另一个系统中则有可能是值对象。

3、聚合(Aggregate)

聚合是DDD中最难理解的概念 ,聚合中所包含的对象之间具有密不可分的联系,他们是内聚在一起的。比如一辆汽车(Car)包含了引擎(Engine)、车轮 (Wheel)和油箱(Tank)等组件,缺一不可。一个聚合中可以包含多个实体和值对象,因此聚合也被称为根实体。聚合是持久化的基本单位,它和资源库具有一一对应的关系。

4、领域服务(Domain Service)

领域概念放在实体上不合适,放在值对象上也不合适,领域服务本来就是来处理这种场景的。比如对密码进行加密,可以创建一个 PasswordEncryptService来专门负责此事。

5、资源库(Repository)

资源库用于保存和获取聚合对象,资源库与DAO相似。但资源库和DAO存在显著区别。DAO只是对数据库的一层很薄的封装,而资源库则更加具有领域特征。另外,所有的实体都可以有相应的DAO,但并不是所有的实体都有资源库,只有聚合才有相应的资源库。资源库分为两种,一种是基于集合的,一种是基于持久化的。顾名思义,基于集合的资源库具有编程语言中集合的特征。

6、领域事件(Domain Event)

《领域驱动设计》中并没有提到领域事件,领域事件是最近几年才加入DDD生态系统的。微服务(Micro Service)的架构中,整个系统被分成了很多个轻量的程序模块,他们之间的数据一致性并不容易通过事务一致性完成,领域事件便可以用于处理上述问题,此时最终一致性取代了事务一致性,通过领域事件的方式达到各个组件之间的数据一致性。

06
3

.NET Core 3.0-preview3 发布

0
归档:2019年3月分类:C#和.NET

.NET Core 3.0 Preview 3已经发布,框架和ASP.NET Core有许多有趣的更新。这是最重要的更新列表。 下载地址 :https://aka.ms/netcore3download 。

.NET Core 3.0的更新:

C#中对索引和范围的更多支持

支持.NET Standard 2.1。以.NET Standard项目文件为目标,并将netstandard2.1指定为目标框架。完整的.NET Framework不支持.NET Standard 2.1。

F#4.6和dotnet fsi命令。可以使用F#4.6和dotnet fsi命令的预览。FSI代表F#互动。

AssemblyDependencyResolver和resolver事件。从给定路径加载依赖程序集(之前不可能),解析程序事件可帮助我们更好地处理动态加载的本机依赖项。

Windows Forms应用程序的高DPI。最后,微软将Windows Forms应用程序推向了当今时代。96DPI不再适用,并且可以构建高DP Windows窗体应用程序。

ASP.NET Core 3.0的更新:

Razor组件的改进。现在2个项目合并成单个项目模板,Razor组件支持端点路由和预渲染,Razor组件可以托管在Razor类库中。还改进了事件处理和表单和验证支持。

运行时编译。它在ASP.NET Core 3.0模板中被禁用,但现在可以通过向项目添加特殊的NuGet包来打开它。

Worker Service 模板。需要编写Windows服务还是Linux守护进程?现在我们有了Worker Service 模板。

gRPC模板。与谷歌一起构建的gRPC是一种流行的远程过程调用(RPC)框架。此版本的ASP.NET Core在ASP.NET Core上引入了第一等的gRPC支持。

Angular模板使用Angular 7. Angular SPA模板现在使用Angular 7,在第一次稳定释放之前,它将被Angular 8替换。

SPA-s的身份验证。Microsoft通过此预览为单页应用程序添加了现成的身份验证支持。

SignalR与端点路由集成。小变化 - 现在使用端点路由定义SingalR路由。

SignalR Java客户端支持长轮询。即使在不支持或不允许WebSocket的环境中,SignalR Java客户端现在也可以使用。

 

14
11

昨天.NET已经宣布正式开源,而我的信念更加坚定了:将业余的大部分时间投入给C#开源世界。

之前一直诟病于C#和.NET只属于微软的产品,现在完全不一样了,我自己的下一步计划:

一、学习计划

1、重新把前几年的项目进行一次全面梳理,把核心和精华的部分留下;

2、重新全面系统完整地把最新的.NET框架学习一边,并且结合大的开源项目研究,目标是基于国外开源系统改造出非常好用的系统;

3、将手头上关于.NET和C#的图书做一遍梳理,包括电子版的;

4、持续跟进最新的关于Mono和.NET开源方面的资讯;

二、工作计划

1、将目前公司的项目做好梳理、未未来的跨平台打好基础;

2、专心研究nopcommerce(电商)项目,并且基于它搭建一个云、端、移动app、网店一体化的电商项目(时间可能会很长),这个项目会为未来二次开源做准备;

3、专心研究CSLA .NET,这个是.NET的一个典范,而且包含了所有平台的样例,这是跨平台的先驱;

4、专心研究Orchard(cms)项目,并且基于它搭建一个云、端、移动app的公司企业网站系统,这个项目也会为未来开源做准备。

将慢慢地把cms和电商项目融入公司,打造一支技术能力强的团队。

13
11

微软正式MIT协议将开源.NET框

0
归档:2014年11月分类:C#和.NET

今天是C#程序员狂欢的日子,这真的是一门非常非常优雅的很棒的编程语言。

“Scott Guthrie宣布微软正式将开源.NET框架,使用MIT协议开源,让它在Linux和OS X系统上也能够运行。开发商们将能够在全球三个最大的操作系统上使用.NET框架了。”

这将是计算机软件行业载入史册的一天,.NET平台终于兑现它对世界的承诺,全面支持所有平台,我坚信这是安德鲁和mono老大的英明推动下、当然也是在时代的发展驱动下的结果,太棒了!

2000

08
8

一、简介

在.NET中,委托,匿名方法和Lambda表达式很容易发生混淆。我想下面的代码能证实这点。下面哪一个First会被编译?哪一个会返回我们需要的结果?即Customer.ID=5.答案是6个First不仅被编译,并都获得正确答案,且他们的结果一样。如果你对此感到困惑,那么请继续看这篇文章。

class Customer
{
    public int ID { get; set; }
    public static bool Test(Customer x)
    {
        return x.ID == 5;
    }
}
...
List<Customer> custs = new List<Customer>();
custs.Add(new Customer() { ID = 1 });
custs.Add(new Customer() { ID = 5 });
 
custs.First(new Func<Customer, bool>(delegate(Customer x) { return x.ID == 5; }));
custs.First(new Func<Customer, bool>((Customer x) => x.ID == 5));
custs.First(delegate(Customer x) { return x.ID == 5; });
custs.First((Customer x) => x.ID == 5);
custs.First(x => x.ID == 5);
custs.First(Customer.Test);

二、什么是委托?

现在你定义一个处理用户订单的购物车ShoppingCart类。管理层决定根据数量,价格等给客人折扣。做为其中的一部分,他们已经实现了处理订单时你要考虑一方面。不用考虑过多,你简单声明一个变量来保存有“吸引力的折扣”(magicDisCount),然后实现逻辑。

class Program {
    static void Main(string[] args)  {
        new ShoppingCart().Process();
    }
class ShoppingCart {
    public void Process() {
        int magicDiscount = 5;
        // ...
    }
}

第二天,异想天开的管理层决定根据购买时间调整折扣。这个很简单,但需要你改动一点代码。

class ShoppingCart {
    public void Process() {
        int magicDiscount = 5;
        if (DateTime.Now.Hour < 12) {
            magicDiscount = 10;
        }
    }
}

接下来一段时间里,管理层又反复添加更多的折扣逻辑。这时你就会在心理抱怨“受够了”。那么我该怎么做才能把这些无聊的逻辑从我的代码中剥离出去,让该处理的人去处理呢?这时你要做的是移交或者委派给相应职能的别人。幸运的是,.NET为此提供了一种叫做“委托”的机制。

三、委托

如果你有C/C++编程背景,描述委托最好的方法是“函数指针”。对所有人来说,可以认为把委托传递给方法与把值或对象传递给方法一样。比如下面三行代码就表现出一样的基本原则:你在传递数据给Process处理而不是你自己使用。

 
// 给方法Process传递一个整形值
Process( 5 );
// 给方法Process传递一个ArrayList的引用
Process( new ArrayList() );
// 给方法Process传递一个方法的引用
Process( discountDelegate );

DiscountDelegate是什么?我如何创建?Process方法如何使用?首先如同声明一个类一样,声明一个委托类型。

delegate int DiscountDelegate();
  这句话的意思是我们有一个叫DiscountDelegate的委托类型,我们可以像使用类,结构体等一样使用它。它不需要数据参数,但返回一个整数值。像类一样,我们必须创建一个它的实例它才有意义。记住,创建一个委托实例实质上是创建一个方法的引用。创建实例时关键是要明白DiscountDelegate没有任何构造器,它有一个隐式的构造函数来构造一个与它相同签名的方法(没有传入参数,返回一个整数)。那你怎么给这个构造函数一个方法呢?.NET向你提供了一个向它名字一样简单的方法,你所做的只是忽略圆括号。
1
DiscountDelegate discount = new DiscountDelegate(class.method);
  在深入之前,先回到开始的例子,整理一个代码。我们会添加一个Calculator类来帮助我们处理折扣逻辑,并给我们的委托提供一些方法。
delegate int DiscountDelegate();
 
class Program {
    static void Main(string[] args) {
        Calculator calc = new Calculator();
        DiscountDelegate discount = null;
        if (DateTime.Now.Hour < 12) {
            discount = new DiscountDelegate(calc.Morning);
        }
        else if (DateTime.Now.Hour < 20) {
            discount = new DiscountDelegate(calc.Afternoon);
        }
        else {
            discount = new DiscountDelegate(calc.Night);
        }
        new ShoppingCart().Process(discount);
    }
class Calculator {
    public int Morning() {
        return 5;
    }
    public int Afternoon() {
        return 10;
    }
    public int Night() {
        return 15;
    }
class ShoppingCart {
    public void Process(DiscountDelegate discount) {
        int magicDiscount = discount();
        // ...
    }
}

正如你所见,在Calculator类中,我们为每个逻辑分支创建了一个方法。在Main方法中,我们创建一个Calculator实例和一个DiscountDelegate实例,并按照我们所期望的把它们整合在一起。

太棒了,我们不用担心Process方法中的逻辑了,我们只需要简单得回调我们定义的委托。记住!我们不关心委托是如何创建的(或什么时间),我们就像调用其他方法一样调用它。如你所见,另一种理解委托的方法是,它延迟执行一个方法。Calculator方法在过去某个时间本选择,但不会执行,直到我们调用discount()的时候。现在看看我们的解决方案,这里仍然存在一些丑陋的代码。在Calculator类中,我们可以用一个不同的方法来返回替代每个有返回值得方法吗?答案是肯定的,让我们把这些乱糟糟的代码合并起来。

 
delegate int DiscountDelegate();
 
class Program {
    static void Main(string[] args) {
        new ShoppingCart().Process(new DiscountDelegate(Calculator.Calculate));
    }
class Calculator {
    public static int Calculate() {
        int discount = 0;
        if (DateTime.Now.Hour < 12) {
            discount = 5;
        }
        else if (DateTime.Now.Hour < 20) {
            discount = 10;
        }
        else {
            discount = 15;
        }
        return discount;
    }
class ShoppingCart {
    public void Process(DiscountDelegate discount) {
        int magicDiscount = discount();
         // ...
    }
}

这样子看起来更好点。你会注意到我们用一个静态的Calculate方法替换了所有原来的方法,在Main方法中也不用费心维护一个指向DiscountDelegate的引用。现在你明白了所有关于委托的东西了吗?在2004年.NET1.1中可以这么说,但是很不幸的是,这种框架自那以后更加成熟了。

四、灯光,镜头,开始 或者我们需要Func!

微软在.NET 2.0中引入了泛型,并提供了一个泛型委托:Action<T>。老实说,我认为它远不够用。后来在.NET 3.5中,它为我们提供了一些我们不想定义的通用委托。他们扩展了Action,并添加了Func,二者唯一区别在于Func型方法有一个返回值而Action型方法没有。

这意味着我们不需要声明自己的DiscountDelegate,可以用Func<int>替代。为说明这些观点是如何工作的,我们来假设管理层又一次改变了我们的逻辑,我们需要提供一些特殊的折扣。很简单,我们将给Calculate方法传入一个bool型值。

现在我们的委托签名变成Func<bool,int>。注意Calculate方法现在包含一个bool型参数,我们用一个bool值调用discount()。

class Program {
    static void Main(string[] args) {
        new ShoppingCart().Process(new Func<bool, int>(Calculator.Calculate));
    }
}
 
class Calculator {
    public static int Calculate(bool special) {
        int discount = 0;
        if (DateTime.Now.Hour < 12) {
            discount = 5;
        }
        else if (DateTime.Now.Hour < 20) {
            discount = 10;
        }
        else if (special) {
            discount = 20;
        }
        else {
            discount = 15;
        }
        return discount;
    }
}
 
class ShoppingCart {
    public void Process(Func<bool,int> discount) {
        int magicDiscount = discount(false);
        int magicDiscount2 = discount(true);
    }
}

好像还算不错,我们又省了一行代码,这样算结束了吗?当然没有,我们甚至能省掉类型判断。只要我们传递的方法有严格签名的委托,.NET允许我们完全忽略掉显式创建Func<bool,int>。

//因为Process期望的方法有一个bool型输入参数和返回一个int值,所以下面这句话是正确的
new ShoppingCart().Process(Calculator.Calculate);

至此,首先通过忽略自定义委托,我们省略了代码;然后排出了明确的创建Func委托。我们能继续压缩代码行吗?到此我们才完成此文的一半,答案显然是“能”。

 五、匿名方法

匿名方法能够让你声明一个方法体而不需要给它指定一个名字。在接下来的场景里,它们以一个“普通的”方法存在;但是在你的代码中没有任何方法显式调用它。匿名方法只能在使用委托的时候创建,事实上,它们通过delegate关键字创建。

class Program {
    static void Main(string[] args)     {
        new ShoppingCart().Process(
            new Func<bool, int>(delegate(bool x) { return x ? 10 : 5; }
        ));
    }
}

正如你所见,我们完全删除了Calculator类的需求。你可以在打括号中添加任何其他方法中的逻辑。如果你在看它如何执行时有困难,那就把delegate(bool x)做为一个方法签名,而不是一个关键字。设想这段代码在一个类里,delegate(bool x){return 5;}是一个完整的合法方法声明(我们确实有一个返回值),恰好delegate是一个保留字,在这里,它让这个方法匿名。

至此,我确信现在你知道这里我们甚至能压缩更多的代码。顺利成章的,我们能忽略显式声明Func委托的需要;.NET让我们使用delegate关键字更方便。

class Program {
    static void Main(string[] args) {
        new ShoppingCart().Process(
          delegate(bool x) { return x ? 10 : 5; }
        );
    }
}

当把.NET方法做为委托参数时或处理时间时,就能看到匿名方法的真正用处。之前,你会为你所关注的所有可能行为创建了一个方法,现在你仅需以内联的方式创建它们,并可以避免污染你的命名空间。

// 创建一个匿名比对方法
custs.Sort(delegate(Customer c1, Customer c2) {
    return Comparer<int>.Default.Compare(c1.ID, c2.ID);
});
 
//  创建一个匿名事件
button1.Click += delegate(object o, EventArgs e) { MessageBox.Show("Click!"); };

六、Lambda 表达式

MSDN中写道:“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。你应当明白“用户创建委托”部分,但什么是“表达式”呢?老实说,表达式和表达式树不在此为讨论范围内。现在我们唯一需要明白的是,表达式是.NET程序运行时表示数据或对象的代码(C#代码)。引用Jon Skeet的话:“表达式树是一种表达逻辑,这样其他的代码可以查询的方法。当一个lambda表达式转换成一个表达式树,编译器不会发出了lambda表达式的白细胞介素,它会发出白细胞介素这将会建立一个表达式树表示相同的逻辑。”

我们需要关注的是Lambda表达式替换匿名方法,和其他的特性。回顾我们最后例子,我们已经在一行代码里压缩了处理整个折扣算法的逻辑。

class Program {
    static void Main(string[] args) {
        new ShoppingCart().Process(
            delegate(bool x) { return x ? 10 : 5; }
        );
    }
}

你相信我们能让这个更短吗?Lambda表达式用'=>'运算符表明什么参数传递给表达式。编译器进一步处理,允许我们忽略类型并自动替我们推断这些类型。如果你有2个或更多个参数,你需要用圆括号:(x,y)=>。如果只有一个,你设置不需要这样:x=>。

static void Main(string[] args) {
    Func<bool, int> del = x => x ? 10 : 5;
    new ShoppingCart().Process(del);
}
// 更短啦...
static void Main(string[] args) {
    new ShoppingCart().Process(x => x ? 10 : 5);
}

就是这样子。x被推断为bool型,并且有返回值,因为Process接收一个Func<bool,int>。如果我们想实现像之前那样的完整代码块,我们只需要加上大括号。

static void Main(string[] args) {
    new ShoppingCart().Process(  x => {
        int discount = 0;
        if (DateTime.Now.Hour < 12) {
            discount = 5;
        }
        else if (DateTime.Now.Hour < 20) {
            discount = 10;
        }
        else if(x)  {
            discount = 20;
        }
        else {
             discount = 15;
        }
        return discount;
    });
}

七、写在最后

使用与不使用大括号有一个重要的不同。当你用时,你创建一个“语句Lambda”,反之,它是"表达Lambda"。语句Lambda能执行多条语句(因此需要大括号),但不能创建表达树。你可能只在使用IQueryable接口是遇到这个问题。下面的例子说明这个问题。

List<string> list = new List<string>();
IQueryable<string> query = list.AsQueryable();
list.Add("one");
list.Add("two");
list.Add("three");
 
string foo = list.First(x => x.EndsWith("o"));
string bar = query.First(x => x.EndsWith("o"));
// foo and bar are now both 'two' as expected
foo = list.First(x => { return x.EndsWith("e"); }); //no error
bar = query.First(x => { return x.EndsWith("e"); }); //error
bar = query.First((Func<string,bool>)(x => { return x.EndsWith("e"); })); //no error

倒数第二行在编译时失败。这是因为IQueryable.First期望得到一个表达式作为参数,然而List<T>.First期望得到一个委托。你可以按照最后一行强制转换Lambda到一个委托(使用First的方法重载)。

这里很难结束讨论,但是我觉得必须停止。Lambda大体上分为两类:一类创建匿名方法和委托;另一类创建表达式。表达式自成一体,并不是.NET开发者的必备知识(无疑在LINQ中已有实现)。

 

        此文为CodeProject上的同名文章<C# Delegates,Anonymous Methods, and Lambda Expressions>

08
8

.NET Framework发展简历

0
归档:2013年8月分类:C#和.NET

.NET Framework1.0版本是在2002年正式发布的,当时获得了世界软件界的极大瞩目,影响深远,比尔盖茨说这是为未来10年的战略做准备,现在看来,微软的这个产品获得了巨大成功。

2005年,微软发布了.NET Framework2.0,这个版本是迄今为止最重要的一个版本,2.0为C#引入了范型和公共语言运行时。.NET 3.0版本是基于2.0的运行时的,引入了WPF和XAML语言,还有一项伟大的技术WCF。.NET3.5则引入了C#3.0,并且支持LINQ语言(一种全新的方式来检索数据)和Lambda表达式。.NET4.0是继2.0之后的一个重要版本,引入了动态语言和并行编程。

.NET Framework4.5是基于4.0版本的更新,主要有下面这些更新:

1、异步变成方式,在C#5.0中,异步编程变得更加简单;

2、引入了Windows Store Apps;

3、增强了数据访问方式,主要是Entity Framework5.0的发布;

4、增强了WPF和江MVC升级到了4.0;

公告栏

欢迎大家来到我的博客,我是dodoro,希望我的博客能给你带来帮助。