C-7-入门实用指南-全-

C#7 入门实用指南(全)

原文:zh.annas-archive.org/md5/0D2F44FACA4630D8785DF55498F3E611

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Beginning C# 7 Hands-On – Advanced Language Features 假设您已经掌握了 C#语言的基本要素,并且现在准备在工作的 Visual Studio 环境中逐行学习更高级的 C#语言和语法。您将学习如何编写高级的 C#语言主题,包括泛型、lambda 表达式和匿名方法。您将学习使用查询语法构建查询和部署执行聚合函数的查询。您将使用 C# 7 和 SQL Server 2017 执行复杂的连接和存储过程。探索高级文件访问方法,并了解如何序列化和反序列化对象——所有这些都是通过编写可以在 Visual Studio 中运行的工作代码来完成的。您还将通过 Web 表单查看 C#的 Web 编程。完成本书后,您将了解 C#语言的所有关键高级要素,以及如何从 C#泛型到 XML、LINQ 和您的第一个完整的 MVC Web 应用程序进行编程。这些都是您可以逐行组合利用 C#编程语言全部功能的高级构建块。本书适用于已经掌握基础知识的初学者 C#开发人员,以及需要快速参考实际编码示例中使用高级 C#语言功能的任何人。

本书需要什么

建议安装和运行在 Windows 7 或以上的 Visual Studio 2017,并推荐使用 2GB 或 4GB 的 RAM。典型安装至少需要 20-50GB 的硬盘空间。

这本书是为谁准备的

这本书将吸引任何对学习如何在 C#中编程感兴趣的人。先前的编程经验将帮助您轻松地通过初始部分,尽管不一定需要具备任何经验。

约定

在本书中,您将找到一些区分不同类型信息的文本样式。以下是这些样式的一些示例及其含义解释。文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:"具体来说,Default.aspx 是一个包含网页上元素标记的文件。"

代码块设置如下:

<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
    <asp:ListItem>Monday</asp:ListItem>
    <asp:ListItem>Tuesday</asp:ListItem>
    <asp:ListItem>Wednesday</asp:ListItem>
</asp:DropDownList>

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True">
    <asp:ListItem>Monday</asp:ListItem>
    <asp:ListItem>Tuesday</asp:ListItem>
    <asp:ListItem>Wednesday</asp:ListItem>
</asp:DropDownList>

新术语重要单词以粗体显示。例如,屏幕上看到的单词,例如菜单或对话框中的单词,会以这样的形式出现在文本中:"如果愿意,点击 浏览 并将文件保存到您选择的位置,然后点击 确定。"

警告或重要说明会出现在这样的形式。

提示和技巧会以这样的形式出现。

第一章:创建一个简单的泛型类

在本章中,您将学习制作一个简单的泛型类的基础知识,以便一个类可以操作许多不同的数据类型。泛型的一个巨大好处是灵活性。

创建一个泛型类

打开一个项目,转到解决方案资源管理器;右键单击,选择添加,然后单击类。将类命名为GenericsClass;一个简单的泛型类。然后,单击确定。当 Visual Studio 消息出现时,单击是。

对于我们的目的,您不需要顶部的任何using System行,也不需要下面的任何注释,所以删除它们。您的初始屏幕应该看起来像图 1.1.1

图 1.1.1:初始 GenericsClass.cs 屏幕

使用不同的数据类型

现在,在public class GenericsClass后面加上<T>符号,如下所示:

public class GenericsClass<T>

这意味着这个单一的类可以同样有效地处理几种不同的数据类型。接下来,在上一行的开放大括号下面输入以下内容:

private T[] vals;

在此行的正上方直接输入以下注释:

//generic array instance variable

换句话说,这将在双精度、小数、整数等上同样有效。

制作通用参数

现在,在下一行中,输入以下内容:

public GenericsClass(T[] input)

正如您所看到的,您也可以制作像这样通用的参数。这是一个参数,input是它的名称,类型是T。所以,它是一个通用数组。

接下来,在上一行的一对大括号之间输入以下内容:

vals = input;

显示值

当然,您应该能够显示这些值。因此,在vals = input;行的关闭大括号下面输入以下行:

public string DisplayValues()

要显示这些值,您将在上一行的一对大括号之间输入以下内容。

首先,输入一个字符串,如下所示:

string str = null;

接下来,声明字符串并将值初始化为 null。

然后,在此行的正下方输入以下内容:

foreach ( T t in vals)

正如你所看到的,在这里foreach循环将会运行。T对象将是不同的数据类型,取决于我们如何选择制作对象。当然,t变量是vals数组中每个特定值。

接下来,在上一行下面的一对大括号之间输入以下内容:

str += $"<br>Value={t}";

请记住,我们使用+=运算符来累积和<br>来推到下一行。当然,为了得到值,我们将放入t变量。

最后,您希望返回这个,所以您将在上一行的关闭大括号下面输入以下内容:

return str;

就是这样。本章的GenericsClass.cs文件的最终版本,包括注释,如下所示:

 //<T> means this class can operate on many different data types
public class GenericsClass<T>
{
    //generic array instance variable
    private T[] vals;//array of T inputs
    public GenericsClass(T[] input)
    {
        //set value of instance variable
        vals = input;
    }
    public string DisplayValues()
    {
        string str = null;//create string to build up display
        foreach(T t in vals)
        {
            //actually accumulate stuff to be displayed
            str += $"<br>Value={t}";
        }
    //return string of outputs to calling code
    return str;
    }
}  

请注意,我们有一个代码块;现在它将在整数、双精度等上运行。

向 Default.aspx 添加按钮

现在,让我们看看Default.aspx。此时,我们真正需要做的唯一的事情就是添加一个Button控件。为此,转到工具箱,从那里获取一个Button控件。将其拖放到以<form id=...开头的行下面。更改Button控件上的文本,例如为显示值。您的完整的Default.aspx文件应该看起来像图 1.1.2中显示的那样:

图 1.1.2:此项目的完整 HTML

现在,转到设计视图。我们非常简单的界面显示在图 1.1.3中:

图 1.1.3:我们在设计视图中非常简单的界面

将整数集合初始化为它们的数组并显示结果

现在,双击显示值按钮,进入Default.aspx.cs。删除Page_Load块。接下来,在以protected void Button1_Click...开头的行下面的一对大括号之间,输入以下内容:

GenericsClass<int> ints = new GenericsClass<int>(new int[] { 1, 2, 3, 4, 5 });

你可以在这行中看到,我们基本上是将整数的集合初始化为它们的数组。

现在,你可以显示这个。例如,你可以在这行下面输入以下内容:

sampLabel.Text += ints.DisplayValues();

请注意,我们构建的GenericsClass正在操作整数,但它同样可以在任何其他数据类型上同样有效地操作。

更改我们泛型类中的数据类型

现在,为了更明显地显示代码的效率,将前面的两行都复制(Ctrl + C)并粘贴(Ctrl + V)在下面,并将其改为 double,如下所示:

GenericsClass<double> dubs = new GenericsClass<double>(new double[] {1, 2, 3, 4, 5});
sampLabel.Text = ints.DisplayValues();

我们将这个称为dubs,并在这里将名称更改为 double:这是相同的代码,相同的类,以及你可以在双精度上操作的相同的泛型类。再次强调一遍,以及看到这种灵活性和代码重用确实是这里的目的;也就是说,重用代码的能力,我们现在将这两行新代码再次复制粘贴到下面,并将double改为decimal,如下所示:

GenericsClass<decimal> decs = new GenericsClass<decimal>(new decimal[] { 1, 2, 3, 4, 5 });
sampLabel.Text = ints.DisplayValues();

让我们称这个为decs。现在,当然,如果你想让事情变得更有趣一点,你可以加入一些小数:

GenericsClass<double> dubs = new GenericsClass<double>(new double[] { 1.0, -2.3, 3, 4, 5 });
sampLabel.Text = ints.DisplayValues();
GenericsClass<decimal> decs = new GenericsClass<decimal>(new decimal[] { 1, 2.0M, 3, 4, 5.79M });
sampLabel.Text = ints.DisplayValues();

对于小数,只需确保在其中加入M后缀,因为你需要在末尾加上M后缀来表示它是一个小数。

运行程序

现在,让我们来看看。当你运行这段代码并点击“显示数值”按钮时,你的屏幕将会看起来像图 1.1.4中所示的样子:

图 1.1.4:我们代码的初始运行

累积输入

现在,我们将累积输入。所以,在以下的sampLabel.Text行中,我们将=号改为+=,如下所示:

GenericsClass<double> dubs = new GenericsClass<double>(new double[] { 1.0, -2.3, 3, 4, 5 });
sampLabel.Text += ints.DisplayValues();
GenericsClass<decimal> decs = new GenericsClass<decimal>(new decimal[] { 1, 2.0M, 3, 4, 5.79M });
sampLabel.Text += ints.DisplayValues();

让我们再跑一遍。点击“显示数值”按钮,你的屏幕现在会看起来像图 1.1.5中所示的样子:

图 1.1.5:现在正在累积输入,并且值显示如预期

程序现在按预期工作。

所以,泛型的重要概念是你可以定义一个泛型类。这个类可以在许多不同的数据类型上同样有效地操作。例如,你可以创建一个泛型类,它既可以操作整数,也可以操作双精度和小数。

这一步并不是严格要求的,但是这里有一点额外的见解。如果你愿意,你可以设置一个断点。选择以protected void Button1_Click....开头的行下面的大括号所在的行。现在,转到调试 | 逐步执行(F11),然后点击“显示数值”。

现在,我们将逐步进行。首先,将鼠标悬停在Generics Class.cs中以下行中的T对象上:

public GenericsClass(T[] input)

在这里,T本质上就像一个参数,因此它确实有一个特定的值,这在vals = input;行中得到了表达。第一次,T用于整数。这就是你可以逐步执行这段代码的方式。在屏幕底部,数组中的值被显示出来,就像图 1.1.6中所示的那样:

图 1.1.6:数组中的值

正如你在图 1.1.7中所看到的,t变量是一个整数,它的操作方式如下:

图 1.1.7:t 是一个整数

还要注意在截图中,它是一个带有<int>数据类型的泛型类。

foreach(T t in vals)行中的T对象现在代表一个整数,其他数据类型也是如此。因此,代码的灵活性和重用意味着你将写更少的代码。如果没有泛型,你将不得不创建单独的类来处理每种不同的数据类型。

章节回顾

回顾一下,包括注释在内,本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //in each case below, GenericsClass<...> works equally well with
        //integers, doubles and decimals, among others
        GenericsClass<int> ints = new GenericsClass<int>(new int[] { 1, 2, 3, 4, 5 });
        sampLabel.Text = ints.DisplayValues();
        GenericsClass<double> dubs = new GenericsClass&lt;double>(new double[] { 1.0, -2.3, 3, 4, 5 });
        sampLabel.Text += ints.DisplayValues();
        GenericsClass<decimal> decs = new GenericsClass<decimal>(new decimal[] { 1, 2.0M, 3, 4, 5.79M });
        sampLabel.Text += decs.DisplayValues();
    }
} 

摘要

在本章中,您学习了创建一个简单的通用类的基础知识,以便一个类可以操作许多不同的数据类型。通用类的一个巨大好处是灵活性。您创建了一个简单的通用类,可以处理不同的数据类型,创建了通用参数,将整数集合初始化为它们的数组并显示结果,然后将通用类中的数据类型更改为双精度和小数。

在下一章中,您将学习关于通用方法的知识,或者可以操作不同数据类型的方法。您还将学习如何约束方法可以操作的数据类型,因此我们将添加一个叫做约束的概念。

第二章:创建一个泛型方法

在本章中,你将学习关于泛型方法的知识,这些方法可以操作不同的数据类型。你还将学习如何约束方法以便它可以操作的数据类型,因此我们将添加一个叫做约束的概念。

创建一个按钮来交换然后比较两个值

打开一个项目并单击标签。在里面唯一需要放置的是一个按钮。这一次,我们不会从用户那里读取任何值,以节省时间。因此,去工具箱中抓取一个Button控件。将其拖放到以<form id=...开头的行下面(你可以删除<div>行,因为我们不需要它们)。将按钮上的文本更改为Exchange And Compare。因此,这将交换两个值然后进行比较。你的完整的Default.aspx文件应该看起来像图 2.2.1中所示的那样:

图 2.2.1:本章的完整 HTML 文件

编写一个交换函数

swap函数是一个常见的写法:一个交换两个值的函数。要做到这一点,转到解决方案资源管理器,右键单击网站的名称,选择添加,然后单击类。将类命名为GenMethods以保持简单,然后单击确定。当 Visual Studio 消息出现时,单击是。

GenMethods文件出现时,你应该只保留using System。我们不需要这个类的构造函数,所以去掉它。然后,在GenMethods的主体内,定义以下内容,放在下面一行的公共类GenMethods行的大括号之间:

public static void Swap<T>(ref T x, ref T y)

这将在类级别起作用:你不必创建GenMethods类型的对象。从某种意义上说,这里唯一新的东西就是这是一个Swap<T>函数,这意味着它可以同样很好地作用于几种不同的数据类型。现在,还要记住,ref关键字表示在这一行的xy参数中,当你在方法内部改变值时,这些改变的值也可以在调用代码内部访问。记住这一点。

在继续之前,让我们在这一行上面输入以下注释:

//method can operate on multiple data types 

这基本上意味着这个方法可以在多种数据类型上平等地操作。

现在,在前一行下面的大括号之间,输入以下内容来交换值:

T temp = x;

在这里,你正在取x的值并将其赋给一个临时的值。然后,在下一个阶段,你可以取x并将y赋给它,然后你可以取y并将temp赋给它,如下所示:

x = y;
y = temp;

在继续之前,让我们添加以下注释:

T temp = x; 
//save x 
x = y;
//assign y to x 
y = temp;
//assign original value of x back to y 

在这里,第一行的意思是用y的值覆盖x的值,然后将y赋给x。在最后阶段,你将temp,即x的原始值,重新赋给y。这代表了值的交换。

使用 CompareTo 方法比较值

现在,让我们再做一个方法。这个方法会更加复杂一些。它将被称为Compare,并且将操作不同的数据类型。因此,在前面一行的闭合大括号下面输入以下内容:

public static string Compare<T>(T x, T y) where T : IComparable

介绍约束

要比较值,你需要使用CompareTo方法。如果你有where T : IComparable,它就可以使用。这是一个新的构造。这是一个约束Compare方法可以工作,但只有在它所操作的数据类型上实现了IComparable时才能这样做。换句话说,比较这些值是有意义的。

完成 GenMethods 类

对于下一个阶段,你可以说以下内容。在这一行下面的一对大括号中输入它:

if(x.CompareTo(y) < 0)

现在,为什么我们要写这个呢?我们写这个是因为如果你右键点击CompareTo方法并在下拉菜单中选择 Go To Definition(F12),你会看到它是在IComparable接口内定义的。如果你展开它并查看它返回的内容,它说:值的含义小于零 这个实例在排序顺序中位于 obj 之前。如图 2.2.2所示:

图 2.2.2:IComparable 的定义

换句话说,在我们的上下文中,这意味着xy在以下意义上相关。

如果CompareTo返回的值小于0,那么x小于y

现在,在前面一行的一对大括号内输入以下内容:

return $"<br>{x}<{y}"; 

在这一行中,你返回并实际格式化一个字符串,所以它不仅仅是一个简单的比较。例如,你可以在这里说x小于y

另一个可能性是相反的。现在,在之前的闭合大括号下面输入以下内容:

else
{
     return $"<br>{x}>{y}";
} 

如果你想了解更多关于CompareTo的信息,右键点击它并在下拉菜单中选择 Go To Definition(F12)。如图 7.2.3中的 Returns 中所示,它说:零 这个实例在排序顺序中与 obj 处于相同位置。大于零 这个实例在排序顺序中跟随对象。:

图 2.2.3:CompareTo 的定义

if (x.CompareTo(y) < 0)行中,这个实例表示x变量,对象表示y变量。

所以,这是基本的GenMethods类。包括注释的GenMethods.cs文件的最终版本如下所示:

using System;
public class GenMethods
{
    //method can operate on multiple data types
    public static void Swap<T>(ref T x, ref T y)
    {
        T temp = x; //save x
        x = y;//assign y to x
        y = temp;//assign original value of x back to y
    }
    //this function has a constraint, so it can operate on values
    //that can be compared
    public static string Compare<T>(T x, T y) where T :IComparable
    {
        //CompareTo returns < 0, =0,or >0, depending on the relationship
        //between x and y
        if(x.CompareTo(y)<0)
        {
            return $"<br>{x}<{y}";
        }
        else
        {
            return $"<br>{x}>{y}";
        }
    }
} 

如你所见,GenMethods类包含一些通用函数,因为它可以操作不同的数据类型,除了第二个CompareTo方法,它是一个稍微更受限制的版本,意味着IComparable类型有一个约束。

硬编码数值

现在,回到Default.aspx,转到设计视图,并双击 Exchange 和 Compare 按钮。我们将只是硬编码值以节省时间。我们不必从用户那里读取它们。当然,如果你愿意的话,你可以通过输入两个框并使用double转换来处理。

现在,在Default.aspx.cs中,在以protected void Button1_Click...开头的一行下面的一对大括号之间,输入以下行:

double x = 25, y = 34;

然后使用sampLabel.Text在屏幕上显示原始值,首先显示x的值,然后显示y的值:

sampLabel.Text = $"x={x}, y={y}";

接下来,进行值的交换。输入以下内容:

GenMethods.Swap<double>(ref x, ref y);  

首先,输入类的名称,然后是函数Swap。你会看到<T>现在可以替换为<double>,因为我们在交换 double。然后,你会输入ref xref y

因为我们使用了ref,所以xy的值必须被初始化,现在我们可以再次显示它们,但是交换了,如下所示:

sampLabel.Text += $"<br>x={x}, y={y}";

运行程序

让我们看看效果,看看这是否按预期工作。所以,在你的浏览器中试一试。点击 Exchange 和 Compare 按钮。结果显示在图 2.2.4中:

图 2.2.4:初始程序运行的结果

如你所见,x 是 25,y 是 34。然后,当你点击 Exchange 和 Compare 后,x 是 34,y 是 25。所以,这正如预期的那样工作。

修改程序以进行额外类型的比较

现在,回到Default.aspx,在设计视图中,我们也将比较这些值。为此,再次双击 Exchange 和 Compare 按钮,并在我们输入的最后一行下面添加以下内容:

sampLabel.Text += GenMethods.Compare<double>(x, y);

请记住,我们设计Compare的方式是返回一个字符串,根据具体情况返回两个值中的一个。因此,在这一行中,我们将比较double;所以你把它放在那里,然后两个值将是xy

让我们在你的浏览器中试一试。再次点击“交换和比较”按钮。新的结果显示在图 2.2.5中:

图 2.2.5:修改后程序运行的结果

现在,x 是 25,y 是 34。当你交换值时,x 是 34,y 是 25。此外,34 肯定比 25 大。看起来非常漂亮和专业。

修改程序以适应不同的数据类型

现在的好处是:想象一下,如果你想重新做这个;你只需输入int作为示例,并将数据类型更改为整数或小数类型以及方法。我们在本章中编写的代码同样适用于这些情况:

int x = 25, y = 34;
sampLabel.Text = $"x={x}, y={y}";
GenMethods.Swap<int> (ref x, ref y);
sampLabel.Text += $"<br>x={x}, y={y}";
sampLabel.Text += GenMethods.Compare<int>(x, y);

当然,唯一的问题是,如果你右键点击int并在下拉菜单中选择“转到定义”(F12),你会看到它说public struct Int32并且它实现了IComparable

图 2.2.6:public struct Int32 的定义

这将起作用是因为我们的函数有一个约束,它规定了T应该是可比较的,如下所示:

public static string Compare<T>(T x, T y) where T : IComparable

这些都是基础知识。

章节复习

为了复习,包括注释在内的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        int x = 25, y = 34;//declare and set two variables
        sampLabel.Text = $"x={x}, y={y}";//display variables
        GenMethods.Swap (ref x, ref y);//swap values
        sampLabel.Text += $"<br>x={x}, y={y}";//display swapped values
        sampLabel.Text += GenMethods.Compare (x, y);
    }
}

摘要

在本章中,你学习了关于泛型方法的知识,这些方法可以操作不同的数据类型。你还学习了如何约束方法以便它可以操作的数据类型,这个概念叫做约束。你创建了一个GenMethods类,编写了一个Swap函数,使用CompareTo方法比较值,学习了约束,并修改了程序以执行额外类型的比较和使用不同的数据类型。

在下一章中,你将学习关于向上转型、向下转型,以及如何实现泛型接口以及它如何帮助我们。

第三章:实现泛型接口以实现排序

在本章中,你将学习向上转型和向下转型,然后学习如何实现泛型接口以及它如何帮助我们。

想象一下,你有一个对象列表,你已经用自己的类型创建了这些对象,并且你想对它们进行排序。你需要弄清楚如何对这些对象进行排序。这来自于实现IComparable,这是一个可以作用于不同数据类型的泛型接口。

添加一个按钮来排序和显示结果

打开一个项目并点击标签。同样,你需要放入的只是一个按钮。为此,转到工具箱并拖动一个Button控件。将其放在<form id=...行下面并将按钮上的文本更改为Sort and Show。现在,在该行的末尾放入一个<br>标签,并保持标签不变:

<asp:Button ID="Button1" runat="server" Text="Sort And Show"/><br />

现在,转到设计视图,在那里你应该只看到一个名为 Sort and Show 的按钮,如图 3.3.1所示。

图 3.3.1:添加一个按钮

创建一个泛型接口类

接下来,转到解决方案资源管理器。右键点击网站的名称,选择添加,然后点击类。将类命名为GenInterface,然后点击确定。当 Visual Studio 消息出现时,点击是。记住,这只是一个例子。

GenInterface类的代码真的很复杂。我将逐行创建它,解释我在做什么以及为什么这样做。

首先,在顶部除了using System;之外删除所有内容。接下来,你将创建一个名为Quad的类,用于表示某种四边形。在using System之后输入以下内容:

public class Quad : IComparable<Quad>

这需要System,这样我们才能使用IComparable。如果你右键点击它并在下拉菜单中选择 Go To Definition(F12),你可以看到这个东西的定义。你会看到namespace System在顶部附近,以及public intCompareTo (T other);函数在Returns定义之后,如图 3.3.2所示:

图 3.3.2:IComparable 的定义

注意它返回一个整数。因此,当我们实现这个接口时,我们必须记住这一点。现在关闭定义窗口。

在我们的特定情况下,在以public class Quad...开头的行下面输入以下文本:

private string name;
public Quad(string na)

现在,为了设置值,在上述行的大括号之间输入以下内容:

name = na;

毕竟,每个四边形形状,无论是正方形、矩形还是菱形,都有一个名称,对吧?所以,在Quad类中将名称特性集中起来是个好主意。

实现接口

接下来,因为IComparable有一个函数,右键点击它,选择快速操作,然后从弹出菜单中选择实现接口。现在,我们需要编写代码。

首先删除throw new NotImplementedException()。现在,我们将以足够说明问题的方式实现接口。为此,在public int CompareTo(Quad other)行下的大括号内输入以下内容:

if(this.name.CompareTo(other.name) <0)

在这里,this表示当前对象,这个对象的名称与other.name进行比较,意思是另一个对象。看看上面一行中(Quad other)所在的地方;换句话说,我们正在比较两个Quads。因此,在左边的对象中,this是调用该函数的对象,而另一个Quad类是与之进行比较的对象。因此,如果this小于0,我们将返回一个数字,比如-1,否则它可以返回其他值,比如1。在这行下面的大括号之间输入以下内容:

{
    return -1;
}
else
{
    return 1;
} 

我们刚刚实现了CompareTo。现在注意到*this*是不必要的。你可以删除它,它仍然可以工作。但请记住,name 本质上意味着将在其中调用CompareTo的当前对象。这就是为什么我喜欢有this关键字存在,因为它更能暗示我想要知道的内容。

基本上,这行的意思是,如果当前对象与另一个名称比较小于0,那么我们返回-1,这意味着当前对象将在排序时出现在下一个对象之前。这是一个简单的解释。

添加一个虚函数

现在,在下一个阶段,我们将添加一个名为Perimeter的虚函数。为此,请在闭合大括号下面输入以下内容:

public virtual string Perimeter()

再次,我们将尽可能地集中。因此,请在此行下面的一对大括号中输入以下内容:

return $"The perimeter of {name} is ";

特定的名称可以来自这一行,因为name实例变量在private string name上面声明。然而,Perimeter将来自派生类。

现在,在前面的闭合大括号下面输入以下内容:

public class Square : Quad

添加改进

现在添加类特定的改进;为此,请在前面的行下面的一对大括号之间输入以下内容:

private double sideLength;
public Square(string n, double s) : base(n) {sideLength = s;}

在这里,string n是名称,doubles是边。然后,用名称(n)调用base类构造函数,然后输入sideLength = s。请记住,当你调用base类构造函数时,你正在重用代码。

我选择将其表达为一行,只是为了节省空间。请记住,通常它看起来是这样的:

private double sideLength;
public Square(string n, double s) : base(n)
{
    sideLength = s;
}

接下来,我们必须实现Perimeter的覆盖版本。因此,在前面的闭合大括号下面输入以下内容:

public override string Perimeter()

现在,我们想要保留return base.Perimeter(),它会自动出现,因为它提供了有用的默认功能,$"The perimeter of {name} is ";,来自前面的返回行:我们不想一直输入那个。我们想要做的是添加一个小的改进。所以,在return base.Perimeter()中添加以下改进:

return base.Perimeter() + 4 * sideLength;

这意味着四倍的sideLength变量,因为要找到正方形的周长,你要取四乘以一边的长度。

改进来自派生类的通用功能,这同样适用于你将其放入base类的虚方法中的所有类:你不必一直写它。

接下来,我们可以为我们的矩形重复这个过程。所以,复制public class Square : Quad块(Ctrl + C),然后粘贴(Ctrl + V)到下面:

public class Rectangle : Quad
{
    private double sideOne, sideTwo;
    public Rectangle(string n, double s1, double s2) : base(n)
    {
        sideOne = s1; sideTwo = s2;
    }
    public override string Perimeter()
    {
        return base.Perimeter() + (2 * sideOne + 2 * sideTwo);
    } 
}

现在进行以下更改:

  1. 将此块重命名为Rectangle。这也是从Quad派生的,所以没问题。

  2. 改变sideLength的地方;因为矩形有两个不同的边长,所以把它改成sideOnesideTwo

  3. public Square改为public Rectangle作为构造函数的名称。它调用了带有名称的基类构造函数。

  4. 然后,初始化另外两个,现在你会说double s1double s2

  5. 接下来,您必须初始化字段,所以说sideOne = s1;sideTwo = s2;。就是这样:它们已经被初始化了。

  6. 现在再次在Rectangle类中覆盖Perimeter,就像之前展示的那样。在这里,你指定适用于矩形的部分,所以是(2 * sideOne + 2 * sideTwo)。确保将其括在括号中,这样计算就会先进行,然后再与base.Perimeter的其余部分结合在一起。

所以,就是这样的类。供参考,包括注释的GenInterface类的完整版本如下所示:

using System;
public class Quad:IComparable<Quad>//implement IComparable
{
    private string name;//instance field
    public Quad(string na)
    {
        name = na;//set value of instance field
    }
    //implement CompareTo to make list sortable
    //in this case, the items are sorted by name
    public int CompareTo(Quad other)
    {
        if(this.name.CompareTo(other.name) < 0)
        {
            return -1;
        }
        else
        {
            return 1;
        }
    }//put default code inside Perimeter
    public virtual string Perimeter()
    {
        return $"The perimeter of {name} is ";
    }
}
public class Square: Quad
{
    private double sideLength;
    public Square(string n, double s):base(n)
    {
        sideLength = s;
    }
    //override Perimeter, calling the base portion
    //and then adding refinement with 4*sideLength
    public override string Perimeter()
    {
        return base.Perimeter() + 4 * sideLength;
    }
}
public class Rectangle: Quad
{
    private double sideOne, sideTwo;
    public Rectangle(string n, double s1, double s2) : base(n)
    {
        sideOne = s1; sideTwo = s2;
    }
    //override Perimeter, calling the base portion
    //and then adding refinement with 2sideOne+2sideTwo
    public override string Perimeter()
    {
        return base.Perimeter() + (2 * sideOne + 2 * sideTwo);
    } 
 }

输入参考代码

现在,我将进行参考代码。这段代码是机械的。有很多代码,但它是机械的。记住,这里的重要思想是使用Quad类内部的CompareTo方法来实现IComparable,这意味着现在当我们将不同的形状放入Quads列表中时,我们将能够以某种方式对它们进行排序。所以,现在我们的名称将被排序。在我们的情况下,我们将按名称对它们进行排序。

现在转到Default.aspx,并进入设计视图。双击“排序并显示”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。

接下来,在以protected void Button1_Click...开头的一行下的大括号之间,我们要做的第一件事是在左侧放置一个Quad,然后我们称之为sqr

Quad sqr = new Square("Square", 4);

向上转型

注意,我写了new Square。这是向上转型。在这里,这涉及到将右侧的对象转换,因为它是从其Quad派生的。在左侧,你可以创建一个Quad命名空间,并在右侧放置一个派生类型的对象;所以,我们将称之为Square,然后输入一个边长为4

接下来,在这行的正下方直接输入以下内容:

Quad rect = new Rectangle("Rectangle", 2, 5);

再次,我们在左侧放置了一个Quad命名空间,这次我们称之为rect。我们给它起名叫Rectangle,然后我们放入两条边,长度分别为25

现在,你可以将这个存储在一个列表中,例如,你可以对其进行排序。想象一下,如果你有很多这样的东西,你需要一种方法来对这些信息进行排序。所以现在,转到这个文件的顶部,在using System下面输入以下内容:

using System.Collections.Generic;

接下来,在Quad rect...行下面输入以下内容:

List<Quad> lst = new List<Quad>(new Quad[] { sqr, rect, rect2, sqr1 });

我们将称这个列表为new List<Quad>,要初始化一个列表,你总是可以使用数组。为此,输入new Quad,然后用sqrrect进行初始化。这就是你可以在数组中初始化列表的方法。

然后,为了对列表进行排序,在这行的正下方直接输入以下内容:

lst.Sort();

所以,现在这是有意义的。它不会出错。想象一下,如果你在GenInterface类的顶部没有写IComparable<Quad>。这个排序就不会起作用。如果你去掉IComparable<Quad>,然后把CompareTo方法拿出来,你会有问题。所以,对于我们的目的,我们现在有一种对这些Quads类进行排序的方法。

在最后阶段,从Sort行下面开始输入以下内容:

if(lst[0] is Square)

所以,is是一个新关键字。你可以用它来检查某个东西是否是某种类型。

向下转型

现在,我们将讨论向下转型,这意味着从父类型转到子类型。在这行下面的大括号之间输入以下内容:

sampLabel.Text += ((Square)lst[0]).Perimeter();

现在,在前一行之后的封闭大括号下面输入以下内容:

else if(lst[0] is Rectangle)

然后,你可以调用以下代码;所以,复制sampLabel.Text...行,并将其粘贴在一对大括号之间:

sampLabel.Text += ((Rectangle)lst[0]).Perimeter();

确保将Square更改为Rectangle,这样它就会被转换为矩形,然后矩形上的Perimeter函数将被调用。当你将鼠标悬停在前两行的Perimeter上时,弹出窗口显示string Square.Perimeter()string Square.Perimeter()。如果你从前一行中删除(Rectangle)并将鼠标悬停在Perimeter上,弹出窗口将显示string Quad.Perimeter()。你明白吗?这就是为什么我有这个转型:因为它改变了函数被识别的方式。

这是从父类向子类的向下转型。所以,当我们谈论批量操作时,你不能转型为父类,执行类似排序的批量操作,或者如果你想添加称为子类和子类对象的细化,那么你可以向下转型。

运行程序

现在,让我们来看看。在浏览器中打开程序,然后点击“排序并显示”按钮。结果显示在图 3.3.3中:

图 3.3.3:运行程序的结果

这确实是矩形的周长。

这些是向上转型、向下转型和实现通用接口(如IComparable)的基础知识。这是非常复杂的代码,但我希望您已经学到了很多。

章节回顾

为了复习,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        sampLabel.Text = "";//clear label every time
        Quad sqr = new Square("John",4);//make a square
        Quad rect = new Rectangle("Bob", 2, 5);//make a rectangle
        Quad rect2 = new Rectangle("Jerry", 4, 5);//make another rectangle
        //stick all these shapes into a list of quads
        List<Quad> lst = new List<Quad>(new Quad[] { sqr, rect,rect2});
        lst.Sort();//sort the list
        if(lst[0] </span>is Square) //if it's asquare
        {
               //down cast to a square, and call Perimeter on it
                sampLabel.Text += ((Square)lst[0]).Perimeter();
        }
        else if(lst[0] is Rectangle)
        {
            //if it's a rectangle, down cost to a rectangle, 
            //and call Perimeter
            sampLabel.Text += ((Rectangle)lst[0]).Perimeter();
        }
    }
} 

摘要

在本章中,您学习了向上转型、向下转型,然后学习了如何实现通用接口以及这对我们有何帮助。您创建了一个通用接口类和一个 Quad 类,实现了一个接口,添加了一个虚拟的Perimeter函数,对代码进行了改进,并输入了大量的机械引用代码。

在下一章中,您将学习有关通用委托的知识。

第四章:使用泛型使委托更加灵活

在本章中,你将学习关于泛型委托。记住,就像在之前的课程中一样,基本的好处是泛型允许你创建灵活的代码,可以轻松处理各种数据类型。如果没有泛型,你将不得不创建更多的代码。

在 HTML 中添加一个总结按钮

打开一个项目。在基本的 HTML 中,删除<div>行,因为你不需要它们。现在,让我们添加一个按钮。这个按钮唯一要做的就是为我们总结一些信息。

转到工具箱,抓取一个Button控件。将其拖放到以<form id=...开头的行下面,并将按钮上的文本更改为Summarize。现在,用一个<br>标签关闭它,并像往常一样保留Label控件。

现在,转到Default.aspx,进入设计视图。你会看到一个按钮用于界面,上面写着 Summarize,看起来像图 4.4.1

图 4.4.1:这个项目的简单界面

现在,双击Summarize按钮。这会带我们进入Default.aspx.cs。删除Page_Load块。你的这个项目的初始代码屏幕应该看起来像图 4.4.2

图 4.4.2:这个项目的初始 Default.aspx.cs 代码

构造委托

首先,为了创建委托,在以public partial class...开头的行上方输入以下内容:

public delegate void Summarize<T>(T x, T y);

这里,public表示可以在任何地方访问,delegate是一个关键字,void不返回值。委托名称是Summarize,它可以作用于不同的数据类型,因为T存在,而不是整数、双精度或类似的东西。T是一个通用类型。

现在记住,委托本质上是函数包装器。对吧?你用它们指向多个函数,这样你就可以级联函数调用,例如。同样的原则在这里也适用。例如,为了利用这一点,输入以下内容在以protected void Button1_Click...开头的行下面的大括号之间:

Summarize<double> s =

分配函数来代表委托

对于右侧,我们首先需要开始分配它所代表的函数。为此,我们可以在这行之后的闭合大括号下面,例如,说以下内容:

public void FindSum(double x, double y)

想象一下,你要做的第一件事是找到两个值的和。所以,例如,你说Find Sum,然后double xdouble y

然后,你可以更新标签;所以,在这行下面的大括号之间输入以下内容:

sampLabel.Text = $"<br>{x}+{y}={x + y}";

现在,你可以将FindSum分配给前面的<int>。你可以设置它等于,如下所示:

Summarize<double> s = FindSum;

当然,还有许多其他操作可以执行。所以,让我们来看看这段代码:这是一个基本的添加函数,并定义一些其他操作。复制(Ctrl + C)这两行,然后粘贴(Ctrl + V)到下面。这次,将FindSum改为FindRatio,并基本上按照相同的计划进行。我们将应用+=运算符来确保它在追加。然后,为了换行,在那里放一个<br>标签,而不是x + y,将它们改为x / y。当然,在这里你需要确保y不是0。我们可以弄清楚这一点:

public void FindRatio(decimal x, decimal y)
{
    sampLabel.Text += $"<br>{x}/{y}={x / y}";
}  

再做一次。所以再次复制这两行,然后粘贴到下面。这次,将FindRatio改为FindProduct,这是两个值相乘的结果,并将x / y改为x * y

public void FindProduct(decimal x, decimal y)
{
    sampLabel.Text += $"<br>{x}*{y}={x * y}";
}

提醒:如果是棕色(Windows)或橙色(Mac),它会在屏幕上显示为原样。

始终记得放入<br>标签,这样东西就会被推到下一行。

调用委托

现在,我们需要堆叠这些调用;所以,在Summarize<double> s = FindSum;行下面输入以下内容:

s += FindRatio;
s += FindProduct;

注意,你放下一个函数名,FindRatio,然后下一行将是FindProduct

然后,当然,要调用它,输入以下内容在下一行:

s(4, 5); 

这是你如何调用那个代理的方式:你会调用它,指定名称,然后传入那些45的值。

Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public delegate void Summarize<T>(T x, T y);//declare generic delegate
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        Summarize<decimal> s = FindSum;//assign FindSum to the delegate
        s += FindRatio;//assign FindRatio to the delegate
        s += FindProduct;//assign FindProduct to the delegate
        s(4, 5);//invoke the delegate, causing the chain of functions to be executed
    }
    public void FindSum(decimal x, decimal y)
    {
        sampLabel.Text = $"<br>{x}+{y}={x + y}";
    }
    public void FindRatio(decimal x, decimal y)
    {
        sampLabel.Text += $"<br>{x}/{y}={x / y}";
    }
    public void FindProduct(decimal x, decimal y)
    {
        sampLabel.Text += $"<br>{x}*{y}={x * y}";
    }
}

运行程序。

现在,让我们来看看效果。为此,启动你的浏览器中的程序。点击 Summarize 按钮。结果显示在图 4.4.3中:

图 4.4.3:运行我们项目的结果

正如你所看到的,4+5=9,4/5=0.8,4*5=20。所以,它的工作效果符合预期。public delegate void Summarize<T>(T x, T y);这一行是一个单一的通用代理,因为它有T而不是固定的数据类型,比如整数或双精度,所以它可以操作不同的数据类型。

现在,如果你打开你的Default.aspx.cs页面,搜索所有的double出现的地方,然后用int替换,会有七个地方被替换。如果你再次运行代码,你会发现它同样有效。为了进一步说明这一点,将int替换为decimal,同样有七个地方被替换。现在,它将以十进制类型运行,如果你再次点击 Summarize 按钮,你会发现它同样有效。

所以,你有一个通用的代理。记住,通过单击一个按钮,你可以通过s代理将一系列函数链接在一起调用,这个代理是 Summarize 类型的,是通用的,所以它可以同样有效地操作不同的数据类型。

总结

在本章中,你学习了关于通用代理。你构建了一个代理,分配了函数来代表这个代理,并调用了这个代理。

在下一章中,你将学习关于通用字典。

第五章:创建和使用通用字典

在本章中,您将学习有关通用字典的知识。字典用于存储键值对

在 HTML 中添加一个显示按钮

打开 Visual Studio。我们需要在中放置一个按钮;因此,转到工具箱并获取一个Button控件。将其拖放到以<form id=...开头的行下方。更改按钮上的文本为Show。您可以删除<div>行,因为您不需要它们。

从网页启动进程

在本章中,我们将学习如何打开记事本,然后直接从页面上进行探索。为此,转到Default.aspx,并进入设计视图。双击显示按钮。这将带我们进入Default.aspx.cs

删除Page_Load块,使其看起来像图 5.5.1所示的屏幕:

图 5.5.1:此项目的初始 Default.aspx.cs 代码

首先,在using System行下面,输入以下内容:

using System.Collections.Generic;

您添加了这行是因为我们正在处理字典。然后,在此行下面再添加一行:

using System.Diagnostics;

很快您将看到为什么需要这行。这是如何启动一个进程的。例如,进程指的是记事本。

现在,在以protected void Button1_Click...开头的行下面的大括号之间,输入以下内容:

Dictionary<string, string> filePrograms = new Dictionary<string, string>();

将鼠标悬停在Dictionary上,并查看弹出提示,如图 5.5.2所示。您看到了吗?它说 TKey?这表示键的类型,TValue 指定值的类型。在我们的情况下,两者都将是string类型:

图 5.5.2:字典的弹出提示

请注意,我们给字典起了一个名字,比如filePrograms。接下来,为它分配内存,输入new Dictionary<string, string>来指示键的类型和值的类型,并用分号结束。

在下一阶段,我们将填充这个字典。因此,在这行的下面直接输入以下内容:

filePrograms.Add("notepad.exe", @"c:\data\samplefile.txt");

创建一个逐字字符串

在前一行中,您需要在括号内指定键值对。键是notepad.exe。如果您尝试直接将路径(例如c:\data\samplefile.txt)放入代码中,它不起作用。您看到它被划为红色了吗?弹出窗口表示将文本表示为一系列 Unicode 字符。这些东西不起作用。因此,为了解决这个问题,您在其前面放置@(at)符号。现在,您有了一个逐字字符串

用旧的方法,您处理这个问题的方式是:c:\\data\\samplefile.txt。这被称为字符转义。如果您尝试使用前面的行,注意红色的下划线消失了,因为c:\已经有了意义。因此,为了避免通常的含义,您添加第二个反斜杠。不过,这是旧的方法。对于这种情况的新方法是使用逐字字符串,以便它被解释为它出现的样子。

在这个上下文中,记事本是键,值是samplefile.txt文件。

接下来,在前一行的下面直接输入以下内容:

filePrograms.Add("iexplore.exe", "http://www.bing.com");

因此,Internet Explorer 将打开www.bing.com页面。您看到了吗?

遍历键值对

现在,假设我们想要遍历这些键,因为我们可能有很多键。一种方法是这样的:

foreach(KeyValuePair<string, string> kvp in filePrograms)

您可以像这样遍历键值对。接下来,在前一行下面的大括号之间输入以下内容:

Process.Start(kvp.Key, kvp.Value);

Process.start之后,您显示键和值。因此,您可以说kvp.key,这是键值对的属性,kvp.value也是键值对的属性。

在一个现实的应用程序中,程序可能丢失或其他情况可能发生,最好将其放在TryCatch块中等等,但对于我们的目的来说,这已经足够了,而且保持了简洁。

如果需要,您还可以遍历单个键和值等。因此,作为另一个示例,您可以在前一行下的封闭大括号下方输入以下内容:

foreach(string key in filePrograms.Keys)

要获取单个键,您需要键入字典的名称,然后键入键集合的名称Keys,该名称将出现在弹出窗口中。

这就是您可以访问键的方法。如果要显示它们,您绝对可以;要执行此操作,请在此行下的一组大括号之间输入以下内容:

sampLabel.Txt += $"<br>{key}";

要显示键,您需要键入{key}。记得插入<br>标签,+=运算符进行追加,$符号,并以分号结尾。

从命令提示符中创建目录和文件

现在,您需要确保您有samplefile.txt文件。因此,在 Windows 中,要执行此操作,请键入cmd,这是 Command Prompt 的缩写,并打开它。在C:\>提示符下,首先键入cd..以向上移动一级,然后再次键入cd..以再次向上移动一级。然后输入cd data以切换到 data 目录。系统会响应此路径不存在,如图 5.5.3所示;因此,我们需要创建路径并创建文件:

图 5.5.3:系统指示路径 c:>data 不存在

要创建路径,请在命令提示符中键入以下内容:

C:\>md data

然后,输入以下内容以切换到该目录:

C:\>cd data

接下来,键入以下内容以显示目录中的文件列表:

C:\data\dir

图 5.5.3中所示,目录中没有任何内容:它是新的,我们刚刚创建它。因此,要在 Notepad 中打开文件,请在提示符中键入以下内容:

C:\data\notepad.exe samplefile.txt

这将创建文件。当屏幕提示询问您是否要创建新文件时,请单击“是”按钮。这将打开一个空的 Notepad 文件。输入一些文本,如图 5.5.4所示:

图 5.5.4:在您创建的新 Notepad 文件中输入一些文本

确保将其保存在C:\data目录中(Ctrl + S),然后可以关闭它。

现在看一下。要调用以前的命令,您只需按上箭头键,或者在这种情况下,在命令提示符中键入dir,如前所述:C:\data\dir。现在您可以看到samplefile.txt存在于目录中,如图 5.5.5所示:

图 5.5.5:samplefile.txt 现在存在于目录中

现在在浏览器中启动它,并查看结果。单击“显示”按钮。它按预期工作:它同时打开了 Notepad 文件和 Bing 主页,如图 5.5.6所示:

图 5.5.6:Notepad 文件和 http://www.bing.com 已在我们的程序中打开

现在,您可以直接从您的页面启动任何浏览器,某种进程,并显示它,如果需要,还可以直接从浏览器中打开文件。

请注意,这是因为我们正在从本地 Web 服务器运行页面,因此我们可以访问 Notepad 和浏览器。在真正的互联网应用程序中,情况会有所不同。

此外,您可以查看键的列表,如图 5.5.7所示:

图 5.5.7:键的列表

因此,这些是您可以使用通用字典的一些基础知识。

章节回顾

回顾一下,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;//needed for EventArgs
using System.Collections.Generic;//needed for dictionary
using System.Diagnostics;//needed for Process.Start
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make a dictionary using string as the type for keys and values
        Dictionary<string, string> filePrograms = 
        new Dictionary<string, string>();
        //add two key/value pairs to the dictionary
        filePrograms.Add("notepad.exe", @"c:\data\samplefile.txt");
        filePrograms.Add("iexplore.exe", "http://www.bing.com");
        //iterate over the key/value pairs
        foreach(KeyValuePair<string, string> kvp in filePrograms)
        {
            //invoke Process.Start to launch notepad and internet explorer
            Process.Start(kvp.Key, kvp.Value);
        }
        //lines below get only at the key inside the filePrograms 
        //dictionary
        foreach(string key in filePrograms.Keys)
        {
            sampLabel.Text += $"<br>{key}";
        }
    }
}

总结

在本章中,您了解了通用字典。这些被称为键值对。您从网页启动了一个进程,制作了一个逐字字符串,遍历了键值对,从命令提示符中创建了一个目录并创建了一个文件。

在下一章中,我们将看一下委托和 lambda 表达式之间的连接。

第六章:委托和 Lambda 表达式之间的连接

在本章中,我们将看一下委托和 Lambda 表达式之间的连接。

在 HTML 中添加一个显示结果的按钮

打开一个项目,在中放入一个显示结果的按钮。要做到这一点,去工具箱中找到Button控件。将其拖放到以<form id=...开头的行下面。你可以删除<div>行,因为你不需要它们。确保在按钮行的末尾插入一个<br>标签:

<asp:Button ID="Button1" runat="server" Text="Show Results" /><br />

我将做一些大杂烩的事情,只是为了向你展示不同的概念。

转到设计视图,并双击显示结果按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的初始代码屏幕应该如图 6.1.1所示:

图 6.1.1:该项目的初始 Default.aspx.cs 代码

添加委托

在第一阶段,你需要添加委托。虽然你可以把它们放到一个单独的文件中,但为了我们的目的,让我们把它们放在这里。因此,在以public partial class...开头的行上面输入以下内容:

public delegate bool Compare(double x, double y);

记住,委托实际上是函数或方法的包装器。然后,在这一行的正下方,输入以下内容:

public delegate double Multiply(double x, double y);

你可以看到我们有两个委托。一个返回Boolean数据类型,另一个返回double数据类型。

设置变量

接下来,在Button1_Click的事件处理程序中,我们将创建两个变量:x(设置为10)和y(等于25)。因此,在大括号之间输入以下内容:

double x = 10, y = 25;

创建委托类型的对象

现在,我们要在上一行下面输入以下内容:

Compare comp = (a, b) => (a == b);

当你开始输入Compare时,注意到从弹出窗口,一旦你有了一个委托(Compare),实质上,你可以创建那种对象;然后,输入comp

定义 Lambda 表达式

现在,要定义一个 Lambda 表达式,你需要输入= (a,b),如所示。然后这将被映射到后面的操作;所以你可以把=>看作映射符号或映射操作符。它将被映射到(a==b)操作。因此,换句话说,comp将允许我们检查这两个值是否相同,这发生在ab被比较的阶段。基本上,(a, b)是参数,被评估的表达式是a是否等于b

接下来,输入以下内容:

sampLabel.Text = $"{x} and {y} are equal is {comp(x, y).ToString().ToLower()}";

要调用这个,注意你要输入comp,然后传入xy的值。然后,为了显示你可以进一步操作它,一旦你得到了结果,你可以将其转换为字符串版本,然后全部转换为小写,就像前面的代码行所示。

记住,这是函数链接,所以它从左到右执行。换句话说,首先运行comp,然后是ToString,最后是ToLower

另外,请注意,在运行时,当你传入xy值时,调用comp(x, y),基本上就是(a==b)会被触发;比较将会被执行,并且值将被返回。

接下来,我们还可以做Multiply委托,所以在这一行下面输入以下内容:

Multiply mult = (a, b) => (a * b);

注意到(a,b)可以被使用和重复使用等等。记住,这里的(a,b)是参数,你可以使用它们并重复使用它们。它们在出现的每一行中都是局部的。因此,你可以在另一行中使用它。然后,你再次说(a,b)映射到(a*b)的操作。以分号结束。

现在,要调用这个乘法委托(它代表的 Lambda 表达式),从上面复制(Ctrl + CsampLabel.Text行,然后粘贴(Ctrl + V)到下面,如下所示:

sampLabel.Text += $"<br>{x}*{y} is {mult(x, y).toString()}";

在这里,我们说{x}*{y},然后+=追加,并删除are equal,并用我们对象的名称mult替换comp。你不需要toString让它工作,而且因为它会返回一个数字,你也不需要ToLower

操作数组

现在,在下一个阶段,你可以做的另一件事是操作一个数组。例如,你可以创建一个双精度数组。我们将其称为dubsArray,这将是一个新的双精度数组。要做到这一点,在下一行输入以下内容:

double[] dubsArray = new double[] { 1, 2, 3, 4, 5 };

使用操作

现在,我们将讨论操作,所以在下一行输入以下内容:

Action<double> showDouble = (a) => sampLabel.Text += "<br>" + (a * a);

注意,“操作”是一个代理。所以,如果你右键单击“操作”并选择“转到定义”,你会看到public delegate void Action()。如果你展开它,它说,封装了一个没有参数并且不返回值的方法。这是.NET 中操作的基本定义。

但是,你可以扩展一个“操作”代理。它们可以是通用的。例如,如果你输入Action<double>,然后右键单击它并再次选择“转到定义”,这个特定形式确实需要一个参数。如果你展开它,参数部分说,这个代理封装的方法的参数。此外,“摘要”部分说,封装了一个具有单个参数并且不返回值的方法。所以,再次强调,没有必要猜测。右键单击并选择“转到定义”或将鼠标悬停在上面。它会告诉你需要知道的内容。在我们的情况下,实际上将是在前一行中看到的showDouble。现在,另一个 lambda 可以用来定义这个;所以你在那里插入(a)作为单个参数,然后输入映射符号=>,然后是sampLabel.text。你想要将这个附加到现有的文本上,所以输入+=,然后说,<br>,然后显示a的平方,输入+ (a * a)并以分号结束。

现在记住从“操作”的定义中,它们不返回值,对吧?事实上,如果我们输入“Action”,并查看弹出提示,如果你浏览整个列表直到 T16,它说,封装了一个具有 16 个参数并且不返回值的方法,如图 6.1.2所示:

图 6.1.2。在输入 Action后,没有一个操作返回值,

所以,它们都不返回值。这是“操作”作为它们在这里定义的一个基本特性,但请记住,最终它只是一个代理。

然后,例如,要利用这些“操作”,你可以做的一件事是输入以下内容:

foreach(var d in dubsArray)

在下一个阶段,在这行下面的花括号之间输入以下内容来调用这些操作:

showDouble(d);

这些是使用代理和 Lambda 表达式的基础。文件顶部的两个代理是我们程序的核心,然后是CompareMultiply,它们是下面使用的代理类型,然后是使用这些代理定义的 Lambda 表达式,如(a, b) => (a == b)(a, b) => (a * b),和(a) => sampLabel.Text += "<br>" + (a * a)

现在,用浏览器查看一下。点击“显示结果”按钮。它说,10 和 25 相等是假的,10*25 是 250,然后打印出了平方。这些是基本的结果,一切看起来都是应该的:

图 6.1.3。运行我们的程序的结果

章节回顾

出于复习目的,包括注释的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public delegate bool Compare(double x, double y);
public delegate double Multiply(double x, double y);
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        double x = 10, y = 25; //declare two variables
        //the two variables are accessible inside the lambda expressions
        Compare comp = (a, b) => (a == b);//define comparison lambda
        //invoke the lambda in the line below
        sampLabel.Text =
         $"{x} and {y} are equal is {comp(x, y).ToString().ToLower()}";
        //line define a lambda for multiplication
        Multiply mult = (a, b) => (a * b);
        //invoke the multiplication lambda
        sampLabel.Text += $"<br>{x}*{y} is {mult(x, y)}";
        //make array of doubles
        double[] dubsArray = new double[] { 1, 2, 3, 4, 5 };
        //actions encapsulate functions that do not return a value
        //but actions can accept arguments to operate on
        Action<double> showDouble = 
        (a) => sampLabel.Text += "&lt;br>" + (a * a);
        //it's now possible to perform the action on each d repeatedly
        foreach (var d in dubsArray)
        {
            showDouble(d);
        }
    }
}

总结

在本章中,你学习了代理和 lambda 表达式之间的关系。你添加了代理,设置了项目变量,创建了代理类型的对象,操作了一个数组,并使用了“操作”。

在下一章中,你将学习关于表达式主体成员以及由代码块定义的 lambda 表达式。

第七章:表达式主体 Lambda 和表达式主体成员

在本章中,您将学习关于表达式主体成员,然后是由代码块定义的 Lambda 表达式。

在 HTML 中添加一个框和一个 Find Max 按钮

打开一个项目,我们将设置一个框,从该框中读取三个值,然后找到最大值。我们还将做一些其他事情,比如学习如何从一个数据类型的数组转换为另一个数据类型的数组。

让我们从在以<form id=...开头的行下方输入Enter Values:开始:

然后,转到工具箱,获取一个Textbox控件,并将其放在 Enter Values:之后。您可以删除<div>行,因为您不需要它们。确保在行末插入<br>标签:

Enter Values:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br />

在下一阶段,您将插入一个Button控件;所以从工具箱中获取一个并将其放在此行下方。将按钮上的文本更改为 Find Max。再次以<br>标签结束该行:

<asp:Button ID="Button1" runat="server" Text="Find Max" /><br />

您的项目的 HTML 文件应该像图 7.2.1那样:

图 7.2.1:该项目的 HTML 文件

现在转到设计视图。我们现在只有一个框和一个按钮,如图 7.2.2所示:

图 7.2.2:我们在设计视图中的简单界面

接下来,双击 Find Max 按钮转到Default.aspx.cs文件,并删除所有内容。本章中的代码可能会有些复杂,也许比以前的章节更具挑战性,但这是成长和前进的最佳方式。我将逐行讲解代码的构建过程。到目前为止,您应该能够看到开始良好编程所需的条件以及您需要了解的内容。

创建委托

像往常一样,在文件的顶部输入using System。接下来,要创建一个委托,请输入以下内容:

delegate double CompareValues(double x, double y);

在这一行中,您有一个delegate类。它返回一个double并接受两个double数据类型。因此,它封装了具有这种签名的函数。

在下一阶段,您将在大括号内输入以下内容:

public partial class_Default: System.Web.UI.Page

这一行像往常一样继承自Page

定义一个表达式主体成员

在下一阶段,我们将开始定义一个表达式成员,因此在一对大括号之间输入以下内容:

double FromStringToDouble(string s) => Convert.ToDouble(s);

这一行展示了创建函数的一种新方法。这本质上就是这样。现在,您可以不在行内放大括号,而是可以直接放一个 Lambda 表达式,例如在这种情况下是=>。然后要转换为double数据类型的是s字符串。它也更加简洁;看起来更现代化,像一个表达式主体成员,像一个函数。请记住,函数是类的成员。

因此,在下一阶段,我们将在此行下方定义按钮点击事件。如果您返回到设计视图并双击按钮,它将自动插入以下行:

protected void Button1_Click(object sender, EventArgs e)

接下来,在上一行下方的大括号之间输入以下内容:

string[] vals = TextBox1.Text.Split(new char[] { ',' });

将字符串数组转换为双精度数组

接下来,让我们使用不同的方法将字符串数组转换为双精度数组;为此,请在此行下面输入以下内容:

double[] doubleVals = Array.ConvertAll(vals, new Converter<string, double>(FromStringToDouble));

注意ConvertAll方法。它并不容易使用。您需要有一个要操作的数组。因此,在这种情况下,数组称为vals,然后需要有一个称为转换器对象的东西(请注意弹出窗口显示Converter<TInput, TOutput> converter>)。要创建一个转换器,您输入new Converter,然后在这种情况下,您将把一个字符串数组转换为双精度数组。因此,字符串是您要转换的内容,双精度是您要转换的类型。这个新的转换器实际上只是包装了一个函数调用,因此在那之后,您输入(FromStringToDouble)

前一行将完成将数组从一种数据类型转换为另一种数据类型。记住,最终它会获取每个值并使用顶部附近的Convert.ToDouble(s)进行转换。

接下来,输入以下内容:

CompareValues compareValues = (xin, yin) =>

在这里,CompareValues是一个委托类型——就像一个数据类型——我们将其命名为compareValues,然后定义一个新的 Lambda(xin, yin)=>

创建一个表达式体 lambda

接下来,你将定义 Lambda 的主体。因为这个 Lambda 将做几件事,你可以将它的主体放在一对大括号中,如下所示:

{
    double x = xin, y = yin;
}

因此,这一行将分配上面参数的值。

接下来直接在这行下面输入以下内容:

return x > y ? x : y;

因此,如果x大于y,则返回x;否则,返回y。这是一个表达式体 Lambda,在结束时使用封闭的大括号后加上分号,就像这样};。正如你所看到的,这个 Lambda 表达式跨越了多行。因此,你可以像前一行一样再次内联代码,使用double FromStringToDouble(string s) => Convert.ToDouble(s);函数。

比较值

在下一个阶段的过程中,我们将比较值。为此,在上一行的封闭大括号/分号之后输入以下内容:

sampLabel.Text = CompareValuesInList(compareValues, doubleVals[0], doubleVals[1], doubleVals[2]).ToString();

在这里,CompareValuesInList是一个你可以创建的函数。然后你将传入compareValues。换句话说,当这一行说compareValues时,整个上面的CompareValues块将被传递到函数的主体中。你以前从未这样做过。你正在传递整个代码块!接下来,你输入doubleVals[0]来获取第一个值,然后你可以复制(Ctrl + C)并重复这个操作,索引为 1 的值为doubleVals[1],索引为 2 的值为doubleVals[2],因为有三个值。

指定参数

现在,在下一个阶段,在上一行之后的封闭大括号下面,输入以下内容:

static double CompareValuesInList(CompareValues compFirstTwo, double first, double second, double third)

CompareValuesInList之后,你将指定参数。所以,第一个参数将是CompareValues。这表明委托也可以用作参数的类型。我们将其命名为compFirstTwo。然后,你做double firstdouble seconddouble third参数。所以,有三个值要传入。

接下来,在上一行之后的一对大括号中输入以下内容:

return third > compFirstTwo(first, second) ? third : compFirstTwo(first, second);

这一行的意思是,如果third大于比较前两个compFirstTwo(first, second)参数的结果(记住,这个表达式会先运行,然后返回一个比较前两个的值),那么它返回third;否则,它会再次运行compFirstTwo并返回这两个中较大的一个。

运行程序

你在这里看到的是非常复杂的代码。现在在浏览器中加速它,并查看结果。输入一些值,比如15-3,然后单击“查找最大”按钮。结果是 5,如图 7.2.3所示:

图 7.2.3:使用纯整数值运行程序的初步结果

接下来,输入诸如1.011.020.9999之类的内容,然后单击“查找最大”按钮。结果是 1.02,如图 7.2.4所示:

图 7.2.4:使用扩展小数值运行程序的结果

因此,程序正在按预期工作。

再次回顾一下,因为这里有很多代码,我们在这个程序中做了以下工作:

  1. 首先,我们声明了一个委托。

  2. 然后我们声明了一个表达式体成员,在这段代码的上下文中,它是一个基本上用 Lambda 定义的函数。

  3. 接下来,我们创建了一个值数组。

  4. 然后我们创建了一行来将值从string类型转换为double类型。

  5. 之后,我们创建了一个表达式体 Lambda。

  6. 然后我们构建了一个名为CompareValuesInList的函数,它将 Lambda 作为参数,然后还会查看其他值。

  7. 最后,CompareValuesInList是魔术真正发生的地方,因为它说,如果third值比前两个比较的值都大,那么你就返回third值。然而,如果不是,那么就简单地返回前两个中较大的那个。

我知道这似乎不是一件容易的事情。我知道这是因为我以前做过这个。然而,你必须绝对添加这个编码水平。输入它,运行它,处理它;然后你会很快发展你的理解。这些是使一些东西有用的基础。

章节回顾

回顾一下,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

using System;//needed for array, Convert, and Converter
delegate double CompareValues(double x, double y);//delegate for defining expression bodied lambda
public partial class _Default :System.Web.UI.Page
{
    double FromStringToDouble(string s) => Convert.ToDouble(s);//expression bodied function member
    protected void Button1_Click(object sender, EventArgs e)
    {
        //split entries into array of strings
        string[] vals = TextBox1.Text.Split(new char[] { ',' });
        //line 10 below converts all strings to doubles using the 
        //vals array, and a new Converter object
        //which really just calls FromStringToDouble
        double[] doubleVals = 
        Array.ConvertAll(vals, new Converter<string, double>(FromStringToDouble));
        //lines 13-17 define the expression bodied lambda, this one compares two 
        //values and returns the bigger
        CompareValues compareValues = (xin, yin) =>
        {
            double x = xin, y = yin;
            return x > y ? x : y;
        };
        //line 19 invokes CompareValuesInList, which needs the lambda, and 
        //list of values to compare
        sampLabel.Text = 
        CompareValuesInList(compareValues, doubleVals[0], doubleVals[1],doubleVals[2]).ToString();
    }
    //lines 22-25 below return either third value if it's biggest, 
    //or one of the other two
    static double CompareValuesInList(CompareValues compFirstTwo, double first, double second, double     third)
    {
        return third > compFirstTwo(first, second) ? third : compFirstTwo(first, second);
    }
}

摘要

在本章中,你学习了表达式主体成员,然后是 Lambda 表达式,它们由代码块定义。你创建了一个委托,定义了一个表达式主体成员,将字符串数组转换为双精度数组,创建了一个表达式主体的 Lambda,并构建了比较值和指定参数的代码。

在下一章中,你将学习有关匿名函数的知识。

第八章:匿名方法和运行其自己委托的对象

在本章中,我们将讨论匿名函数。

将“显示结果”按钮添加到 HTML

打开一个项目,在标签内放入一个写着“显示结果”的按钮。为此,转到工具箱并获取一个“按钮”控件。将其拖放到以<form id=...开头的行下方。您可以删除

行,因为您不需要它们。确保在按钮行的末尾插入
标记。

<asp:Button ID="Button1" runat="server" Text="Show Results" /><br />

接下来,我们将向用户显示一些结果。要做到这一点,请转到设计视图,并双击“显示结果”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 8.3.1所示:

图 8.3.1:该项目的起始代码部分

简化编写函数

在主体内,但在以protected void Button1_Click...开头的行之上,输入以下内容:

private void ShowSquare(double x) => sampLabel.Text += "<br>" + (x * x);

请记住,=>是一个表达式成员。它是一个函数。换句话说,它采用 Lambda 的形式。在行的末尾,我们返回x * x。正如您所看到的,这是编写函数的一种非常简化的方式。

接下来,我们需要添加命名空间。因此,在using System之后,输入以下行:

using System.Collections.Generic;
using System.Threading;

现在,在按钮的事件内,我们将放置以下代码列表;因此,在以protected void Button1_Click...开头的行下方的一组大括号之间输入此行:

List<double> vals = new List<double>(new double[] { 1, 2, 4, 5, 6, 8 });

在这一行中,您正在创建一个新的double数据类型列表,然后将对其进行初始化。您可以以几种方式做到这一点,但您可以只需编写一个数组,然后输入一些值。这些值并不重要。这将创建一个double数据类型的列表。

对所有值执行操作

现在,您可以对所有值执行操作。因此,要执行此操作,输入以下内容:

vals.ForEach(ShowSquare);

这是如何对每个值调用ShowSquare的方法。请注意,在这种情况下,ShowSquare是有名字的。ShowSquare代表这个表达式,sampLabel.Text += "<br>" + (x * x);所以它是一个有名字的数量

创建匿名函数或方法

现在,如果您愿意,您还可以做一些不涉及名称的事情。例如,您可以输入以下内容:

vals.ForEach(delegate (double x)

接下来,我们将定义一组大括号之间的主体或逻辑。这是一个无名或匿名的。例如,您可以在此行下方输入以下内容(请注意,在关闭大括号后用括号和分号结束):

{
    sampLabel.Text += "<br>" + Math.Pow(x, 3);
});

这一行与前一行做的事情类似。唯一的区别是我们没有调用任何有名字的东西;我们只是使用delegate关键字定义了一个匿名函数,一个无名函数。当然,这会接受一个值,即x值。然后你对x值进行立方;Math.Pow(x, 3)的意思是,立方然后使用+=将其附加到标签上,并使用<br>将其推到下一行,如常规操作。

现在,在下一个阶段,您还可以执行以下操作,这是非常有趣的:

Thread td = new Thread(delegate ())

信不信由你,虽然这并不被推荐,但在new Thread之后,您甚至可以输入dele而不是delegate

现在,当您创建这种类型的对象时,您还可以创建一个委托。因此,当您创建这个Thread对象时,您也在创建一个匿名函数。换句话说,您正在发送一段处理,以便它在自己的线程上运行,然后您可以插入以下内容:

{
List<double> arrs = new List<double>(new double[] { 1, 4, 5, 3, 53, 52 });arrs.Sort();arrs.ForEach(x => sampLabel.Text += $"<br>{x}");
}); 

再次注意,在这里,您在关闭大括号后用括号和分号结束。

启动线程

现在,像这样使用线程,您可以在下一行启动一个线程,如下所示:

td.Start();

这将在自己的小独立处理中启动线程,与主程序分开。

因此,这里的重要思想是,这种匿名的东西非常强大。例如,您可以构建一个匿名函数或方法,就像我们创建的前面的那个一样。它可以运行,但没有命名,基本上,即使您创建了一个新的Thread对象,也可以创建一个委托。换句话说,它可以进行一些自己的处理,您不必将其放入其他函数或任何其他地方。

运行和修改程序

现在,让我们运行程序。为此,请在浏览器中启动并单击“显示结果”按钮。查看结果,如图 8.3.2所示。程序存在一个小问题。我们将很快了解问题的原因,然后解决它:

图 8.3.2:我们程序的初始运行

现在,我还想告诉您另一个函数,Join。输入以下内容作为下一行:

td.Join();

现在,如果您将鼠标悬停在Join上,弹出提示会说阻止调用线程,直到线程终止,同时继续执行标准 COM 和 Send,消息泵。如果您将鼠标悬停在Start上,弹出提示会说导致操作系统将当前实例的状态更改为 ThreadState.Running。换句话说,在Thread td = new Thread(delegate ()块中,Thread是一个对象。在这种情况下,您正在创建一个具有委托的新线程,因此它在自己的处理线程中运行,远离主程序。所以,这有点有趣。

现在请注意,当我们打印这些内容时,实际上只有两个主要列表,第二个列表基本上附加到第一个列表上。因此,让我们这样做;否则,我们将无法清楚地看到效果。在前面的vals.ForEach(ShowSquare)行下面,输入以下内容:

sampLabel.Text += "<br>------------------------------------------------------";

请注意,我用引号中的长破折号分隔了这一点。

接下来,在这个之后,让我们在sampLabel.Text += "<br>" + Math.Pow(x, 3)行的闭合大括号、括号和分号下面再做一次。

sampLabel.Text += "<br>-------------------------------------------------------";

现在,如果我们删除td.Join()并运行程序,只有两个列表,如图 8.3.3所示。然而应该有三个:

图 8.3.3:修改后的运行只显示两个列表

因此,重新插入td.Join();并在浏览器中再次查看。现在,正如图 8.3.4所示,有三个列表,正如应该有的那样:

图 8.3.4:最终程序运行显示三个单独的列表

再次回顾,我们在本程序中做了以下工作:

  1. 首先,我们调用了vals.ForEach(ShowSquare),生成了一个列表。

  2. 然后我们调用了以vals.ForEach(delegate (double x)开头的块,作为生成列表的匿名函数或方法。

  3. 接下来,我们使用以Thread td = new Thread(delegate ()开头的块创建了这个匿名对象td,它是一个具有自己匿名方法的Thread类,运行在自己独立的线程中。

  4. 最后,我们启动了它,Join函数阻塞当前线程,等待Thread td = new Thread(delegate ()块执行,然后恢复,这样就显示了所有内容。

这些是这种匿名构造的基础知识。

章节回顾

回顾一下,包括注释的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic;
using System.Threading;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    private void ShowSquare(double x) => 
    sampLabel.Text += "<br>" + (x * x);//expression bodied function
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make list of double values
        List<double> vals = 
        new List<double>(new double[] { 1, 2, 4, 5, 6, 8 });
        //call ShowSquare on each value inside the list
        vals.ForEach(ShowSquare);
        sampLabel.Text += "<br>-----------------------------------" ;
        //lines 21-24 define an unnamed method, which is applied to each 
        //value in the list
        vals.ForEach(delegate (double x)
        {
            sampLabel.Text += "<br>" + Math.Pow(x, 3);
        });
        sampLabel.Text += "<br>-----------------------------------" ;
        //lines 28-35 create a thread object, and an unnamed method inside
        //it that spawns
        //a thread of processing separate from the "main" program
        Thread td = new Thread(delegate ()
        {
            List<double> arrs = 
            new List<double>(new double[] { 1, 4, 5, 3, 53, 52 });
            arrs.Sort();
            arrs.ForEach(x => sampLabel.Text += $"<br>{x}");
        });
        //start the thread
        td.Start();
        td.Join(); //this is needed to ensure that the thread 
        //"td" runs, and then joins back to the
        //current, main thread, so the program finishes running
    }
} 

摘要

在本章中,您学习了匿名函数。您简化了编写函数,对所有值执行了操作,创建了匿名函数或方法,并启动了一个线程。

在下一章中,我们将介绍语言的基础知识:语言集成查询。这是一种在 C#代码中直接操作数据的强大方式。

第九章:使用 LINQ 和内置类型的 C#

在本章中,我们将讨论 LINQ 的基础知识。你将学习如何使用 LINQ 或语言集成查询。这是一种在 C#代码中直接操作数据的强大方式。

在 HTML 中添加一个显示值按钮

打开一个项目,在<form id=...开头的行下面,在中放置一个按钮。更改按钮上的文本为不同的内容,例如,显示值。

现在切换到设计视图,并双击显示值按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。我们不需要它。该项目的起始代码的相关部分应该如图 9.4.1所示:

图 9.4.1:该项目的起始代码部分

我们将在本章中使用一些代码,但是它是从上到下顺序执行的。

添加命名空间

首先要做的是添加两个新的命名空间;因此,在using System之后输入以下内容:

using System.Linq;
using System.Collections.Generic;

LINQ 代表语言集成查询,using System.Collections .Generic用于处理列表。这是我们正在使用的两个新命名空间。

使用 IEnumerable 通用接口

接下来,在以protected void Button1_Click...开头的行下面的花括号之间,我们将首先创建一个名字数组。为此,请输入以下内容:

IEnumerable<string> names = new string[] { "john", "job", "janet", "mary", "steve" };

让我们称其为names,然后说,创建一个new string数组。然后,为了指定初始化列表,我们输入一系列用引号括起来的名字,并以分号结束。

现在注意一下,左侧有IEnumerable。这是一个通用接口。正如你所看到的,这一行中的new string数组可以通过这种方式创建,因为可以取一个数组然后逐个遍历,这样数组中的每个条目都是一个字符串。所以,它是IEnumerable:我们可以在其中列出值,要列出的每个值都是一个字符串。枚举意味着列举。

将数组转换为整数列表

接下来,在这行下面输入以下内容:

List<int> lst = new int[] { 1, 2, 12, 4, 5, -10, 5, 25, 54 }.ToList();

要创建一个整数列表,我们说lst = new int[]。然后我们指定初始化列表和这里显示的值。你使用什么值都无所谓。我会向你展示一些方法。当然,你可以想象到,有很多方法。

现在,请注意,你不能在数组之后停止编写这行。如果你这样做了,弹出提示会说无法隐式转换类型'int[]'为'System.Collections.Generic.List';因此你必须添加.ToList()。你可以将一个数组转换为整数列表。

确定集合中的值

现在我们有了要遍历的项目集合,我们可以这样做。为此,请输入以下内容:

IEnumerable<int> valuesMoreThanTen = lst.Where(x => x >10);

在这里,我们首先操作数字值列表,所以我们说valuesMoreThanTen。为了实现这一点,你输入列表的名称,即lst。注意弹出提示中出现的所有函数。其中之一是Where<>。在选择Where<>之后,你可以指定一个条件,即在我们的情况下,当x大于10时,或者(x => x > 10),并以分号结束。

如果你将鼠标悬停在Where上,并查看它所说的IEnumerable<int>,它表示返回的是一个IEnumerable结构,我们可以通过foreach循环进行迭代。此外,它说(Func<int,bool>...,然后是一个predicate委托。所以,我们将取每个值,然后基本上对其应用一些操作。我们将检查某个条件是否成立:条件对它成立或不成立。

正如你所看到的,我们基本上有了 LINQ,然后在其中有一个 Lambda 表达式。因此,要使用它,你将在下面输入以下内容:

valuesMoreThanTen.ToList().ForEach(x => sampLabel.Text += $"<br>x={x}");

将值转换回列表

valuesMoreThanTen之后,你想要使用foreach循环。为了做到这一点,你必须将其转换回列表,因为记住,IEnumerable不是一个列表。这就是为什么如果你在valuesMoreThanTen.(点)后面直接输入foreach循环,它不会显示出来。你将它转换为列表,然后foreach就会显示出来。现在你可以再次显示这些值;所以在foreach x中,你将取出x的值,并在标签中显示它,就像在前一行代码中所示的那样。这一行现在将显示valuesMoreThanTen列表中的每个x值。

现在,你可以通过检查它来确定122554应该打印出来。这是第一件事。现在,让我们在这一行下面再显示一条水平线。所以,输入以下内容:

sampLabel.Text += "<br><hr/>";

从列表中提取值并对其进行排序

现在,想象一下,你有一个名字数组,你想提取那些,例如,有一个j字母的名字,然后按照从最短到最长的顺序进行排序。当你操作数据时,你可以做这样的事情。所以,输入以下内容:

IEnumerable<string> namesWithJSorted = names.Where(name => name.Contains("j")).OrderBy(name => name.Length);

在这一行中,IEnumerable的类型是string。这就是为什么我们说IEnumerable是泛型的,因为它可以操作整数、字符串等等。接下来,你说namesWithJSorted,我以这种特定的方式命名这个变量,因为函数将从左到右链接。所以,你输入名字数组的名称,然后输入Where(name => name.Contains("j")来检查每个名字是否包含字母j。然后,一旦你有了所有包含字母j的名字,你将按照每个名字的长度对结果进行排序,使用OrderBy(name => name.Length)

再次,从左到右,你可以链接这些函数。这就是 LINQ。正如你在每个函数中所看到的,基本上都有一个 Lambda 表达式:Where,然后是OrderBy。它很强大,对吧?

接下来,要显示它,记住,因为namesWithJSortedIEnumerable,你可以将它转换回列表,然后使用foreach;或者,如果你愿意,你也可以直接输入以下内容:

foreach(var str in namesWithJSorted)
{
    sampLabel.Text += $"<br>{str}";
}

记住,在直接前一行中,+=是用来追加的,$是用于字符串插值,<br>是用来换行的。要打印的实际值出现在花括号内。

这些是这些概念的基础。

运行程序

现在,我们必须确认这将按预期工作。所以,打开你的浏览器,点击“显示值”按钮。正如你在图 9.4.2中所看到的,它显示了 x=12、x=25 和 x=54,然后在下面显示了名字 job、john 和 janet。每个名字都包含一个j字母,并且按照预期的顺序列出:

图 9.4.2:运行本章程序的结果

记住,这基本上是一个组合。你有一个 Lambda 表达式(x => x > 10),然后你把它放到whereOrderBy这样的方法中。当你把这两者结合起来时,代码变得非常强大,正如你所看到的,非常表达,让你能够完成很多事情。还要记住,在左侧,LINQ 中的许多结果返回的是IEnumerable类型的项目。

章节回顾

回顾一下,包括注释在内的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Linq;
using System.Collections.Generic;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //line 16 creates array of names
        IEnumerable<string> names = new string[] { "john", "job", "janet",
        "mary", "steve" };
        //line 18 creates array of integers, and converts to 
        //list of integers
        List<int> lst = new int[] { 1, 2, 12, 4, 5, -10, 5, 25, 54 }.ToList();
        //line below puts a lambda expression inside Where to 
        //create a query
        IEnumerable<int> valuesMoreThanTen = lst.Where(x => x > 10);
        //line 22 prints the results from lines 20 above
        valuesMoreThanTen.ToList().ForEach(x => sampLabel.Text += $"<br>x={x}");
        sampLabel.Text += "<br><hr/>";
        //line 25 below chains functions, going from left to right, to 
        //produce a list of names with j, sorted by length
        IEnumerable<string> namesWithJSorted = 
        names.Where(name => name.Contains( "j")).OrderBy
        (name => name.Length);
        //lines below display the names that are generated line 25 above
        foreach (var str in namesWithJSorted)
        {
            sampLabel.Text += $"<br>{str}";
        }
    }
}

总结

在本章中,我们讨论了 LINQ 的基础知识。你学会了如何使用 LINQ,或者说是语言集成查询。这是一种在 C#代码中直接操作数据的强大方式。你添加了命名空间,使用了IEnumerable泛型接口,将数组转换为整数列表,确定了集合中的值,将这些值转换回列表,并提取并排序了这些值。

在下一章中,我们将讨论如何在自定义类型中使用 LINQ。

第十章:使用 C#和 LINQ 以及自定义数据类型

在本章中,我们将讨论如何使用 LINQ 与自定义类型。

在 HTML 中添加一个显示人员的按钮

打开一个项目。转到Default.aspx,并在以<form id=...开头的行下面放置一个按钮。要做到这一点,转到工具箱,获取一个Button控件,并将其拖放到那里。更改按钮上的文本以显示“显示人员”:

<asp:Button ID="Button1" runat="server" Text="Show People" />

设置数据库

我们将有一个数据库,我们将对其进行查询,并且我们将展示那些,例如,名字中有某个字母的人,赚取一定数量的钱,并以某种方式进行排序。

为了实现这一点,转到设计视图,并双击“显示人员”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该看起来像图 10.5.1

图 10.5.1:该项目的起始代码部分

在下一阶段,首先转到文件顶部,并在using System之后输入以下内容:

using System.Linq;

接下来,我们将创建一个类。我们将其称为Person。因此,在以public partial class...开头的行上面插入以下内容:

public class Person

使用 LINQ 制作自定义类型

现在,在上一行下面的花括号集之间,您将声明两个自动属性,如下所示:

public string Name { get; set; }
public decimal Salary { get; set; }

然后,为了创建一个构造函数,请在这些行下面输入以下内容:

public Person(string name, decimal salary)

接下来,您将在构造函数内设置属性的值。因此,请在以下这些行下面的一组花括号之间输入以下内容:

Name = name; Salary = salary;

这是我们简单的自定义类型Person,具有两个自动属性和一个带参数的构造函数。

设置一个人员数组

在下一阶段,您将创建一个人员数组;请在以下以protected void Button1_Click....开头的行下面的一组花括号之间输入以下内容:

Person[] people = new Person[] { new Person("John", 76877), new Person("Bobby", 78988), new Person("Joan", 87656) };

查询数组

现在,要查询这个,请在此行下面输入以下内容:

IEnumerable<Person> peopleWithN = people.Where(per => per.Name.EndsWith("n")).OrderByDescending(per => per.Salary);

当您输入时,注意到IEnumerable不会显示出来,因此您必须再次转到文件顶部,并在using System.Linq之后输入以下内容:

using System.Collections.Generic;

现在让我们在下面使用它;因此,在以Person[] people...开头的行下面,输入前面提到的IEnumerable<Person>...行。

在这里,Person是可以从人员列表中枚举的对象类型。peopleWithN表示我们将搜索名字中有字母n的人。实际上,代码搜索名字以n结尾的人。(请注意,per代表列表中的每个人。)此外,我们按工资降序排序。

因为人们有时会不一致地输入信息,所以您首先必须将所有内容转换为等效的情况,但这是您自己要解决的问题。

请记住,在这一行中,我们有people,这是某种对象的名称,以及Where,一个扩展方法,后跟一个 Lambda。接下来,我们使用OrderByDescending,您可以从方法列表中选择它,以按降序排序值,例如人的工资。

因此,此行的目的是选择每个名字以n结尾的人,然后按工资排序结果。这将产生一个IEnumerable对象,现在您当然可以逐步进行,并在下一行中说以下内容:

foreach(Person p in peopleWithN)

现在,为了打印所有内容,请在此行下面的一组花括号之间输入以下内容:

sampLabel.Text += $"<br>{p.Name} {p.Salary:C}";

在这里,我们首先放置了Name变量,然后是格式化为货币的Salary变量。

运行程序

这是我们程序的核心。在浏览器中启动它。单击“显示人员”按钮,结果将显示如图 10.5.2所示:

图 10.5.2:运行程序的结果

所以,琼赚了$87,656.00,约翰赚了$76,877.00。他们被选中是因为他们的名字都以小写字母n结尾,正如您所看到的,然后按工资降序排序。所以,它的运行结果符合预期。正如您所看到的,您还可以使用 LINQ 定义自定义类型,比如在public class Person下面的花括号中。它非常强大并且运行良好。

章节复习

为了复习,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Linq;
using System.Collections.Generic;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public class Person
{
    public string Name { get; set; } //auto implemented properties
    public decimal Salary { get; set; }
    public Person(string name, decimal salary)
    {
        Name = name; Salary = salary;//set values of properties
    }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make array of people
        Person[] people = new Person[] { new Person("John", 76877), 
                                         new Person("Bobby",78988), 
                                         new Person("Joan", 87656) };
        //find all people with "n" as the last letter, and then display 
        //the results sorted from high to low salary
        IEnumerable<Person> peopleWithN = 
        people.Where(per => per.Name.EndsWith("n")).OrderByDescending
        (per => per.Salary);
        //display name and salary formatted as currency
        foreach (Person p in peopleWithN)
        {
            sampLabel.Text += $"<br>{p.Name} {p.Salary:C}";
        }
    }
}

摘要

在本章中,我们讨论了如何将 LINQ 与自定义类型一起使用。您设置了一个数据库,使用 LINQ 创建了一个自定义类型,设置了一个人员数组,并对数组进行了查询。

在下一章中,您将学习如何使用查询语法编写查询。

第十一章:使用查询语法构造查询

在本章中,您将学习如何使用查询语法编写查询,例如方法链接,就像我们以前做过的那样。

向 HTML 添加显示按钮

打开一个项目,唯一放入中的是一个按钮,没有其他内容。为此,请转到工具箱,获取一个Button控件,并将其拖放到以<form id=...开头的行下方。将按钮上的文本替换为显示:

<asp:Button ID="Button1" runat="server" Text="Show" />

现在,切换到设计视图,并双击显示按钮。这将带我们进入Default.aspx.cs。删除事件处理存根。此项目的起始代码的相关部分应如图 11.6.1所示:

图 11.6.1:此项目的起始代码部分

接下来,转到文件顶部,在using System下输入以下内容:

using System.Collections.Generic;
using System.Linq;

为了利用这一点,我们将做如下操作。这是例行代码;这是机械的。首先,当有人单击显示按钮时,您希望创建一个标签,以便始终有一个累积输出。为此,请在以protected void Button1_Click...开头的行下的大括号之间输入以下内容:

sampLabel.Text = "";

创建一个 decimal 工资数组

接下来,在上一行下面,您将创建一个名为salariesdecimal数组,自然而然地。因此,输入以下内容:

decimal[] salaries = new decimal[] { 56789, 78888, 35555, 34533, 75000 };

这就是您可以查询decimal数组的方法。这是一个特定的decimal数组,但基本上可以是任何数组。我们加入了一些值,就这样。

使用范围变量

接下来,在此行下面输入以下内容:

IEnumerable<string> salResults = from salary in salaries

请注意,返回或结果集将是string类型,而不是decimal类型。在salResults =之后,您希望定义 LINQ 查询的主体,因此您说from salary in salaries。如果您将鼠标悬停在此处的salary上,您会看到它被称为范围变量,如图 11.6.2所示。因此,您要求它查看salaries。作为范围变量,它是逐个遍历所有条目的数量。

图 11.6.2:范围变量

选择工资范围并按降序排列

现在,您将指定某种逻辑条件。例如,以某种方式过滤结果。因此,在此行下缩进输入以下内容:

where 35000 <= salary && salary <= 75000
select $"<br>{salary:C}";

接下来,您可以对结果集进行orderby,以按降序列出工资,例如;因此,请直接在此行下面输入以下内容:

orderby salary descending

默认情况下是按升序排列的,从小到大,您希望将其反转。当您加入descending关键字时,它就会从大到小排列。

接下来,记住目标是获得一个填充有字符串的IEnumerable构造。因此,最后,请在此代码块的一部分中输入以下内容:

select $"<br>{salary:C}";

您还可以在原地格式化结果,就像我在这一行中所做的那样,例如,以货币格式。

显示结果

有了这个代码块,当然,下一阶段是迭代并显示结果。为此,您可以进行转换为列表并打印,或者您可以接下来做以下操作:

foreach(string formattedSalary in salResults)

我们怎么知道在这一行中应该说string?记住,前面的IEnumerable行填充有字符串,对吗?如果您将鼠标悬停在IEnumerable上,它会说IEnumerable<out T>,而T是字符串。

现在,为了显示结果,请在上一行下面的一对大括号之间输入以下内容:

sampLabel.Text += formattedSalary;

在这里,formattedSalary是要显示的内容。

现在,在浏览器中打开此内容。单击显示按钮。结果将显示如图 11.6.3所示:

图 11.6.3:运行程序的初始结果

这里的工资按降序排列,从高到低,并且在 75000 美元到 35000 美元的范围内。所以,这正如预期的那样工作。

观察延迟执行

现在,你应该知道的一件事是所谓的延迟执行的概念。所以,为了了解这意味着什么,看看接下来的内容。

想象一下,我在foreach(string formattedSalary in salResults)行右边放了一个断点。然后,从调试菜单中选择单步执行,并点击显示按钮。注意每一行是如何连续运行的(每行后面都会显示毫秒数)。你应该看到它是如何进入的;它是如何运行的。

你应该注意的一件事是 LINQ 的延迟执行的概念,这意味着salResults实际上是运行的,正如你所看到的,所以它本质上是一个查询变量。它在你使用foreach循环迭代时运行,就像下面的代码块中所示的那样,而不是在你在前面的IEnumerable块中编写时运行。它不会在那时运行。它会在你迭代它时运行。否则,你的程序可能会在这些查询结果中携带大量信息。所以,这就是延迟执行的内容:

IEnumerable<string> salResults = from salary in salaries
                                 where 35000 <= salary && salary <= 75000
                                 orderby salary descending
                                 select $"<br>{salary:C}";
        foreach(string formattedSalary in salResults)
        {
            //display formatted salaries one at a time
            sampLabel.Text += formattedSalary;
        }

在下一阶段,我们将看一个可能能做的另一个实际例子。我们还想显示水平线,所以在sampLabel.Text += formattedSalary行下面的闭合大括号之后,输入以下内容:

sampLabel.Text += "<br><hr/>";

<hr/>标签将在输出中添加一条水平线。

创建一个字典

接下来,我们将创建一个Dictionary;为此,请在下面输入以下行:

Dictionary<string, decimal> nameSalaries = new Dictionary<string, decimal>();

在这里,<string,decimal>代表键值对。

处理键值对

现在,让我们添加一些键值对。所以,从输入以下内容开始:

nameSalaries.Add("John Jones", 45355);

在这一行中,John Jones是键,值是他的薪水,或者$45,355。

然后,你可以重复这个几次,所以直接在它下面再粘贴这一行三次。比如,约翰·史密斯,76900;约翰·詹金斯,89000;史蒂夫·乔布斯,98000:

nameSalaries.Add("John Smith", 76900);
nameSalaries.Add("John Jenkins", 89000);
nameSalaries.Add("Steve Jobs", 98000);

请注意,我在这里重复了名字约翰几次,因为我想简要说明一个概念。最后一个列出的是史蒂夫·乔布斯,当然他的薪水远远超过了 98000!

查询键值对中的数据

现在,我们将再次查询这个。这是我们在键值对中拥有的数据,我们将对其进行查询。所以,在这些行下面输入以下内容:

var dictResults = from nameSalary in nameSalaries

在这里,nameSalary是一个范围变量,它指的是约翰·琼斯、约翰·史密斯、约翰·詹金斯、史蒂夫·乔布斯等等,而nameSalaries是字典本身。nameSalary是键和值的特定组合。

然后,在这一行下面,缩进以下代码:

where nameSalary.Key.Contains("John") && nameSalary.Value >= 65000

在这里,我们说键包含名字约翰,薪水大于或等于$65,000。如果你愿意,你可以添加OrderBy等等,但对于我们的目的,直接在这一行下面输入以下内容:

select $"<br>{nameSalary.Key} earns {nameSalary.Value:C} per year.";

在这一行中,我们将选择那些符合这两个条件的记录:姓名是约翰,薪水超过$65,000。所以,在我们的情况下,肯定是约翰·史密斯和约翰·詹金斯。为了使输出看起来好看,我们说nameSalary.Value:C以货币格式化,然后添加per year

现在,将鼠标悬停在dictResults上。你看到弹出提示中说IEnumerable吗?现在,var被称为隐式类型。我们之前见过var。有时很难从像我们创建的这样的查询中判断输出会是什么,因为它足够复杂。所以,如果你使用隐式类型,它会告诉你输出应该是什么,所以是string类型的IEnumerable。现在,如果你愿意,你可以将这一行改为:

IEnumerable<string> dictResults = from nameSalary in nameSalaries

现在我们也知道这一点,因为在这个查询的末尾,你看到了字符串,对吧?这些是包含格式化信息的字符串,当然,你可以像往常一样迭代它们;所以,接下来输入以下内容:

foreach(string nameSal in dictResults)

然后,在这一行下面的一对大括号之间,以以下内容结束:

sampLabel.Text += nameSal;

运行程序

在浏览器中运行这个,确保它按预期工作。点击显示按钮。结果显示在图 11.6.4中:

图 11.6.4:运行我们程序的结果

现在你已经得到了我之前描述的第一组结果,第二组结果显示在它们下面,显示两个名字都包含 John,金额为$65,000 或更多。

章节回顾

作为回顾,包括注释的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic;
using System.Linq;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //clear label on button click
        sampLabel.Text = "";
        //make array of salaries
        decimal[] salaries = 
        new decimal[] { 56789, 78888, 35555, 34533, 75000 };
        //construct Linq query, which produces a collection of 
        //formatted strings
        IEnumerable<string> salResults = from salary in salaries
                              where 35000 <= salary && salary <= 75000
                              orderby salary descending
                              select $"<br>{salary:C}";
        foreach(string formattedSalary in salResults)
        {
            //display formatted salaries one at a time
            sampLabel.Text += formattedSalary;
        }
        //show horizontal rule on screen
        sampLabel.Text += "<br><hr/>";
        //make dictionary to hold names and salaries as key/value pairs
        Dictionary<string, decimal> nameSalaries = 
        new Dictionary<string, decimal>();
        nameSalaries.Add("John Jones", 45355);
        nameSalaries.Add("John Smith", 76900);
        nameSalaries.Add("John Jenkins", 89000);
        nameSalaries.Add("Steve Jobs", 98000);
        //query below represents all people named John who make 65000 
        //and more
        //this query gives back a formatted string for each key/value 
        //pair that 
        //satisfies the condition
        IEnumerable<string> dictResults = from nameSalary in nameSalaries
                            where nameSalary.Key.Contains("John") && 
                            nameSalary.Value >= 65000
                            select $"<br>{nameSalary.Key} earns 
                            {nameSalary.Value:C} per year.";
        foreach(string nameSal in dictResults)
        {
            sampLabel.Text += nameSal;//display named and salaries
        }
    }
}

总结

在本章中,你学会了如何使用查询语法编写查询。你创建了一个十进制薪水数组,使用了范围变量,观察了延迟执行,创建了一个字典,使用了键值对,查询了键值对中的数据,并学习了隐式类型。

在下一章中,我们将进一步探讨 LINQ。具体来说,我们将研究 LINQ 的一些强大功能,如平均、求和和计数等聚合函数。此外,我们还将讨论列表的列表,这是非常实用的东西。

第十二章:执行聚合函数的查询

在本章中,我们将进一步探讨 LINQ。具体来说,我们将看看 LINQ 执行聚合函数的能力,比如平均值、求和、计数等。此外,我们还将讨论列表的列表,这是一个非常实用的东西。

在 HTML 中添加显示按钮

打开项目,并且为了保持简洁,我们将在以<form id=....开头的行下面放一个按钮。要做到这一点,去工具箱,拖动一个Button控件,并把它拖到那里。将按钮上的文本更改为Show

现在,切换到设计视图,并双击显示按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。我们不需要那个。这个项目的起始代码的相关部分应该看起来像图 12.7.1

图 12.7.1:这个项目的起始代码部分

在下一阶段,转到文件顶部,在using System下面输入以下内容:

using System.Collections.Generic;
using System.Linq;

创建一个数组

在本章中有很多要输入的代码,但这是机械的。首先,我们将创建一个数组,所以在以protected void Button1_Click...开头的行的大括号之间输入以下内容:

IEnumerable<int> scores = new int[] { 45, 98, 99, 78, 89, 87, 77, 67, 71, 81 };

在这里,IEnumerable是数据类型,scores是数组的名称。你放入数组的值并不重要。

对列表中的值求平均值

现在,首先我们将找到这个列表的平均值。所以,输入以下内容:

var goodStudentAverage = (from score in scores where score >= 90 select score).Average();

我们将选择得分为90或以上的学生。想象一下,这些是几个学生的学期成绩。因此,在前一行中,我们说得分是>=90,选择那个分数。这是一个你可以在一行中写的查询。在这个上下文中,score是范围变量,scores是数组,选择的条件是where score=>90。然后,你输入.(点)Average()来平均整个东西。换句话说,这样写的方式是,括号中的查询将运行,然后平均数组中的值。如果你将鼠标悬停在这一行的var上,你会看到它说double,因为,如果你将鼠标悬停在Average上,你也会看到它返回一个double。因此,这个Average()函数作用于IEnumerable类型的列表,但它返回一个double数据类型给我们。

显示结果

现在,你当然可以显示结果,因为记住它只是一个单一的数值,一个聚合数量。你现在可以在这一行下面说以下内容:

sampLabel.Text = $"<br>The average for great students is {goodStudentAverage}";

使用 Count 函数

现在,如果你愿意,你也可以使用Count函数,所以你可以说下面的内容:

var averageStudentCount = scores.Where(grade =>70 <= grade && grade <80).Count();

在以var开头的前一行中,我们在单行中使用了查询语法,或者内联查询语法,因为我们使用了fromwhere。现在,我们可以使用方法链接和其中的 Lambda 表达式来表达相同的事情。所以,这里我们说scores.Where,然后我们说grade是这样的,即70 <=grade,但grade <80。因此,我们正在定义那些得分在7080之间的人,不包括80的分数,并且我们将它们标记为平均学生。然后我们将Count它们。这将告诉我们有多少这样的人,然后我们可以显示那个数字。例如,你可以输入以下内容:

sampLabel.Text += $"<br>There are {averageStudentCount} average students.";

记住,averageStudentCount产生一个数字,所以,例如,结果可能是,有 25 个平均学生

使用列表的列表

现在,这个概念的一个非常现实的应用可能是有一个列表的列表。首先输入以下内容:

List<int> firstStudent = new List<int> { 90, 89, 92 };

想象一下,您有一个学生firstStudent。然后,他或她有一些成绩,所以您创建了整数的new List,然后在一对大括号中初始化了这个列表。因此,按照所示的方式添加一些值。(请注意,我输入的值在90 +/-范围内。)这是您可以以前未见过的方式初始化列表。

现在,让我们再为另一个学生做一个整数的列表。为此,输入以下内容以为secondStudent创建new List。同样,使用另一组值初始化此列表。(请注意,在这一行中,我将输入的值在80 +/-范围内。)现在,当您有一个完整的班级时,您将有这样的列表,对吧?这是因为您有多个学生在一个班级中:

List<int> secondStudent = new List<int> { 78, 81, 79};

因此,现在您可以创建构造函数。接下来输入以下内容:

List<List<int>> classList = new List<List<int>>();

向 classList 添加学生

在这里,我们有一个整数的列表列表——您可以在其他列表中嵌入列表。然后,我们会说,例如,classList列表等于一个新的列表列表。要初始化此列表,您可以使用Add。在下一行,输入以下内容:

classList.Add(firstStudent);
classList.Add(secondStudent);

这是如何将第一个学生、第二个学生等添加到班级列表中的方法。

总结 classList 中的信息

在下一阶段,您希望能够获得一些有用的信息。例如,想象一下您有这样一个列表的列表,您希望进行总结。因此,接下来输入以下内容:

var avgPerStudent = classList.Select(student => student.Average());

现在,以avgPerStudent为例,表示平均学生分数。现在,在输入classList.Select()之后,要选择的数量是代表每个学生的列表,由(student => student.Average())捕获。现在,请确保您理解student参数是什么。在这里,您选择一个学生并平均他们的成绩。将鼠标悬停在student上,您会看到该数量代表与第一个学生对应的整数列表。然后,student.Average表示对该学生进行平均,然后对下一个学生重复此过程。如果将鼠标悬停在var上,您会看到在这种情况下返回的是IEnumerable类型。您可以迭代这些值。要做到这一点,您将输入以下内容:

foreach(var studentAvg in avgPerStudent)

现在,在此行下方,输入以下内容以在一对大括号中显示结果:

sampLabel.Text += $"<br>Average grade={studentAvg}";

运行程序

现在,构建此程序并在浏览器中运行它。单击“显示”按钮:

图 12.7.2:运行我们程序的结果

现在这些是一些专业的结果。优秀学生的平均分是 98.5。有三个平均学生。两个列表的扩展平均成绩显示在最后。

因此,您学到了更多关于 LINQ 的知识——Average函数和Count函数,还学会了如何制作一个列表的列表。您可以使用Select等语句对这些列表进行操作,然后可以嵌入 Lambda 表达式以单独处理列表中的每个列表。

章节回顾

回顾一下,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic;
using System.Linq;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        IEnumerable<int> scores = 
        new int[] { 45, 98, 99, 78, 89, 87, 77, 67, 71, 81 }; 
        //array of integers
        //line 17 below selects all scores 90 or above, and averages them,
        //giving back a double value
        var goodStudentAverage = (from score in scores where score >= 90 select score).Average();
        //line 19 below displays the average
        sampLabel.Text = $"<br>The average for great students is {goodStudentAverage}";
        //line 21 below selects all students below 70 and 80,
        //and counts them
        var averageStudentCount = scores.Where(grade => 70 <= grade && grade < 80).Count();
        //line 23 below displays the student count
        sampLabel.Text += $"<br>There are {averageStudentCount} average students.";
        //lines 25 and 26 create two new lists with initializer lists
        List<int> firstStudent = new List<int> {90,89,92};
        List<int> secondStudent = new List<int> { 78, 81, 79 };
        //line 28 creates a list of lists
        List<List<int>> classList = new List<List<int>>();
        classList.Add(firstStudent);
        classList.Add(secondStudent);
        //line 32 below find the average for each list, and 
        //stores the averages
        //so avgPerStudent is of type IEnumerable
        var avgPerStudent = classList.Select(student => student.Average());
        //lines 35-38 display the averages
        foreach(var studentAvg in avgPerStudent)
        {
            sampLabel.Text += $"<br>Average grade={studentAvg}";
        }
    }
}

摘要

在本章中,我们进一步探讨了 LINQ。具体来说,我们研究了 LINQ 执行聚合函数(如平均、求和和计数)的能力。此外,我们还讨论了列表的列表。您对列表中的值进行了平均,使用了Count函数,处理了列表的列表,向classList添加了学生,并总结了classList中的信息。

在下一章中,您将学习关于元组的知识,它们基本上是包含多个值的集合。

第十三章:使用 LINQ 对元组进行总结

在本章中,您将了解元组。这些基本上是几个值的集合。

在 HTML 中添加显示元组摘要值按钮

打开一个项目,并在以<form id=....开头的行下放置一个按钮。将按钮文本替换为显示元组摘要值

现在,切换到设计视图,并双击显示元组摘要值按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该看起来像图 13.8.1

图 13.8.1:该项目的起始代码部分

介绍元组

现在,首先我们将创建一个返回元组值的函数。那么,什么是元组?让我们定义它们。正如我之前所说,它基本上是几个值的集合。现在,在 C#中,这意味着您将在以public partial class...开头的行下的封闭大括号下方输入以下内容:

private static Tuple<double, double, double, double> SummarizeList(List<double> listDoubles)

在前一行中,Tuple是一个类。然后,要定义元组存储的值的数量,请记住我们与向量的工作。我们向向量添加了两个或三个值。这是一个类似的概念。如果您将鼠标悬停在Tuple上,它会说Tuple 表示 n 元组,其中n是八或更多,因此 T1、T2、T3,一直到 TRest。哇,所以您可以创建八个或更多的元组!

添加命名空间

在我们的案例中,我们放置了<double, double, double, double>。所以,这是一个可以容纳四个值的元组。请注意,当您输入时,List<double>没有显示,因此您需要添加一些命名空间。在文件顶部的using System下,输入以下内容:

using System.Collections.Generic;
using System.Linq;

在这里,我们使用了通用集合和 LINQ,现在List<double>显示为高亮显示,应该称为listDoubles

使用元组制作列表

在过程的下一个阶段,您将创建此列表。因此,在以下行的大括号之间输入以下内容:

Tuple<double, double, double, double> summary = Tuple.Create(listDoubles.Sum(),listDoubles.Average(),listDoubles.Max(), listDoubles.Min());

要形成元组,您说Tuple.Create(listDoubles.Sum()Tuple是类的名称,该类内的成员之一是Create函数,因此选择它。现在,我们可以创建一个具有四个条目的元组。接下来,我们说listDoubles.Sum()。请注意,当您输入Sum时,它是一个扩展方法。如果您删除Sum,您会注意到Linq变为灰色。这再次确认了为什么需要Linq——用于Sum函数。

这个元组中的第一个条目是列表的总和。记住,我们正在调用summary。所以,这就像是列表中条目的统计摘要。除了listDoubles.Sum(),您当然也可以有其他一些。您可以有一个平均值,listDoubles.Average(),您还可以添加listDoubles.Max()listDoubles.Min()

返回元组

最后,您可以返回元组。为此,请在以下行下输入以下内容:

return summary;

在您之前写的第一行中,记住private表示只有在那里可访问,static表示它在类级别上运行,这意味着您可以直接使用名称调用SummarizeList——您不需要将其放在对象上。

现在,在这种特殊情况下,它将返回这个结构,Tuple<double, double, double, double>,称为元组,在这里只是一种存储四个双精度值的方式。然后,要为第一个条目创建一个元组,您使用 LINQ。然后您使用 LINQ 来获取第二个条目,LINQ 来获取第三个条目,最后,LINQ 来获取第四个条目。因此,SumMinMaxAverage都是扩展方法,然后您将其return

创建一个双精度列表

现在,对于下一阶段,看一下按钮单击事件。这里的代码非常简单。首先,在以protected void Button1_Click...开头的行下的大括号内输入以下内容。您将创建一个名为lst的双精度列表,如下所示:

List<double> lst = new List<double> { 1, 2, 5, 68, 899, 1, -989, 0.1143, 98, 2553 };

new List of double值之后,通过在大括号中添加一些数字来指定初始化程序——不管它们是什么,对吧?放一些负数、一些小数、一些整数等等。

总结列表

接下来,我们将调用SummarizeList。因此,请在此行下面输入以下内容:

var results = SummarizeList(lst);

在这种情况下,老实说,var很容易,对吧?如果你不使用它,你将不得不输入Tuple<double, double, double, double>,这将是数据类型。换句话说,那真的很啰嗦,而且占用了很多空间。所以,请记住,var表示隐式数据类型,但它足够聪明,知道数据类型是什么。

显示结果

然后一旦你返回它,你可以去那里的项目商店。因此,你可以输入以下内容:

sampLabel.Text = $"Sum={results.Item1}";

在下一阶段,复制这行并直接粘贴到下面。编辑Average函数的文本如下:

sampLabel.Text += $"<br>Average={results.Item2}";

确保你调用这些行的方式与函数对应;所以,SumAverageMaxMin。再次复制上一行并直接粘贴到下面,这样你就不必追加了。由于下一个是为Max,编辑文本如下:

sampLabel.Text += $"<br>Max={results.Item3}";

这将是Item3和你可以提取的元组。

最后,让我们再做一次。因此,再次复制上一行并直接粘贴到下面。由于最后一个是为Min,编辑文本如下:

sampLabel.Text += $"<br>Min={results.Item4}";

当然,这是Item4和你可以提取的元组。

运行程序

现在,让我们在浏览器中加速。点击显示元组摘要值按钮。结果显示在图 13.8.2中:

图 13.8.2:运行本章程序的结果

你看到了 Sum、Average、Max 和 Min,所以它的工作符合预期。

现在,作为对此更现实的扩展,想象一个元组列表。你肯定可以做到,所以你可以在最后一行下面添加类似以下内容:

List<Tuple<string, double, double, decimal>>;

你可以有一个元组列表。每个元组代表,例如,关于一个人的信息,然后你会有一个人的列表。这是让你思考的事情:如何构建它并为自己做一个项目。然而,这些都是基础。

章节回顾

回顾一下,包括注释的本章Default.aspx.cs的完整版本在以下代码块中显示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic;
using System.Linq;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    private static Tuple<double, double, double , double> SummarizeList(List<double> listDoubles)
    {
        Tuple<double, double, double, double> summary = 
        Tuple.Create(listDoubles.Sum(),
        listDoubles.Average(), listDoubles.Max(), listDoubles.Min());
    return summary;
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        List<double> lst = 
        new List<double> { 1, 2, 5, 68, 899, 1, -989, 0.1143, 98, 2553 };
        var results = SummarizeList(lst);
        sampLabel.Text = $"Sum={results.Item1}";
        sampLabel.Text += $"<br>Average={results.Item2}";
        sampLabel.Text += $"<br>Max={results.Item3}";
        sampLabel.Text += $"<br>Min={results.Item4}";
    }
}

总结

在本章中,你学习了关于元组的知识,它基本上是几个值的集合。你创建了一个带有元组的列表,返回了元组,并对列表进行了总结。

在下一章中,我们将讨论使用 LINQ 来对相关结果进行分组。分组是在数据库中对结果进行分类的基本操作。

第十四章:用分组总结结果

在本章中,我们将讨论使用 LINQ 对相关结果进行分组。分组是在数据库中对结果进行分类的基本操作。

在 HTML 中添加一个显示结果按钮

打开一个项目。首先,我们将在 HTML 中放置一个按钮,上面写着“显示结果”;要做到这一点,在以<form id=....开头的行下面放置一个按钮。

<asp:Button ID="Button1" runat="server" Text="Show Results" /<br />

接下来,切换到设计视图,并双击“显示结果”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。这个项目的起始代码的相关部分应该看起来像图 14.9.1

图 14.9.1:这个项目的起始代码部分

添加命名空间

首先,我们需要添加一些命名空间。要做到这一点,在文件顶部的using System下面输入以下内容:

using System.Linq;
using System.Collections.Generic;

创建学生类并定义字段

接下来,我们将创建一个名为Student的类。在以public partial class _Default...开头的行之上输入以下内容:

public class Student

接下来,要定义字段,在这一行下面的一对大括号之间输入以下内容:

public string Name { get; set; }

这里有一些属性,然后让我们再添加一个。在这一行下面输入以下内容:

public List<int> Grades;

在这里,List<int>是学生的成绩,让我们称之为Grades

制作学生名单

现在,在下一个阶段,我们将制作一个学生名单。要做到这一点,首先在以protected void Button1_Click...开头的行之后的一对大括号之间输入以下内容:

List<Student> students = new List<Student>

在这里,students是列表的名称。然后我们有一个新的学生列表。接下来,为了初始化列表,我们将把所有新学生放在这一行下面的一对大括号之间,开始如下:

new Student {Name="Smith, John", Grades=new List<int> {78,98,67,87 } },

在前一行的new Student之后,你需要在一对大括号中分别放入每个学生的所有信息。首先,你需要定义Name的值,所以你将其设置为Smith,例如,John,插入一个逗号,然后在一个新的整数列表中放入 John 的Grades,将这些值设置为78986787

接下来,我们需要为其他学生重复几次这个过程;所以,复制这一行,然后粘贴到下面。编辑这一行,将Name变量的值更改为AdamsAmy,然后将成绩更改为91998993

new Student {Name="Adams, Amy", Grades=new List<int> {91,99,89,93 } },

这种编码水平非常实用和现实。在编码了五年之后,我可以告诉你,事情总是更有趣和更具挑战性。

现在,再重复一次这个过程。复制前面的行,然后粘贴到下面。编辑这一行,将Name变量的值更改为SmithMary,然后将成绩更改为89878488。确保在最后一个new Student类的下一行插入一个闭合的大括号和一个分号:

new Student {Name="Smith, Mary", Grades=new List<int> {89,87,84,88 }};

分组名称

同样,因为我们想要按姓和名分组,所以我使用了两个相同的姓。我们将以姓的首字母和名字的顺序进行分组显示结果。

接下来,我们将编写 LINQ 查询来完成分组。同样,这可以以更复杂的方式完成,但这是一个相对简单的例子。所以,在列表中最后一个new Student类的下一行输入以下内容:

var groupsByFirstLetters = from student in students group student by student.Name[0];

记住,groupsByFirstLetters表示姓的第一个字母。所以,要编写查询,你说fromstudentstudents中,然后在下一行你group学生按student.Name分组。因为Name是一个字符串,你可以通过使用方括号提取第一个字符,然后在字符串中获取索引为0的值。这就是你可以写的原因。否则,它会显得有点神秘。

显示分组结果

现在,要以分组的方式显示结果,你必须使用嵌套的foreach循环。所以,接下来输入以下内容:

foreach(var studentGroup in groupsByFirstLetters)

在这里,情况变得更有趣。如果你将鼠标悬停在var上,它会告诉你var代表什么。它说,它是一个字符和学生的分组。它代表具有共同键的对象集合

现在,我们可以按以下方式使用它。在前一行下面的一对大括号之间输入以下内容:

sampLabel.Text += $"<br>{studentGroup.Key}";

首先,我们想显示键,也就是每个姓氏的第一个字母,然后所有内容将在该姓氏的第一个字母下进行总结。所以,我们说studentGroup.Key。这里有一个叫做Key的属性,它是每个组的分组键。请记住,这里我们是按姓氏的第一个字母进行分组。所以,键就是那个数量。

接下来,一旦你修复了该组内的第一个字母,通常会有几个学生或几个项目,对吧?所以,现在你需要逐个显示这些项目。在下面输入以下内容:

foreach(var st in studentGroup)

注意一下关于foreach循环嵌套的情况。你看到了吗,在foreach (var studentGroup in groupsByFirstLetters)这一行中,外部的for循环获取了studentGroup变量,然后该组的键通过sampLabel.Text += $"<br>{studentGroup.Key}"这一行显示出来了?接下来,你将遍历每个组内的学生。这就是为什么在下一阶段,如果你将鼠标悬停在前一行的var上,你会看到它显示student ststudentGroup中。这就是细节。

接下来,为了显示它,输入以下内容在前面foreach行的大括号中:

sampLabel.Text += $"<br>{st.Name}";

这就是核心。现在记住,我们从一个叫做Student的类开始。然后我们有一个学生列表。请注意,在学生列表中,你也可以使用一种语法,即属性的名称,然后是属性的值,不需要括号。你可以直接使用大括号在学生列表的定义中创建对象。

var groupsByFirstLetters...开头的代码块为我们分组。然后我们需要外部循环foreach (var studentGroup...来显示每个组的键。然后内部的foreach循环foreach (var st in studentGroup)显示该组内的学生。所以,这两个循环是必需的,它们有不同的作用。

现在在浏览器中运行一下,看看结果。点击“显示结果”按钮。如你所见,在图 14.9.2中,你有字母 S,这是第一组的键,组内有 Smith, John 和 Smith, Mary。接下来,你有字母 A,这是第二组的键,组内有 Adams, Amy:

图 14.9.2:运行本章程序的结果

当然,这些可以进行排序,还可以做各种其他操作。但这些只是基础知识。所以,你看到这里可以做什么;还有许多更复杂的事情是可能的。

章节回顾

回顾一下,包括注释在内的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Linq;
using System.Collections.Generic;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public class Student //define student class
{
    public string Name { get; set; }
    public List<int> Grades;
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //create list of students
        List<Student> students = new List<Student>
        {
            new Student {Name="Smith, John", 
            Grades=new List<int> {78,98,67,87}},
            new Student {Name="Adams, Amy", 
            Grades=new List<int> {91,99,89,93}},
            new Student {Name="Smith, Mary", 
            Grades=new List<int> {89,87,84,88}}
        };
        //create query that groups students by first letter of last name
        //Name is a string, so student.Name[0] means grab the 
        //first character for grouping
        var groupsByFirstLetters = 
        from student in students group student by student.Name[0];
        //the outer loop is needed to display the "Key", 
        //which is the first letter for each group
        foreach(var studentGroup in groupsByFirstLetters)
        {
            sampLabel.Text += $"<br>{studentGroup.Key}";
            //the inner loop is needed to display the students 
            //within each group
            foreach(var st in studentGroup)
            {
                sampLabel.Text += $"<br>{st.Name}";
            }
        }
    }
}

总结

在本章中,我们讨论了使用 LINQ 对相关结果进行分组。你创建了一个学生类并定义了字段,创建了一个学生列表,对名字进行了分组,最后显示了分组结果。

在下一章中,你将学习如何使用 LINQ 编写查询,以连接不同的结果集或不同的数据集。

第十五章:使用内连接加入数据集

在本章中,您将学习如何使用 LINQ 编写查询,以连接不同的结果集或不同的数据集。本章的代码并不是很复杂,只有一点点。

在 HTML 中添加一个连接类的按钮

打开一个项目。在 HTML 页面中放置一个按钮,上面写着连接类,放在以<form id=....开头的行下面。因此,我们将有两个不同的类,然后将它们连接在一起,产生一些结果,然后显示它们。这是目标:

<asp:Button ID="Button1" runat="server" Text="Join Classes" />

接下来,切换到设计视图,并双击连接类按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 15.10.1所示:

图 15.10.1:该项目的起始代码部分

添加命名空间

现在,我们将编写以下代码。我们需要 LINQ 和泛型集合命名空间;因此,在文件顶部的using System下面输入以下内容:

using System.Linq;
using System.Collections.Generic;

创建人员和汽车类

我们将创建两个类。一个是person,另一个是car类。为此,请在以public partial class _Default...开头的行的正上方直接输入以下内容:

public class Person

现在,我们只需要一个名字;因此,在此行下面的一对大括号之间输入以下内容:

public string Name { get; set; }

然后,我们还需要创建一个名为Car的类。因此,在前一行下面的封闭大括号下面输入以下内容:

public class Car

接下来,在此行下面的一对大括号之间输入以下内容:

public Person Owner { get; set; }

如您现在所见,public Person 被定义为类内字段的数据类型。例如,一辆车有一个所有者。

现在,在前一行下面添加另一个数据类型,如下所示:

public string Maker { get; set; }

显然,您可以看到Car类内有Person字段。这些类之间存在连接。我们很快就会用到这个。现在,让我们来构建。

创建人员对象

首先,我们必须创建一些Person对象,否则我们将没有任何东西可以连接。因此,在以protected void Button1_Click...开头的行下面的一对大括号之间输入以下内容:

Person per1 = new Person() { Name = "Mark Owens" };

现在,复制此行并将其直接粘贴到下面。编辑该行,将其更改为Person per2,并将Name变量的值更改为Jenny Smith

Person per2 = new Person() { Name = "Jenny Smith" };

最后,复制前面的行并将其粘贴到下面。编辑该行,将其更改为Person per3,并将Name变量的值更改为John Jenkins

Person per3 = new Person() { Name = "John Jenkins" };

所以,现在我们有一些人将成为汽车的所有者。

创建汽车对象

现在,让我们创建一些car对象。跳过一行,然后输入以下内容:

Car car1 = new Car() { Owner = per1, Maker = "Honda" };

要初始化car1,您可以从Owner = per1开始。这建立了两个类之间的连接;也就是说,car1的所有者是per1,即Mark Owens。然后,您添加制造商,我们将说car1的制造商是Honda

再次复制此行,并将其直接粘贴到前一行下面。编辑该行,将其更改为Car car2,所有者更改为per2,但制造商保持为Honda

Car car2 = new Car() { Owner = per2, Maker = "Honda" };

有时,不幸的是,为了阐明一个概念,我必须写相当多的代码,否则很难阐明这个概念。

再次复制前面的行并将其粘贴到下面。编辑该行,将其更改为Car car3Owner更改为per1,但这次将Maker更改为Toyota

Car car3 = new Car() { Owner = per1, Maker = "Toyota" };

最后,复制前面的行并将其粘贴到下面。编辑该行,将其更改为Car car4Owner更改为per4,并将Maker更改为Tesla

Car car4 = new Car() { Owner = per2, Maker = "Tesla" }; 

当然,要注意的是,per3变量并未被用作汽车的所有者,对吧?因此,当我们进行连接时,连接这两个数据集的查询将返回共享的记录。这意味着,例如,没有一辆车是由per3拥有的。

创建所有者及其汽车的列表

接下来,跳过一行,输入以下内容:

List<Person> people = new List<Person> { per1, per2, per3 };

在这里,我们说一个人的列表,people,等于一个新的人的列表,然后,我们把这些个体——per1per2per3放进去。接下来,你要对汽车做同样的事情,所以输入以下内容:

List<Car> cars = new List<Car> { car1, car2, car3, car4 };

再次,要初始化汽车列表,你说car1car2car3car4

连接所有者和汽车列表

现在,你可以连接这些列表。要做到这一点,略过一行,然后输入以下内容:

var carsWithOwners = from person in people

对于有所有者的汽车,你可以编写查询:from person in people。接下来,继续输入以下内容:

join car in cars on person equals car.Owner

在这里,我们正在连接这两个列表。我们使用personjoin people,并将其设置为car.Owner。然后,一旦它们连接起来,拥有汽车的人基本上,你可以接着说以下内容:

select new{ OwnerName = person.Name, CarMake = car.Maker };

在这一行中创建了一个匿名类型。因此,如果你将鼠标悬停在var上,它会说 T 是'a。这是一个匿名数据类型。因此,carsWithOwners基本上是一个匿名类型的列表,但因为它是一个列表,而且是IEnumerable,你可以使用foreach循环逐步进行遍历。

获取并显示结果

现在我们需要获取结果。所以,略过一行,说以下内容:

foreach(var ownedCar in carsWithOwners)

接下来,在这条线下面的花括号之间输入以下内容:

sampLabel.Text += $"<br>Owner={ownedCar.OwnerName} Car Make={ownedCar.CarMake}";

这将为我们显示结果。

运行程序

现在在浏览器中打开这个,然后点击连接类按钮。看一下结果,也显示在图 15.10.2中:

图 15.10.2:这个项目的结果

所以,马克·欧文斯有两辆车。接下来,珍妮·史密斯有一辆本田和一辆特斯拉。对吗?

现在,因为约翰·詹金斯是per3,他不会出现在汽车列表中作为所有者。这意味着per3Car列表之间没有连接。换句话说,在 LINQ 中进行连接时,会使用per1,因为它是按所有者Car.Owner进行的。因此,将使用per1per2,但不会使用per3。然后,显示结果。

章节回顾

回顾一下,包括注释在内的本章的Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Linq;
using System.Collections.Generic;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public class Person
{
    //define Person class
    public string Name { get; set; }
}
public class Car
{
    //define Car class, using field of type Person
    public Person Owner { get; set; }
    public string Maker { get; set; }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make three new people
        Person per1 = new Person() { Name = "Mark Owens" };
        Person per2 = new Person() { Name = "Jenny Smith" };
        Person per3 = new Person() { Name = "John Jenkins" };
        //make four new cars
        Car car1 = new Car() { Owner = per1, Maker = "Honda" };
        Car car2 = new Car() { Owner = per2, Maker = "Honda" };
        Car car3 = new Car() { Owner = per1, Maker = "Toyota" };
        Car car4 = new Car() { Owner = per2, Maker = "Tesla" };
        //make lists of people and cars
        List<Person> people = new List<Person> { per1, per2, per3 };
        List<Car> cars = new List<Car> { car1, car2, car3, car4 };
        //use linq to write a query that joins the two lists by car Owner
        //here, the type of var is an enumerable list of anonymous 
        //data types
        var carsWithOwners = from person in people join car in cars on person equals car.Owner
        select new { OwnerName = person.Name, CarMake = car.Maker };
        //foreach loops iterates over carsWithOwners
        foreach(var ownedCar in carsWithOwners)
        {
            sampLabel.Text += $"<br>Owner={ownedCar.OwnerName} Car Make= {ownedCar.CarMake}";
        }
    }
}

总结

在本章中,你学会了如何使用 LINQ 编写查询,连接不同的结果集或不同的数据集。你创建了PersonCar类,制作了PersonCar对象,制作了所有者及其汽车的列表,并连接了所有者和汽车列表。

在下一章中,你将使用 SQL Server 2017 Express。

第十六章:下载、安装和运行 SQL Server 2017

在本章中,您将下载、安装和运行 SQL Server 2017 Express。

下载 SQL Server 2017 Express

单击以下链接,将您带到可以下载 SQL Server 2017 Express 的网站,如图 16.1.1所示:

www.microsoft.com/en-us/sql-server/sql-server-editions-express

图 16.1.1:SQL Server 2017 Express 版下载屏幕

接下来,单击“立即下载”按钮。下载完成后,双击SQL Server2017-SSEI-Expr.exe。在用户账户控制屏幕上选择“是”。

选择安装类型

接下来,您需要选择安装类型,如图 16.1.2所示。选择基本安装并接受许可条款协议:

图 16.1.2:从安装类型屏幕中选择基本安装

安装程序

接下来,要么接受默认的安装位置,要么选择自己的位置。然后安装程序将下载并安装。在安装过程中要耐心等待,因为这是一个很大的程序,可能需要一点时间。

安装完成后,您将看到一个类似于图 16.1.3的屏幕:

图 16.1.3:安装已成功完成

在 Visual Studio 中使用 SQL Server

一旦我们下载并安装了 SQL Server,让我们在 Visual Studio 中查看它。转到“视图”,然后选择“SQL Server 对象资源管理器”;它会在左侧打开一个小窗格,如图 16.1.4所示:

图 16.1.4:Visual Studio 中的 SQL Server 对象资源管理器窗格

接下来,单击“添加 SQL Server”按钮,如图 16.1.5所示:

图 16.1.5:添加 SQL Server 按钮

现在,出现了图 16.1.6中显示的对话框。注意其中写着 Windows 身份验证。这被称为集成安全性。您不必指定不同的用户名和密码。只需填写服务器名称字段,单击“连接”,然后您就可以登录:

图 16.1.6:连接对话框

您拥有的具体版本将与图 16.1.7中显示的版本不同,但这些是适用于许多不同版本的基本内容:

图 16.1.7:特定于 SQL Server 2017 Express 版本的数据库文件夹

创建 SQL Server 数据库

现在,我们将创建一个数据库。要做到这一点,展开“数据库”文件夹,右键单击它。选择“添加新数据库”,如图 16.1.8所示,并将数据库命名为People

图 16.1.8:添加新数据库

添加和定义表

现在,展开 People 节点,然后在其中,您将看到一个名为 Tables 的文件夹。再次展开 Tables 节点,因为您需要添加自己的表。您的 SQL Server 对象资源管理器窗格应该看起来像图 16.1.9中显示的那样:

图 16.1.9:SQL Server 对象资源管理器窗格,其中 People 节点和 Tables 节点已展开

现在,右键单击“Tables”文件夹,然后选择“添加新表”。这将打开表定义阶段,如图 16.1.10所示:

图 16.1.10:表定义屏幕

这是您定义表的地方。看一下截图中左上角附近的第一个带有小键的字段。此键将用于标识表的记录或行,以及当您想要启用自动生成时。换句话说,您希望为每条记录分配的编号自动生成,这样您就不必跟踪它。

因此,如果右键单击键并选择属性,它会在屏幕右侧显示图 16.1.11中显示的面板:

图 16.1.11:表属性面板

现在,看标识规范的位置。展开该节点,然后在标识处,从下拉菜单中选择 True。如图 16.1.12所示,标识增量为 1,标识种子为 1,这很好。因此,它从 1 开始,每次添加新记录时,记录编号只增加 1。请注意,它还会自动更改代码视图。

图 16.1.12:设置标识规范

现在,在屏幕底部的 T-SQL 窗口中,它说CREATE TABLE [dbo].[Table]。如果您将[Table]更改为[People],那么现在就是表名,如图 16.1.13所示:

图 16.1.13:更改表的名称

在以下行中,[Id]是列或字段的名称;INT是数据类型,NOT NULL表示必须指定条目,PRIMARY KEY用于标识记录;您现在知道IDENTITY是什么了,因为您在之前的步骤中已经看到了它。

向表中添加字段

现在,您将添加自己的字段和列。因此,接下来,输入以下内容:

[NAME] VARCHAR(100) NOT NULL

这是NAME字段,数据类型是varchar,代表可变字符。这基本上是一个文本字段,因此将长度指定为100,并且因为条目应该被指定,我们将使其为NOT NULL

让我们再添加一个字段。要做到这一点,请输入以下内容:

[DATEADDED] DATE NULL

在这里,DATE ADDED是记录添加的日期,DATE是数据类型。您的屏幕应该看起来像图 16.1.14中显示的那样。

同样,如果您不想要空值,可以在设计窗口中取消选中。因此,设计选项卡中的表视图和代码视图是相互交互的:

图 16.1.14:已向表中添加了两个字段,NAME 和 DATEADDED

这里需要注意的一点是选项卡上标有 T-SQL。嗯,SQL 是结构化查询语言,微软版本是 T-SQL 或Transact 结构化查询语言。核心基本相同,但在微软版本中有一些附加功能。

更新数据库的结构

现在,您必须更新事物的结构,因此点击屏幕右上角的更新按钮。等待一会儿更新所有内容,然后点击更新数据库按钮。

图 16.1.15所示,更新已成功完成:

图 16.1.15:数据库更新已完成并成功!

现在,在左侧的 SQL Server 对象资源管理器窗格中,如果展开 dbo.People,然后展开列,您可以看到默认字段或列以及您创建的新字段,如图 16.1.16所示:

图 16.1.16:我们在 dbo.People 中创建的列

章节回顾

在上面的截图中,从顶部开始,第二项表示服务器(SQL Server)。然后,在其中,您有数据库,如 People 图标所示。当您展开数据库时,会有一个表的图标,一个列的图标,以及表示主键的关键图标。因此,使用的小图标代表不同级别的嵌套在这个数据库结构中。

这些是基础知识。

因此,请确保您可以重新创建所有这些。退出此窗口,然后单击“查看”,转到“起始页”。然后,执行以下操作:

  1. 打开 SQL Server 对象资源管理器窗格。

  2. 右键单击 SQL Server。

  3. 从下拉菜单中选择“断开连接”。

  4. 右键单击,然后从下拉菜单中选择“添加 SQL Server”。

  5. 浏览您的服务器。

  6. 单击连接屏幕底部的“连接”按钮。

  7. 展开服务器以显示“数据库”文件夹。

  8. 展开“数据库”文件夹,您可以看到People数据库。

  9. 打开“表”文件夹,那里有dbo.People表。

摘要

在本章中,您下载、安装并运行了 SQL Server 2017 Express。您在 Visual Studio 中使用 SQL Server,连接了两者,创建了一个 SQL Server 数据库,添加并定义了一个表,向表中添加了字段,并更新了数据库的结构。

在下一章中,您将学习如何连接到 SQL Server,然后在网页中显示数据库表中的记录。

第十七章:编写手动连接到表并检索记录的代码

在本章中,您将学习如何连接到 SQL Server,然后在网页中显示数据库表dbo.People中的记录。

在 HTML 中添加显示记录按钮

打开一个项目,并在页面中,在以<form id=....开头的行下放置一个按钮。为此,转到工具箱,获取一个Button控件,并将其拖放到那里。将按钮上的文本更改为显示记录。记住,记录只是一行信息,而一行当然是表中的一行,例如,从左到右横跨的一行。

现在,切换到设计视图,并左键双击显示记录按钮。这将带我们进入带有事件处理程序的Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该如图 17.2.1所示:

图 17.2.1:该项目的起始代码部分

添加一个命名空间

要使其与 SQL Server 一起工作,您必须添加一个命名空间。因此,转到文件顶部,并在using System下输入以下内容:

using System.Data.SqlClient;

创建连接字符串

现在,除此之外,我们将逐行构建代码。您需要的第一件事是连接字符串。因此,让我们做以下事情:

  1. 打开 SQL Server 对象资源管理器。

  2. 右键单击数据库的名称,例如 People,在此处查看其属性。

  3. 然后,要获取连接字符串,请确保展开属性窗格中的 General 节点,然后转到称为 Connection string 的节点,并双击它以选择它及其长描述。

  4. 接下来,右键单击长描述并复制它。(手工构造很难准确,最好直接从那里复制)。此过程显示在图 17.2.2中:

图 17.2.2:复制连接字符串

  1. 现在,在以protected void Button1_Click...开头的行下的大括号集合中输入以下内容:
string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

在这里,输入string connString =后,放置@符号以指示它是一个文字字符串或逐字字符串,应该准确解释。然后,在""符号中粘贴长字符串。因此,在这一行中,您当然有Data Source,计算机的名称,Initial Catalog作为数据库,Integrated SecurityTrue,因为我们是这样设置的,以及一些其他现在并不是很重要的信息。

连接到 SQL Server

要通过页面连接到 SQL Server,我们将尝试以下操作。首先,您必须创建一个要发给 SQL Server 的命令。为此,请输入以下内容:

string commandText = "Select * from dbo.People";

在这里,Select *表示从dbo.People中选择所有内容。请记住,我们称我们的数据库为People;因此,这意味着从People数据库中的表中选择所有内容。这就是它的意思:从该表中选择所有内容。

现在,还有一件事。当您处理低级资源时,特别是读取硬盘时,例如,您必须建立与硬盘的通信通道。因此,因为这是这种情况,接下来键入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

在这里,using是一个很好的构造,因为它允许您获取资源,使用资源,然后为您处理资源-非常好地和非常干净地。例如,SqlConnection就是这样一种东西。

现在,如果右键单击SqlConnection并从菜单中选择转到定义,并滚动到底部,您将看到有一行说 Dispose-protected override void Dispose。现在,如果展开protected override void Open()行,它说,使用由 system.Data.SqlClient.SqlConnection.ConnectionString 指定的属性设置打开数据库连接,如图 17.2.3所示:

图 17.2.3:protected override void Open 的扩展定义

如果你想知道可能会抛出哪些异常,所有的都列在protected override void Open()的定义中,同样也是protected override void Close()

构造函数是定义中列出的第一个函数。所以,现在让我们关闭它。

捕获异常

在下一阶段,因为可能会抛出错误,我们将使用trycatch,这样我们就可以捕获它们并显示给用户。首先在以using (SqlConnection conn...开头的行下面的开花括号下面的一行中输入try

try

接下来,在try下面插入一对花括号,然后在那里的闭合花括号下面输入以下内容:

catch (Exception ex)

显示错误

现在,如果生成错误,我们将显示它;因此在此行下面的一对花括号中输入以下内容:

sampLabel.Text = $"{ex.Message}";

如果数据库连接出现问题,将显示一个有用的消息。

打开连接

现在让我们继续连接。首先,让我们尝试打开它。在try下面的一对花括号中输入以下内容:

conn.Open();

这打开了一个连接。然后你将制作一个 SQL 命令,所以接下来输入以下内容:

SqlCommand sqlComm = new SqlCommand(commandText, conn);

这需要命令的文本。所以,我们将从前一行写的Select * from dbo.People中选择它;选择所有人,然后你说(command,conn),这是连接的名称。

请记住,在以string commandText...开头的行中,参数是command,在下面的行中是connection。这是两件不同的事情。

使用 SQL Server 数据读取器

现在,在下一阶段,输入以下内容:

using (SqlDataReader reader = sqlComm.ExecuteReader())

在这里,SqlDataReader是一个类。如果你将鼠标悬停在它上面,弹出的工具提示会告诉你这个东西究竟能做什么。现在,如果你右键单击SqlDataReader并选择“转到定义”,它特别实现了一个叫做IDisposable的接口,以及你可以在下拉时看到的所有函数。此外,如果你右键单击IDisposable并选择“转到定义”,那么就会有void Dispose(),展开后会说,执行与释放、释放或重置非托管资源相关的应用程序定义的任务。这特指低级磁盘写入和读取等操作。

接下来,你会看到前一行中的reader变量和sqlComm.ExecuteReader(),它返回一个SqlDataReader类,正如你在工具提示中所看到的。

现在在此行下面的一对花括号中输入以下内容:

while(reader.Read())

现在,为什么这是合法的?将鼠标悬停在Read上,你会看到它返回一个布尔值,并且它说,将 SqlDataReader 推进到下一条记录。它返回truefalse,无论是否还有记录可以读取。因此,在此行下面的一对花括号中输入以下内容:

sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

一定要放入<br>标签,因为可能会返回多个项目,所以你希望它们垂直堆叠。

在前一行中,012是索引;reader[0]reader[1]reader[2]表示列 1列 2列 3。这与索引为0的数组相同。

运行程序

现在在浏览器中启动这个程序。点击“显示记录”按钮,然后你会看到记录——Id、名称和日期,如图 17.2.4所示:

图 17.2.4:运行我们的程序的结果

如果你右键单击屏幕并选择查看源代码,如图 17.2.5中所示的高亮区域,它会生成一个 span。退出这个屏幕并关闭你不再需要的窗口。

图 17.2.5:如果查看源代码,你会看到它生成了一个 span

章节回顾

为了复习,本章的 HTML 文件的完整版本如下所示:

<!DOCTYPE html>
<html >
  <head runat="server">
    <title>Our First Page</title>
  </head>
  <body>
    <form id="form1" runat="server">
        <asp:Button ID="Button1" runat="server" Text="Show Records"
        OnClick="Button1_Click" />
      <div style="text-align:center;">
        <asp:Label ID="sampLabel" runat="server"></asp:Label>
      </div>
    </form>
  </body>
</html> 

本章的default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;//needed for SQL commands and connections
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;Mu
ltiSubnetFailover=False";
        //this is the SQL that runs against the table
        string commandText = "Select * from dbo.People";
        //using statement here helps to ensure connection is properly
        //disposed of here
        using (SqlConnection conn = new SqlConnection(connString))
        {
            try
            {
                conn.Open(); //open connection
                //make command object
                SqlCommand sqlComm = new SqlCommand(commandText, conn);
                //using here helps to ensure data reader is properly 
                //disposed of also
                using (SqlDataReader reader = sqlComm.ExecuteReader())
                {
                    //Read returns true while there are records to read
                    while(reader.Read())
                    {
                        //reader[0] is column 1, and so on for the 
                        //other two
                        sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";
                    }
                }
            }
            //a common exception occurs when the server is down and cannot 
            //be reached
            catch(Exception ex)
            {
                sampLabel.Text = $"{ex.Message}";
            }
        }
    }
}

您可以查看代码并注意以下内容,这是您在本章中学到的:

  1. 首先是连接字符串connString

  2. 然后是CommandText

  3. 获取SqlConnection

  4. 使用conn.Open()打开它。

  5. 创建一个命令:SqlCommand(commandText, conn)

  6. 使用SqlDataReader数据读取器。

  7. 读取值:sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

  8. 如果有任何异常,您可以使用catch (Exception ex)捕获它们。

总结

在本章中,您学会了如何连接到 SQL Server,然后在网页中显示来自数据库表的记录。您创建了一个连接字符串,连接到 SQL Server,编写了捕获异常和显示错误的代码,打开了连接,并与 SQL Server 的DataReader一起工作。

在下一章中,您将制作一个表,编写一个过程,并使用该过程将记录插入到表中。

第十八章:使用存储过程将记录插入表中

在本章中,您将学习如何使用存储在 SQL Server 的Programmability文件夹中的存储过程直接将记录插入表中。我们将通过 HTML 页面中的文本框进行操作。

在 HTML 中添加文本框和按钮

启动一个项目。首先,在页面中放入一对框。为此,请在以<form id= ....开头的行下输入以下内容:

Enter Name:<asp:TextBoxID="TextBox1" runat="server"></asp:TextBox><br />
Enter Date:<asp:TextBoxID="TextBox2" runat="server"></asp:TextBox><br />

对于Name字段,它只是一个文本框。因此,对于文本,换句话说,我们将使用一个字符串。转到工具箱,获取一个TextBox控件,并将其拖放到那里。对于日期,我们将尝试从框中解析为日期时间。

您的Default.aspx屏幕现在应该看起来像图 18.3.1中显示的屏幕。

图 18.3.1:本章的 Default.aspx 屏幕中的屏幕

请记住,我们有两个框,我们输入值,并将它们保存到表中。这是这里的目标。

接下来,让我们也在那里放一个按钮。因此,再次转到工具箱,获取一个按钮,并将其拖放到这些行的正下方。更改按钮上的文本,以使其更有帮助,例如,说插入并显示

因此,当您单击按钮时,您将插入新记录,并且还将显示记录以确认它与现有记录一起保存。

回顾在 SQL Server 中已经创建的内容

接下来,打开 SQL Server 对象资源管理器屏幕。现在,请记住,您创建了一个名为People的数据库,然后在其中还有一个名为People的表。此外,在其中,您有一个名为Id的列。这是主键。请记住,它是自动递增的,因此您不必指定 ID。也就是说,它会自动为您完成。

接下来,有两个字段:一个是NAME,另一个是DATEADDEDNAMEvarchar(100)DATEADDED的类型是date。两个值都必须提供,这就是为什么它说not null。到目前为止,SQL Server 对象资源管理器屏幕显示在图 18.3.2中:

图 18.3.2:数据库 People 的 SQL Server 对象资源管理器屏幕

创建新的存储过程

现在,展开Programmability文件夹。有一个名为存储过程的文件夹。右键单击它,然后选择添加新存储过程...。这是基本的存储过程代码:

图 18.3.3:默认存储过程屏幕

要使用存储过程,您首先需要将其重命名。为此,请将顶行中的[Procedure]更改为[AddName],如下所示:

CREATE PROCEDURE[dbo].[AddName]

正如您所看到的,它只是驻留在 SQL Server 中的一些代码。然后,您可以,例如,执行该代码以在数据库表中执行某些操作。

在我们的情况下,我们将使用此过程将记录插入表中。我们需要参数,因为我们将输入两个值。因此,按照以下方式编辑存储过程的下两行:

首先,将param1更改为Name,将默认值更改为int = 0,并将数据类型分配为varchar(100)

下一行,将param2更改为DateAdded,类型为date。因此,这是两个参数:

@Name varchar(100), 
@DateAdded date

现在,因为您不会选择记录,而是会插入记录,所以,我们将输入一个insert语句,然后在SELECT行的位置键入以下内容:

insert into People (NAME,DATEADDED) values (@Name,@DateAdded)

在这里,您insert into People数据库,然后列出应接收新信息的字段,即NAMEDATEADDED。然后,您输入values,然后是参数列表—@Name@DateAdded

记住,这行中的参数类似于之前创建的函数。通过它们,在你在 C#中编写函数时,将值传递给函数。同样的原理也适用于这里。通过参数,值被传递到存储过程的主体中,在这种特殊情况下,直接将字段值插入到表中。在这里,@Name@DateAdded的值被传递到NAMEDATEADDED中。这就是目标。

完整的存储过程显示在图 18.3.4中:

图 18.3.4:存储过程,dbo.AddName

更新数据库结构

现在,让我们更新一下结构;所以,单击“更新”按钮,然后在弹出的对话框中单击“更新数据库”,如图 18.3.5所示。

图 18.3.5:预览数据库更新对话框

更新后,展开“可编程性”文件夹,然后展开“存储过程”文件夹。在那里,你会看到dbo.AddName。现在,如果你展开dbo.AddName,会看到一系列参数:@Name@DateAdded

现在,让我们利用到目前为止所做的事情。单击Default.aspx选项卡,然后进入设计视图,双击“插入并显示”按钮。这将带我们进入Default.aspx.cs。删除Page_Load存根。我们将从图 18.3.6中显示的代码开始这个项目:

图 18.3.6:这个项目的起始代码

添加一个命名空间

再次,要使这个与 SQL Server 一起工作,你必须添加一个命名空间。所以,转到文件顶部,在using System下输入以下内容:

using System.Data.SqlClient;//commands and connections 

当然,这将用于诸如命令和连接之类的东西,你可以将其填写为注释。我们将在这一行下面再做一个,所以输入以下内容:

using System.Data;

这行也将为我们服务。会有相当多的代码,但它是高度顺序的——它从上到下自然而然地进行,它会为你完成工作。

现在,每次单击按钮时,你都希望清除标签,以便输出不会继续累积;所以,在以protected void Button1_Click...开头的行下的一对大括号之间,输入以下内容:

sampLabel.Text = "";

构建连接字符串

在下一阶段,你想要获取连接字符串;所以,在下一行开始时输入string connString =,然后跟上@符号使其成为逐字字符串,然后放入""符号。现在,要获取连接字符串,做以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击People数据库,然后选择“属性”。

  3. 在属性窗格中,双击连接字符串以选择它及其长描述。

  4. 然后,右键单击长描述并复制它。

  5. 在一对""符号之间粘贴描述。

连接字符串行应该看起来像下面这样:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

现在可以关闭 SQL Server 对象资源管理器和属性窗格。

初始化连接

在下一阶段,因为我们正在访问硬盘来读取和保存记录,输入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

这就是如何初始化连接。如果右键单击SqlConnection并选择“转到定义”,它会说它是DbConnection类型,并且继承自SqlConnection。现在,如果你右键单击DbConnection并选择“转到定义”,它会说它实现了IDisposable。然后,如果你右键单击IDisposable并选择“转到定义”,它会说,执行与释放、释放或重置非托管资源相关的应用程序定义的任务。因此,例如,对于从硬盘获取信息的低级通道,你必须确保它们被正确清理。现在可以关闭这个窗口。

捕获异常

接下来,因为在与数据库一起工作时可能会出现各种问题,您需要先try,然后捕获任何异常。为此,在前一行下的开放大括号下面,输入以下内容:

try
{

}
catch (Exception ex) 

在这里,我真的只是为了能够显示一些诊断信息而添加了catch (Exception ex)。接下来,在此下面的一对大括号之间,输入以下内容:

sampLabel.Text = $"{ex.Message}";

我们使用这行只是为了显示诊断信息。

尝试命令

现在,让我们进入try部分。这是一切都可能发生的地方。首先,让我们创建一个命令。在try下面的大括号之间输入以下内容:

SqlCommand cmd = new SqlCommand();

接下来,您将设置命令的类型,因此请输入以下内容:

cmd.CommandType = CommandType.StoredProcedure;

这行说明了它自己。

现在,为了实际获取文本以选择要调用的特定存储过程,您需要输入以下内容:

cmd.CommandText = "AddName";

记住,AddName是我们在 SQL Server 中称之为的存储过程。

添加参数

现在,对于下一个阶段,我们将添加所谓的参数。换句话说,您必须确保实际传递值到存储过程中,以便您可以将它们保存在表中。因此,请接下来输入以下内容:

cmd.Parameters.AddWithValue("@Name", TextBox1.Text);

在这里,我们从参数的名称开始:@Name,然后它的值将来自第一个框:TextBox1.Text

接下来,您将重复此逻辑,因此请输入以下内容:

cmd.Parameters.AddWithValue("@DateAdded", DateTime.Parse(TextBox2.Text));

在这里,@DateAdded是参数的名称,下一个阶段来自第二个框:TextBox2.Text。这行将转换框中的值,假设它可以转换为DateTime对象,以便它与数据库中的@DateAdded类型匹配。这就是为什么我们要采取这一步骤。

当然,在更现实的情况下,您可能想尝试DateTime.TryParse。为了避免过多的复杂性,我们将只使用DateTime.Parse

接下来输入以下内容:

cmd.Connection = conn;

您必须设置conn属性。我们在文件顶部创建了这个属性,以using(SqlConnection conn...开头的行。

对于下一行,请输入以下内容以打开连接:

conn.Open();

保存信息以便以后检索

在下一个阶段,我们将执行NonQuery。为此,请输入以下内容:

cmd.ExecuteNonQuery();

这行将保存信息。现在,从那里开始,当您想要检索信息时,请确保它按预期工作。我们只需将命令类型切换为Text类型的CommandType,因此请接下来输入以下内容:

cmd.CommandType = CommandType.Text;

接下来,我们将指定文本,因此请输入以下内容:

cmd.CommandText = "select * from dbo.People";

在这里,select *表示从People数据库中选择所有内容。

之后,输入以下内容:

using (SqlDataReader reader = cmd.ExecuteReader())

认识索引器的作用

现在,我将向您展示一些以前没有向您展示的东西。将鼠标悬停在ExecuteReader上。这将返回一个SqlDataReader类。现在,在前一行中右键单击SqlDataReader,然后选择“转到定义”。您还记得我们之前学到的索引器吗?看看它说的 public override object this[string name]。如果您展开它,它说它获取指定列的值及其本机格式,给定列名。如果您返回,下一个定义是 public override object this[int i]。如果您展开这个,它说,获取指定列的值及其本机格式,给定列的顺序,这里是列的编号。因此,public override object...行是指当前的SqlDataReader对象。这基本上是一个索引器。现在您可以看到索引器确实发挥了作用。现在您可以关闭这个。

为了利用这些信息,请在前面using行下的一对大括号之间输入以下内容:

while(reader.Read())

然后,在这行下面的一对大括号之间,输入以下内容:

sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";

在这里,在sampLabel.Text...之后,您指定reader[0]{reader[1]}{reader2},这是通过索引访问的三列。

您现在已经输入了程序的核心。

运行程序

现在,让我们来看看结果。在浏览器中打开这个。首先,输入一些值:Berry Gibbs作为Name,一个日期,然后点击“插入并显示”按钮。结果显示在图 18.3.7中:

![图 18.3.7:运行我们的程序后的初始结果所以,就是这样——它按预期工作。现在,让我们再试一个。输入Mark Owens作为Name,添加一个日期,然后再次点击“插入并显示”按钮。正如您在图 18.3.8中所看到的,它已经被自动添加了。这证实它已保存到表中,然后我们可以检索它:

图 18.3.8:运行程序后修改的结果

所以,这些是建立连接的基础知识。

现在考虑这一点。想象一下,在前一行中,我写成了cmd.CommandText = "AddNames"而不是AddName。换句话说,我拼错了存储过程的名称。如果我在浏览器中打开这个,就像图 18.3.9中所看到的那样,它会显示“字符串无法识别为有效的日期时间”。这很有用,对吧?我没有填写NameDate。所以,它无法转换为DateTime

图 18.3.9:未输入值运行程序的结果

现在,即使我为NameDate输入值,它也会显示“找不到存储过程'AddNames'”,如图 18.3.10所示,因为我拼错了存储过程的名称:

图 18.3.10:拼错存储过程名称后运行程序的结果

所以,使用try行,因为之后的所有命令都可能产生某种错误,至少您可以捕获它并显示错误消息,这样您就能知道发生了什么。所以,这非常有用。

章节回顾

为了回顾,包括注释的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;//commands and connections
using System.Data;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        sampLabel.Text = "";
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //put conn in a using so it can be properly closed and disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            try
            {
                //make sql command
                SqlCommand cmd = new SqlCommand();
                //specify type
                cmd.CommandType = CommandType.StoredProcedure;
                //write name of stored procedure inside SQL Server as  
                //the name here
                cmd.CommandText = "AddName";
                //read the field box 1, and pass in through @Name
                cmd.Parameters.AddWithValue("@Name", TextBox1.Text);
                //pass in date through @DateAdded
                cmd.Parameters.AddWithValue("@DateAdded", 
                DateTime.Parse(TextBox2.Text));
                //set connection property of command object
                cmd.Connection = conn;
                //open connection
                conn.Open();
                //execute the stored procedure
                cmd.ExecuteNonQuery();
                //change command type to just plain text
                cmd.CommandType = CommandType.Text;
                //write a simple SQL select statement
                cmd.CommandText = "select * from dbo.People";
                //execute reader
                using (SqlDataReader reader = cmd.ExecuteReader())
                {
                    //Read() returns true while it can read
                    while(reader.Read())
                    {
                        //reader[0] means get first column, 
                        //reader uses an indexer to do this
                        sampLabel.Text += $"<br>{reader[0]}, {reader[1]}, {reader[2]}";
                    }
                }
            }
            catch(Exception ex)
            {
                sampLabel.Text = $"{ex.Message}";
            }
        }
    }
}

总结

在本章中,您学习了如何使用存储过程直接将记录插入到表中,并存储在 SQL Server 的可编程文件夹中。您创建了一个新的存储过程,更新了数据库结构,构建了连接字符串,初始化了连接,尝试了命令并捕获了异常,添加了参数,保存了信息以供以后检索,并认识到了索引器的作用。

在下一章中,您将学习如何使用nullable关键字来确保具有缺失值的记录仍然可以被引入应用程序中。

第十九章:使用可空特性使应用程序更稳定

在本章中,您将学习使用nullable关键字来确保具有缺失值的记录仍然可以被引入应用程序中。

在 HTML 中添加一个显示人员按钮

打开 Visual Studio,创建一个项目。我们首先要做的是将一个简单的按钮放入 HTML 页面中。为此,请转到工具箱,获取一个Button控件,并将其放在以<form id=...开头的行下面。将按钮上的文本更改为“显示人员”。

您将创建一个名为Person的类,并且将从数据库中创建该类。要做到这一点,转到“视图”菜单并打开 SQL Server 对象资源管理器。请记住,我们创建了一个名为People的数据库,它由这些字段组成:IdNAMEDATEADDED

向 people 数据库添加一个字段

现在,让我们再添加一个字段。右键单击 dbo.People 表图标,然后选择“查看代码”。要添加一个额外的字段,在DATEADDED之后输入以下内容:

SALARY decimal(18,2)

这是一个新的字段类型,decimal (18,2)表示一个宽度为 18 个单位且有 2 位小数的字段;也就是说,它总共有 18 个单位宽,右边有 2 个单位,左边有 16 个单位,总共有 18 个单位。接下来,点击“更新”,然后点击出现的对话框中的“更新数据库”按钮。现在,您可以在 SQL Server 对象资源管理器窗格中看到,该字段已添加,如图 19.4.1所示:

图 19.4.1:工资字段已添加到 dbo.People

修改 dbo.People 表

现在,有了这个,您可以修改表。右键单击 dbo.People 表图标,然后转到“查看数据”。为了说明这个概念,在一些行中输入一些工资金额,将其他行留空。因此,数据库的组合将获得 NULL 信息。dbo.People 现在看起来像图 19.4.2

图 19.4.2:工资已输入到表中

如果点击刷新按钮()重新加载它,它会确认已保存。

如果双击工资列标题,它会展开列以适应。

在这里,如果您为薪水输入诸如77777777777777777777之类的内容,将显示一个错误消息,指示单元格(行,列)的值无效。因此,请记住,如果您尝试输入诸如788777.988888之类的内容,它将自动四舍五入为两位小数,即788777.99。这基本上就是decimal (18,2)的工作原理:它对可以输入的数据施加限制。

为该项目编写代码

在下一个阶段,转到设计视图,并双击“显示人员”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 19.4.3所示:

图 19.4.3:该项目的起始代码部分

现在,我们将编写代码。让我们逐步进行代码的创建。首先,在using System下面的文件顶部输入以下内容:

using System.Collections.Generic;

我们将使用这行来制作一个人员列表。然后,在它的下面也输入以下内容:

using System.Data.SqlClient;

创建 person 类

现在是下一个阶段;我们将创建一个名为Person的类;因此,请在以public partial class _Default...开头的行的上方输入以下内容:

public class Person

创建属性

接下来,我们将创建两个属性。因此,请在一对大括号之间输入以下行:

public string Name { get; set; }
public decimal? Salary { get; set; }

因为由 public decimal 引用的信息可能丢失,所以您要输入一个?符号。这是一个nullable数量,我们将其称为Salary。这就是这个类。

现在,要使用这个,你必须采取以下典型的步骤。首先,你希望在有人点击按钮时清除标签的输出,所以在以protected void Button1_Click...开头的行下面的花括号之间输入以下内容:

sampLabel.Text = "";

制作人员名单

在下一个阶段,我们将制作一个人员名单,所以在这一行下面输入以下内容:

List<Person> peopleList = new List<Person>();

在这里,我们称之为peopleList,并将其设置为新的人员列表。

构建连接字符串

在下一个阶段,你需要获取连接字符串,所以,在下一行开始,输入string connString =,然后跟上@符号使其成为逐字字符串,然后放上""符号。现在,要获取连接字符串,做以下操作:

  1. 点击菜单栏中的“查看”,选择“SQL Server 对象资源管理器”。

  2. 右键单击 People 数据库,选择属性。

  3. 在属性窗格中,双击连接字符串以选择它及其长描述。

  4. 然后右键单击长描述并复制它。

  5. 在一对""符号之间粘贴描述。

连接字符串行应该如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

现在可以关闭 SQL Server 对象资源管理器和属性窗格了。

输入与 SQL 相关的代码

现在,让我们转到与 SQL 相关的代码。首先,在连接字符串下面输入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

我们将调用 SQL 连接,conn,并使用连接字符串初始化新的 SQL 连接。

现在,让我们创建一个命令;在这一行下面的花括号之间输入以下内容:

SqlCommand comm = new SqlCommand("select * from dbo.People", conn);

接下来,通过在下面输入以下内容打开一个连接:

conn.Open();

接下来,在这条线下面输入以下内容:

using (SqlDataReader reader = comm.ExecuteReader())

从表中添加人员到列表

在过程的下一个阶段,从下面的花括号之间输入以下内容:

while(reader.Read())

当这个条件返回True时,我们将使用数据库中表的信息来创建对象。为了做到这一点,在这一行下面的花括号之间输入以下内容:

peopleList.Add(new Person() { Name = (string)reader[1], Salary = reader[3] as decimal? });

这里,这行的第一部分获取索引为 1 的列,将其转换为字符串,然后将其分配给每个对象的名称属性。然后,我们说Salary = reader[3],因为这是可能缺少值的部分,我们说decimal?—即可空的十进制。

显示记录

在这一点上我们已经接近了;最后阶段,当然是显示记录以查看可空的效果。在peopleList.Add...行下面的所有花括号之外(如下所示),输入以下foreach语句:

peopleList.Add(new Person()... 
    }}}foreach (Person p in peopleList)

接下来,在这一行下面的花括号之间输入以下内容:

sampLabel.Text += $"<br>{p.Name}, {p.Salary:C}";

这是我们应用程序的核心。

在运行此应用程序之前,再次注意...Salary = reader[3] as decimal? })Salary属性的一个有趣的地方。as decimal后面的问号表示它是一个可空的十进制数。十进制值可能丢失,这是一个不同的情况。如果你只是写as decimal,工具提示会说这是一个错误。

运行程序

现在,打开你的浏览器。点击“显示人员”按钮。让我们检查结果,如图 19.4.4所示:

图 19.4.4:运行程序的结果

注意,当没有工资时,它只显示姓名,不会提供其他任何信息,也不会崩溃。所以,这很好。

这是那个小符号的实际应用,问号,在我们的数据类型和可空之后。

章节回顾

为了回顾,包括注释的本章Default.aspx.cs文件的完整版本如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Collections.Generic; //needed for lists
using System.Data.SqlClient;//needed for commands and connections
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public class Person
{
    public string Name { get; set; }
    public decimal? Salary { get; set;}
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //clear label text every button click
        sampLabel.Text = "";
        //make list of people
        List<Person> peopleList = new List<Person>();
        //get connection string form SQL Server
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial
        Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection, be sure it's in a using so it's properly
        //disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make sql command
            SqlCommand comm = new SqlCommand("select * from dbo.People", conn);
            //open connection
            conn.Open();
            //make reader, be sure it's inside a using so it's properly 
            //disposed of
            using (SqlDataReader reader = comm.ExecuteReader())
            {
                while (reader.Read())
                {
                    //add new people to list, noting that reader[3] 
                    //could be null, so do it as "as decimal?" 
                    //nullable decimal
                    peopleList.Add(new Person() { Name = (string)reader[1], 
                    Salary = reader[3] as decimal? });
                }
            }
        }
        //display list of people, formatting salary as currency
        foreach(Person p in peopleList)
        {
            sampLabel.Text += $"<br>{p.Name}, {p.Salary:C}";
        }
    }
}

总结

在本章中,您学习了使用nullable关键字来确保具有缺失值的记录仍然可以被引入应用程序。您向People数据库添加了一个字段,修改了dbo.people表,创建了一个Person类,制作了一个人员列表,构建了连接字符串,输入了与 SQL 相关的代码,并从dbo.people表中添加了人员到列表中。

在下一章中,您将学习如何将图表拖入页面,然后通过 C#作为连接页面和数据库的语言,使它们与 SQL Server 内的一些简单表格配合使用。

第二十章:将图表控件连接到 SQL Server

在本章中,你将学习如何将图表拖放到页面中,然后通过 C#作为连接页面和数据库的语言,使其与 SQL Server 中的一些简单表一起工作。

将图表放入 HTML 页面

启动一个项目,我们首先要做的是将一个图表放在页面中。转到工具箱(Ctrl + Alt + X),在搜索栏中输入char...,然后将其拖放到以<form id=...开头的行下面。

如你在屏幕上看到的,这生成了所有以下标记。你可以保持不变。对我们的目的来说已经足够了:

<asp:Chart ID="Chart1"runat="server">
  <Series>
    <asp:SeriesName="Series1" ChartType="Point"></asp:Series>
  </Series>
  <ChartAreas>
    <asp:ChartArea Name="ChartArea1"></asp:ChartArea>
  </ChartAreas>
</asp:Chart>

你可以删除两个<div...行和<asp:Label ID...行。我们不需要它们。

在 HTML 页面中添加一个按钮

接下来,在</asp:Chart>行下面放置一个按钮。再次,转到工具箱,抓取一个Button控件,拖放到那里。将按钮上的文本更改为“加载数据”。这里,“加载数据”意味着加载并在图表中显示它。

注意,当你拖入一个图表时,页面会在System.Web.UI.DataVisualization.Charting的顶部添加整个块,如下所示:

<%@Register Assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %>

People数据库中添加一个新表

接下来,在下一个阶段,点击菜单栏中的“查看”,选择“SQL Server 对象资源管理器”。你需要添加一个新表,所以在People数据库中,右键点击“表”文件夹,选择“添加新表...”。你的屏幕应该看起来像图 20.5.1中所示的样子:

图 20.5.1:一个空白的新表

接下来,在Id字段中输入XValues,然后点击数据类型字段。开始输入decimal,注意到decimal(18,0)会自动显示出来。现在将其改为(18,3)。这意味着一个宽度为 18 且有 3 位小数的字段;也就是说,总共有 18 位,右边 3 位,左边 15 位。对于这个字段,应该勾选“允许空值”。对于YValues也是一样。假设我们做了一个实验,测量了一些数量。所以,在Id字段中输入YValues,在数据类型字段中输入decimal(18,3),并且对于这个字段勾选“允许空值”。

接下来,右键点击“Id”,选择“设置为主键”。

启用自动递增

接下来,你想要启用自动递增,具体来说就是以下内容:

  1. 首先,将表重命名为ExperimentValues,如下所示:
    CREATE TABLE [dbo].[ExperimentValues]
  1. 在“主键”后面,输入identity(1,1),如下所示:
    [Id] INT NOT NULL PRIMARY KEY identity(1,1),

在这里,identity(1,1),正如你之前学到的,意味着这个字段将从 1 开始每次添加新记录时增加 1。所以,这是我们表的结构,如图 20.5.2所示:

图 20.5.2:本章表的结构

向新表中添加值

接下来,点击“更新”按钮。在弹出的对话框中点击“更新数据库”。

现在,你有了ExperimentValues。右键点击它,选择“查看数据”,然后让我们添加一些数值,如图 20.5.3所示:

图 20.5.3:添加到 ExperimentValues 表中的值

现在,我们在表中有了一些数值。再次注意到,“Id”字段是自动递增的——它从 1 开始,每添加一条新记录就增加 1。关闭表窗口,回到Default.aspx.cs

现在,双击“设计”按钮,会出现一个小图表,如图 20.5.4所示:

图 20.5.4:实验值表中数据的理论预览

编码项目

这个图表还不代表真实的数据。这只是一个理论预览。所以,双击“加载数据”按钮,这会打开Default.aspx.cs中的事件处理程序。删除Page_Load存根。我们将从本项目的代码开始,如图 20.5.5所示:

图 20.5.5:此项目的起始代码

添加命名空间

首先,您必须添加一个命名空间。因此,转到文件顶部,在using System下输入以下内容:

using System.Data.SqlClient;

此行用于连接和命令。

构建连接字符串

在下一阶段,您需要连接字符串。因此,在下一行开始时,输入string connString =,然后输入@符号使其成为逐字字符串,然后放置""符号。现在,要获取连接字符串,请执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击 People 数据库,然后选择属性。

  3. 在属性窗格中,双击连接字符串以选择其长描述。

  4. 然后,右键单击长描述并将其复制。

  5. ""符号集之间粘贴描述。

然后,连接字符串行应如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

这是特定于您计算机的连接字符串。您现在可以关闭 SQL Server 对象资源管理器和属性窗格。

现在,在此行下方输入以下内容:

using (SqlConnection conn = new SqlConnection(connString)) 

编写 SQL 查询

接下来,您将创建commandText变量。因此,在一对大括号之间,输入以下内容:

string commandText = "select XValues, YValues from dbo.ExperimentValues";

要定义文本,您必须编写实际的 SQL 查询,因此输入select XValues, YValues from dbo.ExperimentValues。这将从ExperimentValues表中的这两个列名中选择XValuesYValues

创建命令对象

现在,您需要创建命令对象,因此接下来输入以下内容:

SqlCommand command = new SqlCommand(commandText, conn);

在这里,您传入两个相关数量,两个参数,具体来说是(commandText, conn)

打开连接并创建 SQL 数据读取器

在下一阶段,您将打开一个连接,因此在上一行下方输入以下内容:

conn.Open();

然后,您将创建一个 SQL 数据读取器,因此接下来输入以下内容:

SqlDataReader reader = command.ExecuteReader(); 

此行将获取我们需要的数据。

现在您已经完成了所有这些,接下来在上一行下方输入以下内容:

Chart1.DataBindTable(reader, "XValues");

请注意,我们包括列名XValues,它将用作x轴的标签。因此,x轴是水平轴。

运行程序

这是应用程序的核心。在浏览器中启动它,并单击“加载数据”按钮。

图 20.5.6:来自 ExperimentValues 表的实际数据的显示

这是数据,如图 20.5.6所示。它具有沿水平和垂直轴的值。

修改程序以显示 Y 值

如果您愿意,只是为了向您展示它有多容易,您可以将以下行更改为 Y 值。换句话说,您可以将它们颠倒过来:

Chart1.DataBindTable(reader, "YValues");

现在,在浏览器中启动它,并再次单击“加载数据”按钮。结果显示在图 20.5.7中:

图 20.5.7:来自 ExperimentValues 表的值的图表

现在您看到它看起来非常不同。这就是您如何制作简单的图表。现在保存这个。这就是整个应用程序。

章节回顾

让我们回顾一下我们做了什么:您构建了连接字符串并完成了在using (SqlConnection conn...行内建立连接,以便可以正确处理连接。然后,您编写了查询字符串,创建了命令对象,打开了连接并执行了读取器。最后,您使用DataBind将数据库表绑定到图表,以便可以显示结果。

本章的Default.aspx.cs文件的完整版本,包括注释,显示在以下代码块中:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //enclose connection making inside a using so connection is
        //properly disposed of
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make command text
            string commandText = "select XValues, YValues from dbo.ExperimentValues";
            //make command object
            SqlCommand command = new SqlCommand(commandText, conn);
            //open connection
            conn.Open();
            //execute reader to read values from table
            SqlDataReader reader = command.ExecuteReader();
            //bind chart to table do display the results
            Chart1.DataBindTable(reader, "XValues");
        }
    }
}

摘要

在本章中,您学习了如何将图表拖入页面,然后通过 C#作为连接页面和数据库的语言,使它们与 SQL Server 中的一些简单表一起工作。您将图表放入 HTML 页面,向People数据库添加了一个新表,启用了自动递增,向新表添加了值,添加了命名空间,构建了连接字符串,编写了 SQL 查询,打开了连接并创建了一个 SQL 数据读取器,运行了程序,最后修改它以显示 Y 值。

在下一章中,您将学习如何将 LINQ 与 SQL 和 SQL Server 一起使用。

第二十一章:使用 LINQ 操作来自 SQL Server 的表

在本章中,您将学习如何将 LINQ 与 SQL 和 SQL Server 一起使用。

更改 ExperimentValues 表中的数据

我们将使用在上一章中创建的数据库表ExperimentValues,如图 21.6.1所示:

图 21.6.1:第二十章的 ExperimentValues 表

请记住,表中有一个Id字段(PK,主键整数,非空),然后是XValues(十进制,(18, 3),表示宽 18 个单位,小数点后 3 位,然后左边 15 个单位,总共 18 个单位。如果你愿意,你可以将其设置为null。同样,YValues(十进制,(18, 3);所以,小数点后 3 位,然后左边 15 个单位,总共 18 个单位。

现在确保你在里面有数据。所以,右键单击dbo.ExperimentValues并选择查看数据。你应该看到我们在上一章中输入的数据。当然,你可以随时更改它。为了使事情变得更容易,让我们将值更改为图 21.6.2中显示的值:

图 21.6.2:ExperimentValues 表的新数据

如果你愿意,你可以重新加载它以查看它是否已保存。这就是我们简单的数据库表。

总结字段

现在我们将进入并总结字段。您将使用 LINQ 找到X值的总和和Y值的总和。首先,进入,并在以<form id= ....开头的行下方放置一个按钮。转到工具箱(Ctrl + Alt + X),获取一个Button控件,并将其拖放到那里。更改按钮上的文本以显示 Sum Fields。当然,还可以执行其他几个操作。这只是一个操作:求和。

关闭工具箱并切换到设计视图。双击 Sum Fields 按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 21.6.3所示:

图 21.6.3:该项目的起始代码部分

添加命名空间

首先,在文件顶部的using System下,输入以下所有必要的行:

using System.Data.SqlClient;
using System.Linq;
using System.Data;

构建连接字符串

下一阶段将是建立连接字符串,所以在以下行下的一对大括号中,首先输入string connString =,然后跟上@符号使其成为逐字字符串,然后放入""符号。现在要获取连接字符串,执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击People数据库,然后选择属性。

  3. 在属性窗格中,双击连接字符串以选择带有其长描述的内容。

  4. 然后右键单击长描述并复制它。

  5. 将描述粘贴在一对""符号之间。

然后连接字符串行应如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

现在可以关闭 SQL Server 对象资源管理器和属性窗格。

建立 SQL 连接

在下一阶段,我们将像往常一样进行。所以,输入以下行:

using (SqlConnection conn = new SqlConnection(connString))

注意,当你输入这个时,你会看到文件顶部的using System.Data.SqlClient;行中的SqlClient变为活动状态。它变成了黑色。这意味着 SQL 连接存储在那里,如果你将鼠标悬停在上面,它还会告诉你这一点:class System.Data.SqlClient.SqlConnection

在下一阶段,在此行下的一对大括号之间输入以下内容:

SqlCommand command = new SqlCommand("select * from dbo.ExperimentValues", conn);

SqlCommand()后面的括号之间,将定义命令的文本直接放入构造函数作为参数。记住,你已经有了ExperimentValues*符号表示选择所有列。所以,你需要命令文本和连接。

制作适配器

接下来,你将创建一个适配器。所以,输入以下内容:

SqlDataAdapter adapter = new SqlDataAdapter(command);

在这里,SqlDataAdapter是存在于实际数据库和我们之间的东西。这是一种将信息从这里适应到那里的方式。要初始化它,可以传入特定的 SQL 命令。因此,在我们的情况下,我们将传入(command)。您可以在此行后面添加注释//make adapter

制作数据表

接下来,您将制作一个数据表,如下所示:

DataTable dt = new DataTable();

再次注意,一旦输入DataTable,文件顶部的using System.Data;命名空间就会激活。因此,如果将鼠标悬停在DataTable上,它会说 System.Data.DataTable 类。这就是它存储的地方。因此,它存储在Data命名空间中。

用数据填充表

现在我们需要用一些信息填充这个表。因此,接下来输入以下内容:

adapter.Fill(dt);

在这里,您输入适配器的名称,然后填充数据集。因此,通过这三行,首先制作一个适配器并使用 SQL 命令获取信息,然后制作一个数据表。然后使用适配器填充该表。现在我们可以如下使用它:

var summedXValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(1));

在这里,我们可以将数据表变成可枚举的,以便我们可以浏览它。请注意,我们在其中使用=>添加了一个 Lambda 表达式;<decimal>是数据类型,然后,如果将鼠标悬停在<decimal>()括号后面,工具提示会说(DataColumn column): 提供对指定行中每个列值的强类型访问。因此,在括号之间插入 1。

接下来,为summedYValues变量输入以下内容,并注意我们在括号之间放了一个 2:

var summedYValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(2));

一旦您输入了所有这些,然后您可以显示xy值的总和,因此接下来输入以下行:

sampLabel.Text = $"Sum of y values={summedYValues}";
sampLabel.Text += $"<br>Sum of x values={summedXValues}";

显示总和值

在前面的行中,请注意第一行不需要<br>标签,但下一行需要。此外,第一行只需要=, 而下一行需要+=来追加。

运行程序

请记住,目的是对字段求和,因此打开浏览器,然后单击 Sum Fields 按钮:

图 21.6.4:运行我们程序的初始结果

您可以看到 Y 值的总和为 50.000,X 值的总和为 10.000。您可以通过打开 SQL Server 对象资源管理器窗格,右键单击ExperimentValues表,并将值相加来确认这是否按预期工作,如图 21.6.5所示:

图 21.6.5:添加 X 和 Y 列中的值

XValues 列加起来是 10.000,YValues 列加起来是 50.000。这两个总和与程序运行的结果一致。

关闭ExperimentValues表窗口和 SQL 对象资源管理器窗格。这次又按预期工作了。

添加注释

现在在连接字符串行上面添加此注释:

//make connection string

每当处理低级资源时,应用using块。在以using (SqlConnection conn...开头的行上面添加以下评论:

//make connection object

请记住,目的是正确地制作、使用和处理它,以便不会留下任何内存泄漏。每当处理硬盘访问时,例如,都要这样做。

在以SqlCommand command =...开头的行上面添加以下评论:

//make SQL command

然后,在以sqlDataAdapter adapter...开头的行上面添加以下评论以强调适配器的目的:

//make adapter object and pass in the command

同时,在该行的末尾添加此评论:

//make adapter

接下来,对于DataTable dt...,添加此注释:

//make table

适配器是允许我们填充表的机制,因此在adapter.Fill(dt);行的末尾添加以下评论:

//fill table with adapter

接下来,在第 30 行上面添加以下评论:

//lines 30 - 31 use LINQ to sum each column

最后,在第 33 行上面添加以下评论:

//lines 33-34 display the results in the web page

在下一行中,请注意这里的字段是decimal,因为这是我们在 SQL Server 中创建的,1 只是表示第一个字段,索引为 1。然而,请记住,这实际上意味着第二列,因为有三列:

var summedXValues = dt.AsEnumerable().Sum(row => row.Field<decimal>(1));

图 21.6.6所示,Id 实际上是索引 0,XValues 是索引 1,YValues 是索引 2。这就是为什么我们在这里使用 1 和 2,因为有三列,其中第二列位于索引 1:

图 21.6.6:Id 是索引 0,XValues 是索引 1,YValues 是索引 2

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下面的代码块所示。

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
using System.Linq;
using System.Data;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS;Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection object
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //make sql command
            SqlCommand command = new SqlCommand("select * from dbo.ExperimentValues", conn);
            //make adapter object and pass in the command
            //make adapter
            SqlDataAdapter adapter = new SqlDataAdapter(command);
            //make table
            DataTable dt = new DataTable();
            adapter.Fill(dt); //fill table with adapter
            //lines 30 - 31 use linq to sum each column
            var summedXValues = dt.AsEnumerable().Sum(row => row.Field< 
            decimal>(1));
            var summedYValues = dt.AsEnumerable().Sum(row => row.Field< 
            decimal>(2));
            //lines 33-34 display the results in the web page
            sampLabel.Text = $"Sum of y values={summedYValues}";
            sampLabel.Text += $"<br>Sum of x values={summedXValues}";
        }
    }
} 

总结

在本章中,您学习了如何将 LINQ 与 SQL 和 SQL Server 一起使用。您更改了ExperimentValues表中的数据,编写了使用 LINQ 对字段进行汇总的代码,添加了命名空间,构建了连接字符串,建立了 SQL 连接,创建了适配器,创建了数据表,填充了数据表,显示了汇总值,运行了程序,并最终添加了注释。

在下一章中,您将学习如何制作一个页面,将页面上的内容保存到硬盘上,然后再读取它。

第二十二章:创建一个保存文本到磁盘的页面

在本章中,您将学习如何制作一个页面,将页面上的内容保存到硬盘上,然后再读取它。

创建一个保存文本的应用程序

在本章结束时,您将制作一个类似于图 22.1.1所示的小应用程序。对于保存位置,您可以输入类似于c:\data\samp.txt的内容,以保存一个文本文件:

图 22.1.1:与本章中要构建的应用程序类似的用户界面

然后,您可以输入一些文本,例如这是要保存的一些示例文本。

图 22.1.2:在应用程序中输入一些示例文本的保存位置

现在单击“保存文本”按钮。这将弹出记事本,以确认已保存,如图 22.1.3所示:

图 22.1.3:示例文本已保存,并弹出记事本

如果您愿意,您也可以在页面中打开文本。因此,单击“打开”,然后它保存在页面中,如图 22.1.4所示:

图 22.1.4:示例文本保存在页面中

此外,如果您没有指定路径,显然会导致错误,如图 22.1.5所示。在这种情况下,它显示“空路径名不合法。”消息:

图 22.1.5:未输入保存位置时显示的错误消息

所以,这是这里的目标。记住这个例子。

现在让我们创建一个项目。转到“文件”|“新建”|“网站...”然后,从“视图”菜单中,转到“解决方案资源管理器”,并单击“Default.aspx”。

为您的项目创建用户界面

首先,您必须构建您的用户界面,因此您需要在 HTML 页面中有一个文本框,您可以在其中输入路径。为此,请转到工具箱,获取一个TextBox控件,并将其放在以<form id=...开头的行下面。在此行的开头输入“保存路径”,如下所示:

Save Path:<asp:TextBoxID="TextBox1"runat="server"></asp:TextBox><br/>

接下来,您将有一个按钮,基本上是用来在网页中打开保存的文件,因此将按钮中的文本更改为“在页面中打开”,如下所示:

<asp:Button ID="Button1"runat="server"Text="Open In Page" /><br />

在这种情况下,它只是意味着将简单的文本读回页面。现在进入设计视图,查看到目前为止的用户界面,如图 22.1.6所示:

图 22.1.6:到目前为止的用户界面

接下来,您还需要一个地方来输入要保存的文本。因此,获取另一个TextBox控件,并在此行的开头输入Text To Be Saved,如下所示:

Text To Be Saved:<asp:TextBoxID="TextBox2"runat="server"Width="1069px"></asp:TextBox><br/>

删除两个<div>行 - 您不需要它们。

现在让我们添加一个“保存”按钮。因此,我们将有一个“在页面中打开”按钮和一个“保存”按钮。现在,从工具箱中拖动一个按钮,并将其放在Button1上方。(当然,布局取决于您。)更改文本如下:

<asp:Button ID="Button2"runat="server"Text="Save" /><br />

该项目的完整 HTML 文件如下代码块所示。

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html>
<html >
  <head runat="server">
    <title>Our First Page</title>
  </head>
  <body>
    <form id="form1" runat="server">
      Save Path:<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
      <br />
      <asp:Button ID="Button2" runat="server" Text="Save" 
      OnClick="Button2_Click" />
      <br />
      <asp:Button ID="Button1" runat="server" Text="Open In Page" 
      OnClick="Button1_Click" />
      <br />
      Text To Be Saved:<asp:TextBox ID="TextBox2" runat="server" 
      Width="1069px"></asp:TextBox>
      <br />
      <asp:Label ID="sampLabel" runat="server"></asp:Label>
    </form>
  </body>
</html>

现在,如果您进入设计视图,将显示一个简单的界面,如图 22.1.7所示:

图 22.1.7:完整的简单用户界面

如果您愿意,您可以拖动Text To Be Saved框的一个角,将其放大,以便有更大的地方保存您的文本。现在您有一个保存的地方,一个保存按钮,打开页面和sampLabel。这对我们的目的已经足够了。

开始编写项目

现在双击“保存”按钮。这将带我们进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应如图 22.1.8所示:

图 22.1.8:该项目的起始代码

因此,对于“保存”按钮代码,您必须添加一个命名空间。首先,在文件顶部的using System下,输入以下内容:

using System.IO;

捕获异常

让我们利用一下这个。现在,因为有可能有人没有在框中输入任何内容,可能会生成错误消息,你想要捕获它。所以,在以protected void Button2_Click...开头的行的大括号下面,输入以下内容:

try
{

}
catch(Exception ex)
{
   sampLabel.Text = ex.Message;
}

前面的sampLabel.Text行用于显示生成并捕获的异常的消息。

创建一个 StreamWriter 类

接下来,我们将使用一个StreamWriter类。这个类可以获得对硬盘等的低级访问,所以你必须确保它在一个using语句内。你需要能够创建它,使用它,并完全处理掉它。所以,在try下面的一对大括号之间输入以下内容:

using (StreamWriter sw = new StreamWriter(TextBox1.Text))

初始化这个类时,要传入的参数是TextBox1.Text。所以这个将写入文件。确认一下,你可以在Default.aspx的源视图中,验证Save PathTextBox1

现在,为了实际写入文件,在上一条语句下的一对大括号之间输入以下内容:

sw.Write(TextBox2.Text);

在这里,sw是一个流写入器,sw.write是它的一个方法,一个函数,然后你将取出TextBox2的东西并写入。所以,从TextBox1中获取路径,从TextBox2中取出文本。

现在,如果你右键点击StreamWriter并选择Go To Definition,结果看起来像图 22.1.9所示:

图 22.1.9:StreamWriter 的定义

在最底部,你可以看到它有Dispose,你可以在顶部附近看到StreamWriter继承自TextWriter。接下来,如果你选择TextWriterGo To Definition,你会看到有IDisposable,如图 22.1.10所示:

图 22.1.10:TextWriter 的定义

如果你右键点击IDisposable并选择Go To Definition,就会出现Dispose,如图 22.1.11所示:

图 22.1.11:IDisposable 的定义

如果你展开public interface IDisposable,它会显示注释,执行与释放、释放或重置非托管资源相关的应用程序定义的任务;换句话说,这些是低级资源,所以最好不要使用它。

另外,你想要确认它保存到文件中,所以在文件顶部的using System.IO;下面输入以下内容:

using System.Diagnostics;

这一行将在一切保存之后打开记事本。

现在,在sampLabel.Text = ex.Message;下面的闭合大括号下面输入以下内容:

Process.Start("notepad.exe", TextBox1.Text);

在这里,TextBox1.Text只是将你在框中输入的文本反馈出来。

接下来,回到Default.aspx。在设计视图中,双击打开页面按钮。这将再次带你进入Default.aspx.cs。你接下来编写的代码将在Open按钮上执行。所以逻辑上非常相似。

现在,在以protected void Button1_Click...开头的行的大括号下面,输入以下内容:

try
{
}
catch(Exception ex)
{
   sampLabel.Text = ex.Message;
}

再次,你使用trycatch,因为在尝试打开时可能会产生错误。在标签上显示相同的文本。基本上,从上面复制try/catch块,然后粘贴到下面。它完全相同。

创建一个 StreamReader 类

现在,然而,你将在这个try语句下的大括号之间输入以下内容:

using (StreamReader sr = new StreamReader(TextBox1.Text))

再次,StreamReader是一个类——它需要一个流。就像是两个地方之间的通信渠道。

接下来,为了显示文本,在这行下面的一对大括号之间输入以下内容:

sampLabel.Text = sr.ReadToEnd();

在这里,ReadToEndStreamReader类内部可用的函数,它从当前位置读取到流的末尾的所有字符。这对我们的目的已经足够了。所以这就是代码。

你已经创建了在设计视图中看到的简单界面,如图 22.1.7所示。

运行程序

现在,在浏览器中打开它。在顶部,您有保存路径。首先,想象一下在框中未输入路径,然后单击保存按钮。如图 22.1.12所示,它会打开记事本;所以,那部分是有效的。但是,它显示了消息“空路径名称不合法”。但这是一个有用的东西,对吧?

图 22.1.12:未指定路径时,显示错误消息并打开空白记事本

现在,让我们指定一个合法的路径,比如c:\data\temp.txt。然后,在“要保存的文本”框中输入“大项目”。单击保存按钮。大项目被打开,文件名为 temp,如图 22.1.13所示。所以,它已经保存了:

图 22.1.13:指定合法路径后,记事本打开,显示“要保存的文本”框中的文本

如果您愿意,您可以确认它将在页面中打开,因此单击“在页面中打开”,页面上也会显示“大项目”,如图 22.1.14所示:

图 22.1.14:文本也在页面中打开

所以,它正在按预期工作。

章节回顾

回顾一下,回到Default.aspx.cs。因为您正在处理输入/输出资源,所以必须确保您有 I/O(using System.IO;);另外,因为您正在处理低级磁盘写入和读取,所以确保您将StreamWriterStreamReader封装在using中,这样您就可以获取它们,使用它们,并正确地处理掉它们。最后,因为通常会生成异常,例如当找不到路径或类似情况时,还要使用trycatch,并向用户显示消息,使应用程序看起来专业。请记住,这将运行,因为我们正在从本地计算机运行页面。

本章的Default.aspx.cs文件的完整版本,包括注释,如下代码块所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.IO;//needed for files
using System.Diagnostics;//needed for Process.Start
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button2_Click(object sender, EventArgs e)
    {
        //this is needed so that errors can be caught
        try
        {
            //enclose Streams inside usings because streams deal with 
            //low level access
            using (StreamWriter sw = new StreamWriter(TextBox1.Text))
            {
                //this writes the text to a file
                sw.Write(TextBox2.Text);
            }
        }
        catch(Exception ex)
        {
            sampLabel.Text = ex.Message;
        }
        Process.Start("notepad.exe", TextBox1.Text);
    }
    protected void Button1_Click(object sender, EventArgs e)
    {
        //same as try above
        try
        {
            //save as StreamWriter above
            using (StreamReader sr = new StreamReader(TextBox1.Text))
            {
                //read file contents into label text property
                sampLabel.Text = sr.ReadToEnd();
            }
        }
        catch (Exception ex)
        {
            sampLabel.Text = ex.Message;
        }
    }
}

总结

在本章中,您学会了如何创建页面,然后将页面上的内容保存到硬盘并读取回来。您创建了一个简单的用户界面,编写了捕获异常的代码,并创建了StreamWriterStreamReader类。

在下一章中,您将学习如何在 ASP.NET 中使用上传功能。

第二十三章:创建使用文件上传控件的页面

在本章中,您将学习如何在 ASP.NET 中使用上传功能。为此,我们将在页面上创建一个带有以下控件的界面:

图 23.2.1:我们用户界面的控件

当您点击浏览按钮时,您应该会得到一些示例文件,如图 23.2.2所示。选择其中一个文件,例如samp.txt

图 23.2.2:C:\data 目录文件列表

现在,当您点击上传按钮时,一旦文件上传完成,浏览器将显示一个消息,类似于图 23.2.3中显示的消息,显示文件已上传的位置,目录中有多少文件,以及它们的名称。这是我们的目标:

图 23.2.3:单击上传按钮时显示的消息

确保您的硬盘的根目录中有一个名为data的文件夹,并且在该文件夹中,您有另一个名为uploads的文件夹。要在命令行级别执行此操作,请转到命令提示符(C:\)并按照以下步骤操作:

  1. 输入cd..以切换到根目录。

  2. 然后,输入cd data并按Enter

  3. C:\data目录下,输入dir,如下所示:

 C:\data\dir
  1. C:\data目录下,输入cd uploads,如下所示:
 C:\data\cd uploads
  1. C:\data\uploads目录下,再次输入dir
 C:\data\cd uploads\dir

您的屏幕将类似于图 23.2.4所示的屏幕:

图 23.2.4:C:\data\uploads 的命令行目录列表

现在让我们实现这一点。

从头开始启动我们的项目

让我们从头开始创建一个新项目。转到文件 | 新建 | 网站...;然后,转到解决方案资源管理器,单击Default.aspx

现在我们可以看到一个基本的 HTML。让我们把一个FileUpload控件放进去。要做到这一点,去工具箱,抓取一个FileUpload控件,并将其拖放到以<form id=...开头的行下方,并在其后添加一个<br/>标签,如下所示:

<asp:FileUploadID ="FileUpload1" runat="server" /><br/>

接下来,在这一行下面放入一个按钮,如下所示:

<asp:Button ID="Button1" runat="server" Text="Upload" /><br /> 

更改按钮上的文本,使其显示更有意义的内容,例如上传

删除两个<div>行——您不需要它们。

当您进入设计视图时,您会看到这个简单的界面,如图 23.2.5所示。您有一个浏览按钮,它是上传控件的一部分,因此不需要单独放在那里,还有一个上传按钮:

图 23.2.5:我们项目的简单界面

现在,双击上传按钮。这将带您进入Default.aspx.cs。删除Page_Load块。该项目的起始代码的相关部分应该如图 23.2.6所示:

图 23.2.6:该项目的起始代码

添加一个命名空间

要读取文件,首先在文件顶部的using System之后插入以下内容:

using System.IO;

将文件保存到特定位置

您需要做的第一件事是指定文件应该保存的位置。因此,在以protected void Button1_Click...开头的行下方的一对大括号中输入以下内容:

string savePath = @"c:\data\uploads\";

这里,savePath是文件将被保存的路径的名称。您输入@符号来创建一个逐字字符串,c:\data\uploads就是文件将被保存的地方。请记住,如果您删除@符号,会导致错误,因为它意味着按照原样读取字符串。

接下来,输入以下内容:

if(FileUpload1.HasFile)

这里,HasFile是一个简单的属性。然后,您可以这样说(在一对大括号之间):

{
    string fileName = FileUpload1.FileName;
}

此行获取文件名,这里再次,FileName是一个属性。

现在,输入以下内容:

savePath += fileName;

因此,savePath首先是文件夹结构,然后您还将文件名附加到其中。

保存文件

现在,要实际保存文件,请输入以下内容:

FileUpload1.SaveAs(savePath);

请记住,每当您想了解这些术语中的任何一个更多信息时,都可以这样做。只需右键单击它们,然后选择转到定义。例如,如图 23.2.7所示,如果展开public void SaveAs行,它会说将上传文件的内容保存到 Web 服务器上指定的路径。它还会引发异常,因此存在错误的可能性。请记住这一点。

图 23.2.7:Go To Definition 中 SaveAs 的解释

向用户显示消息

接下来,让我们向用户显示一些有用的诊断消息。为此,请输入以下内容:

sampLabel.Text = "<br>Your file was saved as " + fileName;

另一种可能性是没有文件。换句话说,FileUpload1.HasFile为 false。如果是这种情况——没有文件,您可以将前一行复制到下面,并更改文本以使其有意义。从上一个闭合大括号下面开始输入else,然后输入以下内容:

{
   sampLabel.Text = "<br>You did not specify a file to upload.";
} 

确定目录中存储了哪些文件

接下来,让我们去看看目录中有哪些文件。因此,在上一行下面的闭合大括号下面输入以下内容:

string sourceDirectory = @"C:\data\uploads";

同样,您将从与之前以“String savePath…”开头的相同位置获取它,并将c:\data\uploads\粘贴到这一行中。

接下来,您可以在以下行上键入try,并在其下的一对大括号之间输入以下内容:

{
   var txtFiles = Directory.EnumerateFiles(sourceDirectory, "*.txt");
}

输入EnumerateFiles时出现的工具提示显示有几种重载——string pathstring searchPattern。因此,这里的路径将是sourceDirectory,而searchPattern将用于搜索以.txt结尾的所有内容。因此我们在末尾放置*.txt。这就是如何枚举所有文件。

确定返回类型

如果您将鼠标悬停在上一行中的var上,弹出的工具提示会告诉您返回类型是什么。它说IEnumerable。现在将鼠标悬停在EnumerateFiles上,右键单击它,然后选择转到定义:

图 23.2.8:在定义中,它显示返回类型为 IEnumerable

图 23.2.8所示,返回类型是IEnumerable,这意味着您可以遍历结果,或者使用foreach语句显示它们。

接下来,在上一行下面输入以下内容:

foreach(string currentFile in txtFiles)

然后就在这下面输入以下内容(缩进):

sampLabel.Text += $"<br>{currentFile}";

探索 EnumerateFiles 的异常

现在,再次将鼠标悬停在EnumerateFiles上,右键单击它,然后选择转到定义。展开定义并查看它可能引发的异常。有很多异常,其中一些在图 23.2.9中显示:

图 23.2.9:EnumerateFiles 可能引发的异常的部分列表

例如,DirectoryNotFound可能是一个常见的异常;path是一个文件名,PathTooLongSecurityException也是常见的异常。因此,EnumerateFiles有相当多的异常。

捕获异常

换句话说,您需要插入某种catch来处理这些情况。因此,在最后一个闭合大括号之后输入以下内容:

catch(Exception ex)

现在,在一对大括号之间,输入以下内容:

{
    sampLabel.Text += ex.Message;
}

在这里,ex.Message表示要在屏幕上显示的异常对象的消息。

运行程序

现在让我们确认这将起作用,所以在浏览器中启动它。单击“浏览”,并从C:\data目录中获取temp.txt文件。单击“上传”。正如您在图 23.2.10中所看到的,您的文件已保存,并且在同一目录中还有其他文件。完美!

图 23.2.10:运行我们的程序的结果

现在,想象一下您犯了以下错误(输入upload而不是“uploads”):

string sourceDirectory = @"C:\data\upload";

如果您再次运行它,通过单击“浏览”并选择samplefile.txt文件,您可以从图 23.2.11中显示的错误消息中看到,它找不到路径的一部分...:

图 23.2.11:当路径输入错误时显示的错误消息

所以这些是使它工作的基础知识。再次确保输入并运行此代码几次,然后您将完全了解发生了什么。请记住,我们可以这样做是因为网页只能在我们的本地计算机上访问。在更现实的情况下,您需要更加关注安全性,并防范恶意上传。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

using System;
using System.IO;
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        string savePath = @"c:\data\uploads\"; //make upload directory
        if (FileUpload1.HasFile)
        {
            string fileName = FileUpload1.FileName;//get file name
            savePath += fileName;//attach file name to save path
            FileUpload1.SaveAs(savePath);//save file
            sampLabel.Text = "<br>Your file was saved as " + fileName;
        }
        else
        {
            sampLabel.Text = "<br>You did not specify a file to upload.";
        }
        //could also use savePath here
        string sourceDirectory = @"C:\data\uploads";
        try
        {
            //list files using EnumerateFiles
            var txtFiles = Directory.EnumerateFiles(sourceDirectory,
            "*.txt");
            foreach (string currentFile in txtFiles) //display files
            sampLabel.Text += $"<br>{currentFile}";
        }
        //display any error messages
        catch (Exception ex)
        {
            sampLabel.Text += ex.Message;
        }
    }
} 

总结

在本章中,您学会了如何在 ASP.NET 中使用上传功能。您将文件保存到特定位置,向用户显示消息,确定存储在目录中的文件,探索EnumerateFiles的异常,并编写捕获异常的代码。

在下一章中,您将学习使用序列化将对象保存到硬盘的另一种方法。然后,您将了解从硬盘重建对象的过程,这被称为反序列化

第二十四章:序列化和反序列化对象

在本章中,您将学习另一种将对象保存到硬盘的方法—使用序列化。您还将学习从硬盘重建对象的过程,这称为反序列化。

将两个按钮添加到 HTML 中

启动一个项目,在这个项目中,您将在页面中插入两个按钮。您将在以<form id=...开头的行下方放置第一个按钮。要做到这一点,转到工具箱,获取一个Button控件,并将其拖放到那里。将第一个按钮上的文本更改为Save。现在获取另一个按钮,并将其拖放到该行下方。将第二个按钮上的文本更改为Open。因此,您在页面中放置了两个按钮,如下所示:

<asp:ButtonID="Button1" runat="server" Text="Save" /><br/>
<asp:ButtonID="Button2" runat="server" Text="Open" /><br/>

删除两个<div>行—您不需要它们。当然,在最后您还有一个标签:

<asp:LabelID="sampLabel" runat="server"></asp:Label>

在设计视图中,如图 24.3.1所示,您有两个按钮—Save 和 Open—然后是一个标签,打开的对象可以在其中显示:

图 24.3.1:我们在设计视图中的简单界面

开始编写项目

首先,我们将创建Save按钮,双击它,这将显示Button1_click的事件处理程序。删除Page_Load块。该项目的起始代码的相关部分应如图 24.3.2所示:

图 24.3.2:该项目的起始代码

添加命名空间

接下来,您需要添加新的命名空间,因此在文件顶部的using System下面,输入以下内容:

using System.IO;

显然,这一行用于输入和输出。接下来,输入以下内容:

using System.Runtime.Serialization.Formatters.Binary;

这一行允许您编写代码。当我们一起编写代码时,您将更好地理解这些命名空间的目的。接下来,让我们再做一个,如下所示:

using System.Diagnostics;

这一行只是为了让您能够打开记事本。保存为二进制格式后,您将使用记事本查看文件。现在您可以折叠这些命名空间,如果您愿意的话。

创建可序列化类

因此,首先您需要可以序列化的东西—一个可序列化的类。您将在前面的using语句下方放置它。输入以下内容:

[Serializable()]

您可以以这种方式装饰一个类。接下来,要序列化的内容如下输入:

public class Person

为可序列化类添加功能

这是你的可序列化类。接下来,你将为其添加功能。因此,在此行下面的一对大括号之间,输入以下内容:

public string Name { get; set; }
public decimal Salary { get; set; }

接下来,我们将重写一个方法,以便我们可以显示一个人并实际格式化它。因此,输入以下内容:

public override string ToString()

现在,如果您将鼠标悬停在ToString上,您将看到它是一个对象类。请记住,对象类是整个层次结构的父类。这是ToString被定义的地方。工具提示显示为 string object.ToString()。现在我们将覆盖它并编写我们自己的定义。

接下来,在override行下面的一对大括号中,输入以下内容:

return $"{Name} makes {Salary:C} per year.";

这将是我们对ToString的特定实现;也就是说,Name每年赚取一定金额的钱—无论每个Person类的实例的名称和薪水是什么。

定义保存文件的路径

接下来,在以protected void Button1_Click...开头的行下面的一对大括号中,输入以下内容:

string file = @"c:\data\person.bin"; 

在这里,您正在定义文件将被保存的路径。请注意,这次我们使用了不同的扩展名—.bin代表二进制,而不是.txt代表文本。

创建一个 Person 对象

接下来,输入以下内容以创建一个新的Person对象:

Person per = new Person() { Name = "John Smith", Salary = 78999 };

请记住,创建对象的另一种方法是您可以在大括号内设置属性的值。因此,我们有John Smith和他的Salary属性值。因此,我们创建了一个new Person对象。

处理非托管资源

现在,输入以下内容:

using (FileStream str = File.Create(file))

将鼠标悬停在前一行的FileStream上,以查看其位置;它在System.IO内。请注意,using System.IO;不再是灰色的,因为FileStream现在在那里。

接下来,右键单击FileStream,然后选择“转到定义”。您会看到它是从Stream派生的。现在,如果您向下滚动到底部,看到Dispose并展开它,您会看到它说释放 System.IO.FileStream 使用的未托管资源...,如图 24.3.3所示:

图 24.3.3:FileStream 的扩展定义

这就是为什么我们将其放在using语句中的原因,因为它涉及到未托管资源,比如低级磁盘访问。所以,我们将创建一个文件。

创建一个二进制格式化程序

接下来,您将创建一个二进制格式化程序,所以在以下行之间输入以下内容:

BinaryFormatter binFormatter = new BinaryFormatter();

同样,BinaryFormatter在这里是一个类,所以如果您将鼠标悬停在它上面,工具提示会说以二进制格式序列化和反序列化对象,或连接对象的整个图形。

序列化对象

接下来,要序列化我们的对象,您说binFormatter.Serialize,这是一个在那里定义的函数,然后您需要一个流和一个要通过流序列化的对象(per):

binFormatter.Serialize(str, per);

为了确认这个工作,输入以下内容在闭合大括号下面:

Process.Start("notepad.exe", file);

这将只是启动文件,以确认它已经被保存。

测试程序

在编写其余代码之前,我们可以进行一次测试。所以让我们在浏览器中启动它,然后点击保存:

图 24.3.4:程序的测试运行,以确保它正常工作

现在您可以看到,当您检查它时,保存的内容看起来与普通文本非常不同。请记住,当您学习属性时,我们谈到了后备字段。字段的实际值显示在图 24.3.5中。您可以看到工资、姓名值,然后是字段。这就是我们所说的二进制。它看起来与普通文本非常不同:

图 24.3.5:后备字段显示字段的实际值

从硬盘重建对象

在下一阶段,我们希望能够从硬盘重新构建这个对象。为此,在设计视图中双击“打开”按钮。这将带您回到Default.aspx.cs文件。

现在,在以protected void Button2_Click...开头的行下面的一组大括号内,创建一个新的Person对象,如下所示:

Person personRebuilt;

我们从硬盘构建这个。在这行下面输入以下内容:

string file = @"c:\data\person.bin";

通过这行,我们将从该文件中读取回来。

接下来,您必须确认文件实际存在,所以输入以下内容:

if(File.Exists(file))

如果文件存在,您将采取一些操作,这些操作将重建对象。

现在在以下行下面的一组大括号之间输入以下内容:

using (FileStream personStream = File.OpenRead(file))

在这里,我们打开文件进行读取。将鼠标悬停在OpenRead上。注意它返回一个FileStream类,因此表达式的右侧和左侧是一致的。

接下来,在这行下面的另一组大括号之间,输入以下内容:

BinaryFormatter binReader = new BinaryFormatter();

现在,我们将重建Person对象,所以接下来输入以下内容:

personRebuilt = (Person)binReader.Deserialize(personStream);

这将是对Person类型的转换。然后,将personStream传递到二进制读取器上定义的Deserialize函数中,然后将其转换回Person对象。

显示结果

现在,有了这个,我们可以显示东西。例如,接下来输入以下内容:

sampLabel.Text = personRebuilt.ToString();

请记住,此行中的ToString是在Person内部定义的。它是覆盖对象内部定义的基本ToString方法的方法。如果您将鼠标悬停在此处的ToString上,它会显示字符串Person.ToString()

运行程序

现在让我们在浏览器中打开这个新代码。单击“保存”按钮,它会打开记事本,如图 24.3.6所示:

图 24.3.6:单击“保存”按钮时运行程序的结果

现在单击“打开”按钮,它看起来像图 24.3.7中显示的屏幕:

图 24.3.7:单击“打开”按钮时运行程序的结果

因此,这证明了对象已被构建,并且还证实了在这个重建的对象personRebuilt上,您可以调用在类的定义中详细说明的通常函数、方法等,即return $"{Name} makes {Salary:C} per year.";行。

章节回顾

回顾一下,要记住的重点是,您可以从一个对象开始,并添加相当多的命名空间,特别是BinaryFormatterIO。接下来,您定义一个类,并在下面添加可序列化属性。然后,您编写代码以二进制格式保存,还编写了代码以从二进制格式重建为您的应用程序中可以使用的格式。

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics; //for notepad
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
[Serializable()]
public class Person //make class serializable
{
    public string Name { get; set; } //define name property
    public decimal Salary { get; set; } //define Salary property
    public override string ToString() 
    //override ToString() from object class
    {
        //return pretty string to describe each person
        return $"{Name} makes {Salary:C} per year.";
    }
}
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //define path where file will be saved
        string file = @"c:\data\person.bin";
        //build an object
        Person per = new Person() { Name = "John Smith", Salary = 78999 };
        //enclose FileStream in a using because of low level access
        using (FileStream str = File.Create(file))
        {
            //make a formatter
            BinaryFormatter binFormatter = new BinaryFormatter();
            //this is the step that saves the information
            binFormatter.Serialize(str, per);
        }
        //start notepad and display file
        Process.Start("notepad.exe", file);
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
        //person object to hold the rebuild person from disk
        Person personRebuilt;
        string file = @"c:\data\person.bin"; //path
        if(File.Exists(file)) //first confirm file exists
        {
            //enclose FileStream in a using
            using (FileStream personStream = File.OpenRead(file))
            {
                //make a formatter
                BinaryFormatter binReader = new BinaryFormatter();
                //reconstruct person using a cast
                personRebuilt = 
                (Person)binReader.Deserialize(personStream);
                //invoke to string on the person
                sampLabel.Text = personRebuilt.ToString();
            }
        }
    }
}

总结

在本章中,您学习了另一种将对象保存到硬盘的方法——使用序列化。然后,您学习了从硬盘重建对象的过程——反序列化。您创建了一个serializable类,为该类添加了特性,定义了保存文件的路径,创建了一个Person对象,编写了处理非托管资源的代码,创建了一个二进制格式化程序,对对象进行了序列化,并测试了您的程序。

在下一章中,您将学习如何在像素级别处理图像。我们将反转颜色并进行更改。

第二十五章:通过像素操作图像来玩一点

在本章中,你将学习如何在像素级别处理图像。你将反转颜色,改变它们。

操作图像

首先,在我的c:\data文件夹中,我有一个名为lessonimage的文件。正如你在图 25.4.1中所看到的,可乐罐上的文字是红色的,背景似乎是红棕色的:

图 25.4.1:本章中用于在像素级别反转颜色的图像

我们要做的是交换颜色,这样可乐罐上的文字,例如,将变成绿色,你将学习如何在单个像素级别操作图像。

向 HTML 添加一个按钮和一个图像控件

打开一个新项目。删除以<div...开头的两行;也删除这次的Label行。你不需要它们中的任何一个。

接下来,你需要在页面中插入一个Button控件。要做到这一点,去工具箱,拖动一个Button控件,并将其拖放到以<form id=...开头的行下面。将第一个按钮上的文本更改为Load

现在,你需要在页面中插入一个图像控件。所以,回到工具箱,拖动一个Image控件,并将其拖放到前一行下面,两者之间留一行空白。你的Default.aspx文件应该看起来像图 25.4.2中显示的那样:

图 25.4.2:本章的完整 HTML

所以,你为这个项目有一个非常简单的界面:一个按钮用于加载图像,另一个按钮是一个图像控件,用于显示图像:

图 25.4.3:我们项目的简单界面

现在,双击加载按钮。这会带你进入Default.aspx.cs。删除Page_Load事件;我们不关心那个。这个项目起始代码的相关部分应该看起来像图 25.4.4

图 25.4.4:这个项目的起始代码

添加一个命名空间

自然而然的,首先要做的是添加一个相关的新命名空间。为此,在文件顶部的using System下面输入以下行:

using System.Drawing;

为了使事情变得清晰干净,如果你愿意的话,你可以折叠文件顶部的所有代码组,这样基本上第一行清晰可见的是public partial class...

制作一个位图

当然,下一步是放入代码来实现你想要的功能。首先,你将制作一个位图。在以protected void Button1_Click...开头的行下面的大括号之间输入以下内容:

Bitmap image = new Bitmap(@"c:\data\lessonimage.bmp");

在这里,Bitmap是一个我们将称之为image的类。基本上,你有一个可以操作的位图。然后,为了初始化它,你传入一个路径。在这种情况下,它是(@"c:\data\lessonimage.bmp");

将图像保存为位图图片

接下来,打开画图并加载本章要操作的图像,如图 25.4.5所示:

图 25.4.5:在画图中要操作的图像

现在,要将其保存为位图,转到文件 | 另存为,然后选择 BMP 图片,如图 25.4.6所示:

图 25.4.6:画图中的另存为选项

BMP 图片的描述说保存任何类型的高质量图片并在计算机上使用。当你要保存文件时,另存为对话框中的文件类型字段显示为 24 位位图(.bmp;.dib)。你可以将任何图像保存为位图。

访问像素的位置

接下来,在Bitmap image = new Bitmap...行后输入以下内容:

int x, y;

你需要这行来获取图像中每个像素的位置。

现在,要访问每个像素的位置,你需要嵌套的for循环。首先输入下一行:

for(x = 0; x < image.Width; x++)

这里的外部for循环用于控制x坐标的水平移动。

现在,在一对花括号之间,输入以下内容:

for(y = 0; y < image.Height; y++)

这个内部的for循环是为了控制每个像素的y坐标,或者它的垂直位置。

操纵像素

一旦你完成了所有这些,下一阶段就是操纵像素。所以,从另一组花括号开始,并在它们之间输入以下内容,缩进:

Color pixelColor = image.GetPixel(x, y);

这一行首先读取每个像素的颜色。如果你将鼠标悬停在这一行的GetPixel上,你会看到它返回的是颜色,而不是位置。工具提示说它获取了位图中指定像素的颜色。

现在你将创建一个新的颜色。输入以下内容,也缩进,接下来:

Color newColor = Color.FromArgb(pixelColor.B, pixelColor.R, pixelColor.G);

在这里,=符号后面的Color是一个struct值类型。除了FromArgb,你还可以使用FromKnownColorFromName。这意味着字符串名称是已知的。在FromArgb后面,你说pixelColor.B来获取这个颜色结构的蓝色分量,pixelColor.R来获取红色分量,然后pixelColor.G来获取绿色分量。因此,你用这一行创建了一个新的Color对象。

接下来,输入以下内容:

image.SetPixel(x, y, newColor);

如果你将鼠标悬停在SetPixel上,工具提示说在这个位图中设置指定像素的颜色。然后,(x, y, newColor)表示要用来着色该像素的新颜色。

将图片转换为字节数组

现在你需要能够显示图片。你需要写一些代码来完成转换。所以,从外部for循环的闭合花括号下面输入以下内容:

byte[] picBytes = (byte[])new ImageConverter().ConvertTo(image, typeof(byte[]));

在这里,你创建了一个名为picBytes的字节数组,然后使用(byte[])进行转换。有一个图像转换器类,所以你创建了一个新的ImageConverter()类,然后你转换为目标类型,typeof,然后是byte。所以,在这里你将图片转换为字节数组。

现在,如果你去掉(byte[])的转换,工具提示会说你的字节数组不能隐式转换为'object'。这是因为ConvertTo返回一个对象。因此,你需要在它的前面有(byte[])转换。

现在你有了这个,接下来可以说以下内容:

string baseString = Convert.ToBase64String(picBytes);

Convert内部,你现在可以输入Convert.ToBase64String,并且picBytes可以转换为base64字符串。

发送图片 URL

现在你可以发送图片 URL,所以输入以下内容:

Image1.ImageUrl = "data:image/png;base64," + baseString;

在这一行的末尾的baseString变量是在一个图片字节数组上运行两个base64字符串的结果。

运行程序

有了这个,现在让我们来看一下结果;打开你的浏览器,点击加载按钮。修改后的图片显示在图 25.4.7中:

图 25.4.7:运行程序时产生的操纵后的图片

现在你会看到,承诺的图片已经被反转:颜色是绿色的。原来的背景有点红棕色,现在是绿色的。男人的头发原来有点棕色,现在有点深色,桌子也是一样。然而,有些东西似乎没有受到太大影响,比如钱的颜色,对吧?那仍然有点灰色。图片中的黑色物体也是一样。

正如你所看到的,你可以操纵图片,改变它们的位置,并使它们看起来非常不同,所以没有什么是真正固定的。这就是重点。此外,你可以单独访问每个像素,改变颜色,然后程序中最后三行代码负责让你写入Image1.ImageUrl。要设置这个图片的 URL,你需要通过这三行中的前两行。可能有一些更简单的方法,但这是一个可行的方法。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,显示在以下代码块中:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Drawing;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        Bitmap image = new Bitmap(@"c:\data\lessonimage.bmp");
        //to get each pixel's location
        int x, y;
        //controls moving horizontally
        for(x=0;x<image.Width;x++)
        {
            //controls moving vertically
            for(y=0;y<image.Height;y++)
            {
                //get each pixel's color
                Color pixelColor = image.GetPixel(x, y);
                //make a new color
                Color newColor = Color.FromArgb(pixelColor.B, pixelColor.R, 
                pixelColor.G);
                image.SetPixel(x, y, newColor);//set new color
            }
        }
        //converts picture to array of bytes
        byte[] picBytes =(byte[])new ImageConverter().ConvertTo(image, 
        typeof(byte[]));
        //converts array of bytes to a certain kind of string
        string baseString = Convert.ToBase64String(picBytes);
        //sets the image URL in a format that allows the image to be
        //displayed in a web page
        Image1.ImageUrl = "data:image/png;base64," + baseString;
    }
}

总结

在本章中,您学会了如何在像素级别处理图像。您反转了颜色,对其进行了更改。您将按钮和图像控件插入到 HTML 中,制作了位图,将图像保存为位图图片,编写了访问每个像素位置以操纵像素的代码,将图片转换为字节数组,并发送了图像 URL。

在下一章中,您将学习如何读取文件,然后将它们保存到 SQL Server 作为图像。

第二十六章:将图像保存到 SQL Server

在本章中,您将学习如何读取文件,然后将它们保存到 SQL Server 中作为图像。

在 HTML 中添加按钮和列表框

打开一个包含基本 HTML 的项目。这里需要做的第一件事是插入一个按钮。要做到这一点,转到工具箱,然后将Button控件拖放到以<form id=...开头的行下方。请记住,我们将在此项目中构建的简单界面将涉及单击按钮并从硬盘中读取文件到列表框中。更改Button控件上的文本为扫描文件夹。您将在此项目中扫描图像文件夹。

之后,您将插入一个ListBox控件。因此,再次转到工具箱,在搜索字段中键入list,然后将ListBox控件拖放到上一行下方。单击按钮后,您将填充ListBox控件。

在最后阶段,您将把所有文件保存到 SQL Server。这是我们的目标。为此,在上一行下方再拖入一个按钮。更改Button控件上的文本为保存到 SQL Server

删除以<div...开头的两行,还有Label行也要删除。这些都不需要。

你的Default.aspx文件应该看起来像图 26.5.1中显示的那样:

图 26.5.1:本章的完整 HTML

转到设计视图,如图 26.5.2所示,您将得到此项目的非常简单的界面:一个扫描文件夹按钮,用于获取文件名,然后一个保存文件到 SQL Server 的按钮:

图 26.5.2:我们项目的简单界面

创建用于存储文件的数据库表

您需要有一个数据库表,可以在其中保存文件。首先打开 SQL Server 对象资源管理器。您会记得您有一个名为People的数据库。转到表文件夹,在其上右键单击,并选择添加新表...,如图 26.5.3所示:

图 26.5.3:在 SQL Server 对象资源管理器中添加新表

您可以保留顶部的默认内容基本上不变,但要进行以下更改:

  1. 更改底部的 T-SQL 选项卡中的第一行,如下所示:
    [Id] INT NOT NULL PRIMARY KEY identity(1.1),
  1. 接下来添加这一行:
    IMAGE image not null
  1. 更改表的名称为Images,如下所示:
    CREATE TABLE[dbo].Images

这是我们的表,如图 26.5.4所示:

图 26.5.4:SQL Server 中的 dbo.Images 表

然后更新此内容,然后单击出现的对话框中的“更新数据库”按钮。等待更改生效。因此,如果展开表节点,您应该看到一个带有 IMAGE 列的 dbo.Images 表,如图 26.5.5所示:

图 26.5.5:表节点包含带有 IMAGE 列的 dbo.Images 表

将图像文件存储在硬盘上

在下一个阶段的过程中,您必须确保您有要读取的图像。因此,请在C:\驱动器的某个位置放置一些图像。例如,图 26.5.6显示了针对此特定计算机上的C:\data目录运行dir *.jpeg命令时获得的列表:

图 26.5.6:列出存储在 C:\data 目录中的三个图像文件

列表显示这些图像:face1.jpegface2.jpegface3.jpeg。因此,在这种特殊情况下,有三个文件需要从硬盘中读取。

现在在设计视图中双击扫描文件夹按钮。这将带您进入Default.aspx.cs。删除Page_Load存根。我们将处理与此相关的事件。这涉及到相当多的代码,因此这更像是一个项目。此项目的起始代码的相关部分应如图 26.5.7所示:

图 26.5.7:此项目的起始代码

添加命名空间

首先,您需要添加相关的命名空间。因此,在文件顶部附近的using System下面,输入以下内容:

using System.Data.SqlClient;

请记住,我们在连接和命令中使用这个。

在这一行下面输入以下内容:

using System.IO;

同样,这一行是为了能够读取硬盘。这是两个新的命名空间。您现在可以折叠掉以public partial class...开头的所有内容。

编写应用程序

现在让我们逐行通过代码的创建。因此,从以protected void Button1_Click...开头的行开始,在一组花括号中输入以下内容:

var imgFiles = Directory.GetFiles(@"c:\data\", "*.jpg");

在这里,您有一个Directory类和一个名为GetFiles的文件读取方法,它返回一个字符串数组,这些字符串是文件的路径。然后指定它们搜索的目录的路径,所以(@"c:\data\"...),然后您只想搜索图像文件,所以您可以指定一个过滤器,或者在这种情况下是*.jpg。如果您将鼠标悬停在var上,您会看到它是一个字符串数组。

现在您可以加载到ListBox控件中。接下来输入以下内容:

foreach(var imgFile in imgFiles)

接下来,对于文件数组中的每个文件,输入以下内容:

ListBox1.Items.Add(imgFile);

因此,您获取了所有文件路径,然后使用foreach循环将它们添加到ListBox控件中,以便它们可以在页面中显示。这是我们的目标。

测试扫描文件夹功能

进入设计视图,在这一点上,扫描文件夹应该可以工作。为此,点击“扫描文件夹”按钮。

正如您在图 26.5.8中所看到的,文件已加载:

图 26.5.8:文件已正确加载到 ListBox 中

现在这一步完成了,您可以再次使用foreach循环获取每个文件,并将其保存到 SQL Server。接下来我们来做这个。

构建连接字符串

现在在设计视图中双击“保存到 SQL Server”按钮。这将带您回到Default.aspx.cs。正如您可以想象的那样,下一个阶段将是获取连接字符串。您以前已经做过这个。因此,在以protected void Button2_Click...开头的行下面的一组花括号中,首先输入string connString =,然后输入@符号使其成为逐字字符串,然后放入""符号。现在要获取连接字符串,请执行以下操作:

  1. 单击菜单栏中的“查看”,然后选择“SQL Server 对象资源管理器”。

  2. 右键单击“People”数据库,然后选择“属性”。

  3. 在属性窗格中,双击“连接字符串”以选择它及其长描述。

  4. 然后,右键单击长描述并将其复制。

  5. 在“”符号的一组之间粘贴描述。

连接字符串行应该如下所示:

string connString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=People;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False";

您可以将此内容分成多行,这样会更整洁一些,如果您愿意的话。现在可以关闭 SQL Server 对象资源管理器和属性窗格。

使用连接字符串

现在我们将使用连接字符串,当然。因此,对于下一个阶段,在另一组花括号中输入以下内容:

using (SqlConnection conn = new SqlConnection(connString))

我们将调用连接字符串conn,并使用连接字符串初始化SqlConnection

接下来,我们需要打开一个连接。在上一行下面的一组花括号中输入以下内容:

conn.Open();

然后,输入以下foreach循环:

foreach(var item in ListBox1.Items)

这里,ItemsListBox控件的一个属性。这是它包含的项目的列表,您可以逐个检查它们,以便对它们采取离散的操作。在另一组花括号中输入以下内容:

using (SqlCommand cmd = new SqlCommand("insert into dbo.Images (image) values(@image)", conn))

请注意,我们将SqlCommand放在using语句中。如果您右键单击SqlCommand并选择“转到定义”,您会看到它说,DbCommand 继承自它,如果您向下滚动到底部,您会看到它有一个Dispose行。要完成这里的代码,您有(image)作为字段,其参数是@image

对于下一个阶段,在另一组花括号中输入以下内容:

byte[] picAsBytes = File.ReadAllBytes(item.ToString());

如果您将前一行仅留在(item),它会在红色下划线下出现错误。因此,我们将其转换为ToString。在这里,我们将每个项目作为一系列字节读取,并将其存储在数组中,因为然后,它可以在 SQL Server 中转换为图像。

输入以下内容:

cmd.Parameters.AddWithValue("@image", picAsBytes);

再次,@image在这里是参数。因此,我们将把图片保存到image参数作为一系列字节。现在输入以下内容:

cmd.ExecuteNonQuery();

这一行执行实际的保存。信不信由你,这就是整个应用程序。

运行程序

现在让我们在浏览器中查看结果。首先,点击“扫描文件夹”。您可以看到图像列表。然后,点击“保存到 SQL Server”按钮。页面上没有显示任何内容,因为我们还没有编写任何代码在保存后显示任何内容。所以现在我们必须检查 SQL Server。

让我们转到“查看”|“SQL Server 对象资源管理器”。右键单击 dbo.Images 表图标,然后选择“查看数据”。正如您在图 26.5.9中所看到的,这些是以低级形式存储的图像。这证实它们已经被保存了:

图 26.5.9:dbo.Images 表中以低级形式存储的图像

也许,作为自己的任务,您可以从 SQL Server 中提取文件并将它们显示为图像。这将是一个有趣的练习。

章节回顾

回顾一下,Default.aspx是扫描文件夹按钮、ListBox和保存到 SQL Server 按钮的源代码。Button1_Click...块内的代码实际上扫描文件夹,然后显示可用的图像文件;也就是说,至少以.jpg结尾的文件。然后,当您想要从ListBox控件保存文件到 SQL Server 时,以连接字符串开头的代码运行。

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Data.SqlClient;
using System.IO;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //scan folder for all files ending in jpg
        var imgFiles = Directory.GetFiles(@"c:\data\", "*.jpg");
        foreach(var imgFile in imgFiles)
        {
            //add files to list box in page
            ListBox1.Items.Add(imgFile);
        }
    }
    protected void Button2_Click(object sender, EventArgs e)
    {
        //make a connection string
        string connString = @"Data Source=DESKTOP-4L6NSGO\SQLEXPRESS; Initial Catalog=People;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False; ApplicationIntent=ReadWrite;MultiSubnetFailover=False";
        //make connection
        using (SqlConnection conn = new SqlConnection(connString))
        {
            //open connection
            conn.Open();
            foreach(var item in ListBox1.Items)
            {
                using (SqlCommand cmd = 
                new SqlCommand
                ("insert into dbo.Images (image) values (@image)", conn))
                {
                    //read picture as bytes
                    byte[] picAsBytes = File.ReadAllBytes(item.ToString());
                    //add pictures to SQL server as bytes
                    cmd.Parameters.AddWithValue("@image", picAsBytes);
                    //perform the actual saving
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

摘要

在本章中,您学会了如何读取文件,然后将它们保存在 SQL Server 中作为图像。您创建了一个数据库表来存储文件,在硬盘上存储图像文件,添加了命名空间,测试了扫描文件夹功能,并建立并利用了连接字符串。

在下一章中,我们将介绍 XML 的基础知识,XML 代表可扩展标记语言。

第二十七章:创建和使用 XML 文件

在本章中,我们将介绍 XML(可扩展标记语言)的基础知识。基本上,这是一种在互联网上结构化信息的方式。XML 的一个有用的方面是它是可扩展的,这意味着您可以创建自己的标签。

在 HTML 中添加按钮

启动一个项目。在<html>中唯一要放置的是一个Button控件。要做到这一点,转到工具箱,在搜索字段中输入but,然后将Button控件拖放到以<form id=...开头的行下面。将按钮上的文本更改为Read XML

编写 XML

现在您需要一个可以阅读的文件。为此,转到解决方案资源管理器,右键单击网站的名称。在下拉菜单中选择添加,然后选择添加新项...。在搜索字段中输入xml,并确保选择 Visual C#中标有 XML 文件的 XML 文件。您的XMLFile.xml的起始屏幕应该如图 27.1.1所示:

图 27.1.1:XMLFile.xml 的起始屏幕

现在让我们逐行创建代码,这样您就可以看到到底发生了什么。基本上,就像在 HTML 中一样,XML 中有元素、元素的嵌套和属性。

首先,想象一下您有一家书店。在 XML 中,您可以创建自己的标签。因此,接下来输入以下内容:

<bookstore>

注意它自动创建了开放和关闭标签:<bookstore> </bookstore>。在这些标签之间插入几行空白。

当然,您的书店里有书,所以在第一个<bookstore>标签下面输入以下内容:

<book type="eBook">

一本书可能是传统的教科书,也可能是电子书。因此,我们将指定一个类型属性,并将其设置为我们第一本书的eBook

现在让我们谈谈存储在<book type="eBook">下的一些元素。显然,一个基本的项目是书名,所以输入以下内容:

<booktitle>The Great Way</booktitle>

我们将这本书称为The Great Way

在下一个阶段,自然地,您要输入作者,所以输入以下内容:

<author>Bob Jones</author>

因此,我们的书是由Bob Jones写的。

最后一项当然是价格,我们将说这个案例中是$10.00,所以输入以下内容:

<price>10.00</price>

这些信息提供了第一个书籍元素,正如您所看到的,它由称为<booktitle><author><price>的子元素组成。

现在让我们再做一本书,只是为了多样性,如下所示:

<book type="traditional">
    <booktitle>Happy People</booktitle>
    <author>Mary Jenkins</author>
    <price>11.00</price>
</book>

我们的简单 XML 文件如下代码块所示:

<?xml version="1.0" encoding="utf-8" ?>
<bookstore>
    <book type="eBook">
        <booktitle>The Great Way</booktitle>
        <author>Bob Jones</author>
        <price>10.00</price>
    </book>
    <book type="traditional">
        <booktitle>Happy People</booktitle>
        <author>Mary Jenkins</author>
        <price>11.00</price>
    </book>
</bookstore>

再次记住,XML 是可扩展的,因为您可以创建自己的标签,标记因为它具有类似 HTML 的结构,当然,它是一种语言

现在,右键单击标有XMLFile.xml的选项卡,并从下拉菜单中选择复制完整路径。我们将很快使用这个路径。(如果您将鼠标悬停在XMLFile.xml选项卡上,可以看到完整路径,但它很长且难以记住,因此最好右键单击并选择复制完整路径。)

现在点击 HTML 中的Default.aspx选项卡,切换到设计视图,然后双击读取 XML 按钮。这会打开Default.aspx.cs中的事件处理代码。删除Page_Load存根。该项目的起始代码的相关部分应该如图 27.1.2所示:

图 27.1.2:该项目的起始代码

添加一个命名空间

让我们首先添加一个命名空间。您需要一个新的,所以在文件顶部附近的using System之后输入以下内容:

using System.Xml.Linq;

您将在编码中使用此命名空间。(您可以折叠public partial class...上面的所有代码。)

将 XML 文件加载到您的程序中

在下一个阶段,在以protected void Button1_Click...开头的行下面的一对大括号中输入以下内容:

XElement fromFile = XElement.Load(@"C:\Users\towsi\Documents\Visual Studio 2015\WebSites\CSharpTemplateUpdated76143\XMLFile.xml");

你想要加载XElement fromFile,所以你说XElement.Load()。然后,在括号内,你放置@符号使其成为原始字符串,然后是双引号。现在你需要利用从XMLFile.xml中复制的路径,这样你就可以从文件中加载 XML。所以,将路径粘贴在一对""符号之间。这将允许你加载可扩展标记文件。现在将鼠标悬停在XElement上。它说,类 System.Xml.Linq.XElement,表示 XML 元素。

遍历 XML 文件的内容

现在,输入以下内容:

foreach(XElement childElement in fromFile.Elements())

当你将鼠标悬停在这一行末尾的Elements上时,你会发现它是一个函数,返回的是 IEnumerable,所以你可以遍历它的内容,其中每个成员都是一个元素。

显示结果

现在你可以显示它们,所以在一对大括号之间输入以下内容:

首先,你需要书的类型。要获取它,在你输入sampLabel.Text += $"<br>Book Type:之后,你说{childElement.Attribute("type"),然后获取值,你输入.Value}";

sampLabel.Text += $"<br>Book Type:{childElement.Attribute("type").Value}";

现在,要获取作者,你使用{childElement.Element("author")}";,如下所示:

sampLabel.Text += $"<br>{childElement.Element("author")}";

这就是你可以将所有元素取出来的方法。在这个阶段,你可以直接复制并粘贴这行代码,因为对于书名和书价来说基本上是一样的。

对于书名,你可以这样说:{childElement.Element("booktitle")}";,如下所示:

sampLabel.Text += $"<br>{childElement.Element("booktitle")}";

对于价格,你可以这样说:{childElement.Element("price")}";,如下所示:

sampLabel.Text += $"<br>{childElement.Element("price")}";

最后,为了分隔开,你可以使用"<br><hr/>";,如下所示:

sampLabel.Text += $"<br><hr/>";

运行程序

现在让我们在这里试一下,在浏览器中打开它。记住,你实际上是在将 XML 读入网页。这是我们的目标。点击“读取 XML”按钮。结果显示在图 27.1.3中:

图 27.1.3:运行程序的结果

信息被报告的方式与你输入的方式完全一样,这是你所期望的。请记住,水平线存在是因为你在 HTML 页面中输入了"<br><hr/>",这添加了一个换行和一个水平规则或线。

这就是你可以将从 XML 文件中读取的内容与 C#结合起来,然后产生结果的方法。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Xml.Linq;//needed for XElement
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //load XML file into "fromFile" variable
        XElement fromFile = XElement.Load(@"C:\Users\towsi\Documents\Visual Studio 2015\WebSites\CSharpTemplateUpdated76143\XMLFile.xml" );

        foreach(XElement childElement in fromFile.Elements())
        {
            //display value
            sampLabel.Text += $"<br>Book Type:{childElement.Attribute("type").Value}";
            //display author
            sampLabel.Text += $"<br>{childElement.Element("author")}";
            //display book title
            sampLabel.Text += $"<br>{childElement.Element("booktitle")}";
            //display price
            sampLabel.Text += $"<br>{childElement.Element("price")}";
            //adds horizontal rule across the page
            sampLabel.Text += $"<br><hr/>";
        }
    }
}

总结

在本章中,你学习了 XML 的基础知识。你编写了 XML 代码,将生成的 XML 文件加载到程序中,遍历了 XML 文件的内容,并编写了显示结果的代码。

在下一章中,你将学习如何将 XML 写入文件,然后在记事本和 Internet Explorer 中查看结果。因此,你将遇到许多有用的小技巧。

第二十八章:使用 C#创建 XML 文件

在本章中,您将学习如何将 XML 写入文件,然后在记事本和 Internet Explorer 中查看结果。

向 HTML 添加按钮

启动一个项目,并在 HTML 页面中放置一个按钮。要做到这一点,转到视图|工具箱(Ctrl + Alt-X),在搜索字段中输入but,并将Button控件拖放到以<form id=...开头的行下面。更改按钮上的文本为保存文件

接下来,转到设计视图。双击保存文件按钮。这会打开Default.aspx.cs中的事件处理程序。删除Page_Load存根。折叠using System;上下的所有注释—你不需要它们。该项目起始代码的相关部分应该看起来像图 28.2.1中的那样:

图 28.2.1:该项目的起始代码

添加命名空间

首先,让我们添加一些命名空间。在文件顶部附近的using System后面输入以下内容:

using System.Xml;
using System.Diagnostics;

您需要using System.Diagnostics;,这样您就可以在创建文件后立即在 Internet Explorer 和记事本中查看文件。

编码XmlWriter设置

接下来,您将设置XmlWriter设置。因此,在以protected void Button1_Click...开头的行下面的大括号之间输入以下内容:

XmlWriterSettings settings = new XmlWriterSettings();

在这一行中,您创建了该类的设置对象,然后设置了功能。接下来输入以下内容:

settings.Indent = true;

在此行下面输入以下内容:

settings.IndentChars = "\t";

在这里,"\t"是一个制表符。

写入硬盘

现在,因为XmlWriter类使用硬盘等,您需要将其包含在using语句中。因此,接下来输入以下内容:

using (XmlWriter writer = XmlWriter.Create(@"c:\data\sampfile2.xml", settings))

您将在硬盘上创建一个文件,c:\data\sampfile2.xml,然后将设置传递给要使用的设置。设置对象作为参数传递给XmlWriter内定义的Create函数。

在下一阶段,我们将实际写入,因此在大括号之间输入以下内容:

writer.WriteStartElement("bookstore");
writer.WriteEndElement();

在第二行,您立即关闭WriteStartElement方法。我们在这里添加一个结构。

现在,您将在这两行之间添加几行代码。首先编写一个属性字符串,如下所示:

writer.WriteAttributeString("name", "Tom's Book Store");

接下来,您将创建另一个元素。在这里,如果您缩进代码,将有所帮助,这表明book元素位于bookstore元素下面。为此,输入以下内容:

writer.WriteStartElement("book");

要写入的元素是book。接下来输入以下内容:

writer.WriteStartElement("bookauthor");

现在让我们做以下操作来关闭这个:

writer.WriteEndElement();

您这样做是为了保持结束和开始成对。

现在,在此处(在WriteEndElement行上方),您可以写入另一个元素。在这一行中,您将包括特定的书籍作者。同样,您将写入一个字符串,作者的名字将是值。输入以下内容:

writer.WriteString("John Smith");

在这里,要注意WriteAttributeWriteString是不同的。WriteString在标签之间,而WriteAttribute给出属性,因此是不同的。这对我们的目的已经足够了。

格式化结果

现在,您希望确保结果看起来不错。因此,在最后一个WriteEndElement行下面的闭合大括号外面,输入以下内容:

Process.Start("notepad.exe", @"c:\data\sampfile2.xml");

您将在记事本中查看结果,然后需要文件的路径,因此从前面的using行中复制,c:\data\sampfile2.xml,并粘贴到此行中。

现在让我们再做一个。基本上,只需重复此行,并将其中的notepad.exe更改为iexplore.exe,如下所示,以指示接下来应该使用 Internet Explorer:

Process.Start("iexplore.exe", @"c:\data\sampfile2.xml");

运行程序

现在让我们在浏览器中打开并查看结果。单击保存文件按钮,您将看到在 Internet Explorer 中的样子:

图 28.2.2:在 Internet Explorer 中运行程序的结果

您可以看到它有结构,结果甚至是可折叠的,如 XML 标签之前的-符号所示,当然也是可展开的。书店的名称是汤姆的书店,这是属性,然后是约翰·史密斯,这是作为字符串写在书的作者标签或元素之间。

同样,在记事本中,它看起来像图 28.2.3中显示的屏幕,格式正确的 XML:

图 28.2.3:在记事本中运行程序的结果

所以,这些就是进行这些操作的基础知识。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Xml;
using System.Diagnostics;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //make a setting object
        XmlWriterSettings settings = new XmlWriterSettings();
        //set indent to true
        settings.Indent = true;
        //use tabs for indenting
        settings.IndentChars = "\t";
        //create file to write to
        using (XmlWriter writer = 
        XmlWriter.Create(@"c:\data\sampfile2.xml", settings))
        {
            //outermost element
            writer.WriteStartElement("bookstore");
            //attribute of book store
            writer.WriteAttributeString("name", "Tom's Book Store");
                //new element called book
                writer.WriteStartElement("book");
                    //new element called author
                    writer.WriteStartElement("bookauthor");
                    //this goes between the author tags
                    writer.WriteString("John Smith");
                writer.WriteEndElement();
            writer.WriteEndElement();
        }
        //priview the files in notepad and internet explorer
        Process.Start("notepad.exe", @"c:\data\sampfile2.xml");
        Process.Start("iexplore.exe", @"c:\data\sampfile2.xml");
    }
}

总结

在本章中,您学会了如何将 XML 写入文件,然后在记事本和 Internet Explorer 中查看结果。您编写了XmlWriter设置,并编写了将结果写入硬盘并格式化结果的代码。

在下一章中,您将学习如何将 LINQ 和 XML 结合起来,使其更加实用。

第二十九章:使用 LINQ 查询 XML 文档

在本章中,您将学习如何将 LINQ 和 XML 结合起来,使其更加实用。

向 HTML 添加文本框和按钮

启动项目,并在内部,您需要做的第一件事是添加一个TextBox控件。要执行此操作,请转到视图|工具箱,在搜索字段中键入tex,然后将TextBox拖放到以<form id=...开头的行下面。在该行开头输入输入值:,使其看起来如下:

Enter Value:<asp:TextBoxID="TextBox1" runat="server"></asp:TextBox>

因此,您将有一个框;在框中输入一个值,然后您将获得一个结果。您将扫描 XML 文档以选择高于某个值的项目,例如$50 或$60。这是我们的目标;换句话说,制作一个可搜索的页面。

接下来,您将在中插入一个按钮。因此,再次转到工具箱,在搜索字段中键入but,然后将Button控件拖放到前一行下面。更改Button控件上的文本,例如更改为搜索

<asp:ButtonID="Button1" runat="server" Text="Search" />

接下来,转到设计视图。它看起来像图 29.3.1中显示的屏幕截图:

图 29.3.1: 设计视图中该项目的界面

双击搜索按钮。这将打开Default.aspx.cs文件。删除Page_Load存根。折叠using System;上下的所有注释—您不需要它们。该项目起始代码的相关部分应如图 29.3.2所示:

图 29.3.2: 该项目的起始代码

这里有一些有趣的代码—非常实用。请记住,无论您学习编程语言,现实生活中的挑战远比您在这本书中看到的任何内容都要困难得多。

添加命名空间

现在让我们添加一些命名空间。在文件顶部附近的using System下面输入以下内容:

using System.Xml.Linq;
using System.Linq;

因此,我们在 XML 和 LINQ 之间建立了一个桥梁—这是我们的目标。

清除输出

首先,您需要每次清除标签,以便输出不会在标签上累积。因此,在以protected void Button1_Click...开头的行下面的大括号之间输入以下内容:

sampLabel.Text = "";

构建元素树

接下来,我们将使用以下语法创建一个元素树:

XElement store = new XElement("store",

在这一行中,store是树的名称。基本上,它保存有关产品的信息。请记住,如果您想知道某物来自何处,只需将鼠标悬停在其上。因此,如果您将鼠标悬停在此行开头的XElement上,工具提示将显示它不是来自 XML 命名空间。相反,它来自 Xml.Linq 命名空间。

接下来,您将在store内放入其他元素。因此,在带有分号的括号关闭之前插入几行空行,现在您将在其中堆叠东西。

确保在前一行的store后面加上逗号。在键入逗号时,查看工具提示。您看到它说 params object[] content 吗?这意味着您可以指定要构建树的可变数量的参数。请记住,params 表示您可以指定可变数量的参数。

首先,我们将在 store 内部添加一个名为shoes的新元素。因此,缩进以下行:

new XElement("shoes",

接下来,进一步缩进以下行:

new XElement("brand", "Nike", new XAttribute("price", "65")),

在这里,您说new XAttribute,只是为了向您表明这是可能的。属性将是price,值将是,例如,$65。您关闭该属性并使用逗号关闭元素。

现在,由于您将重复此操作,请复制此行,并在下面粘贴它,将品牌名称更改为Stacy Adams,价格更改为$120,如下所示:

new XElement("brand", "Stacy Adams", new XAttribute("price", "120")),

让我们再重复一次。因此,再次复制此行,并将其粘贴在下面,将品牌名称更改为Florsheim,价格更改为$90,如下所示:

new XElement("brand", "Florsheim", new XAttribute("price", "90"))));

注意,在这里的最后一行,你用四个括号和一个分号结束。你必须非常小心。你必须确保一切匹配。所以,你有一个商店,然后你有一个鞋部门,在鞋部门内部你有不同的品牌:Nike、Stacy Adams 和 Florsheim。

保存商店 XML 文件

现在,最好能将这些写入文件,以确认结构被解释为预期的样子。所以在前面的XElement store...行下面输入以下内容,对齐缩进:

store.Save(@"c:\data\storefile.xml");

在这里,store.Save()是一个很好的函数,你可以直接调用它。你可以将它保存到一个文件中,比如:(@"c:\data \storefile.xml");

测试程序

在做任何其他事情之前,让我们确认这将按预期工作,并且生成一个看起来不错的 XML 文件。所以,打开它在你的浏览器中,并点击搜索按钮,如图 29.3.3所示:

图 29.3.3:目前测试程序时显示的界面

当然,现在什么都没有显示,因为你还没有编写那部分代码。但是,如果你在c:\data目录下列出目录,就会看到保存的文件storefile.xml,如图 29.3.4所示:

图 29.3.4:文件 storefile.xml 保存在 c:\data 目录中

如果你在c:\data>提示符下键入notepad.exe storefile.xml,你将在记事本中看到图 29.3.5中显示的结果:

图 29.3.5:在记事本中打开的文件 storefile.xml

看起来很不错。你有一个store元素,然后在store元素内部有shoes,在shoes内部有品牌NikeStacy AdamsFlorsheim,每双鞋的价格分别是:$65、$120 和$90。所以,这看起来是一个很好的文件,对我们的目的来说已经足够了。(在现实生活中,相信我,这些事情要复杂得多。)

搜索符合特定条件的项目

接着,在以store.Save...开头的行下面输入以下内容,以搜索鞋子:

var shoeSearch = from shoes in store.Descendants("shoes").Descendants("brand")

在这里,var shoeSearch是 LINQ 和 XML 的组合。

接下来,输入where (decimal),用于转换为十进制值,并且价格大于用户输入的值:

where (decimal)shoes.Attribute("price") >decimal.Parse(TextBox1.Text)

从符合搜索条件的项目中进行选择

一旦找到这些鞋子,你可以从中选择:

select shoes;

如果你将鼠标悬停在前面使用Descendants的第一次上面,它会告诉你它返回 IEnumerable。工具提示说它返回此文档或元素的后代元素的过滤集合,按文档顺序排列。

另外,如果你将鼠标悬停在第二次使用Descendants上,你会看到它是按品牌进行的。一旦你到达那个级别,你可以,例如,将鼠标悬停在前面以where...开头的行中的price属性上,然后将这个属性与用户指定的值进行比较。所以,就好像你从外部到内部遍历,直到你到达价格属性,然后在那个阶段,你将该值与用户输入的值进行比较。

显示结果

接下来输入以下行,以显示搜索选择的所有鞋子品牌和价格:

foreach(XElement shoeBrand in shoeSearch)

最后,在前面的行下面的一对大括号之间输入以下内容:

sampLabel.Text += $"<br>Brand:{shoeBrand}<br>Price:{(decimal)shoeBrand.Attribute("price"):C}";

在这一行中,可能有多个值,所以你要追加。请注意,我们使用<br>标签将每个结果推到下一行。要显示价格,你要说(decimal)来转换为十进制值,然后在shoeBrand.Attribute("price")之后,你用:C将其转换为货币格式。这就是所有的代码。将所有这些都打出来非常重要。学习的最佳方式是通过实践,而不仅仅是打开一个事先准备好的文件并运行它。

运行程序

现在再次打开浏览器,输入一个值,比如45,然后点击搜索按钮。它应该返回所有的鞋子,因为价格都高于这个值,如图 29.3.6所示:

图 29.3.6:显示所有鞋子和价格,因为输入的值小于任何鞋子的价格

现在输入100作为值,然后再次点击搜索按钮。在这种情况下,它只返回价格为 120 美元的 Stacy Adams 鞋子,如图 29.3.7所示:

图 29.3.7:只返回 Stacy Adams 鞋子,因为它的价格超过 100 美元

让我们再做一个。再输入85,然后再次点击搜索按钮。如图 29.3.8所示,它返回 Stacy Adams 和 Florsheim 鞋子,因为这两者的价格都在 85 美元或以上:

图 29.3.8:返回 Stacy Adams 和 Florsheim 鞋子,因为两者的价格都在 85 美元或以上

就是这样。一切都按预期运行。我们还使用了你编写的整个XElement构造来生成了一个漂亮的 XML 文件,以便这个程序能够正确运行。

章节回顾

本章的Default.aspx.cs文件的完整版本,包括注释,如下所示:

//using is a directive
//System is a name space
//name space is a collection of features that our needs to run
using System;
using System.Xml.Linq;
using System.Linq;
//public means accessible anywhere
//partial means this class is split over multiple files
//class is a keyword and think of it as the outermost level of grouping
//:System.Web.UI.Page means our page inherits the features of a Page
public partial class _Default : System.Web.UI.Page
{
    protected void Button1_Click(object sender, EventArgs e)
    {
        //clear label on every button click so stuff does not accumulate
        sampLabel.Text = "";
        //create a nice XML tree structure for searching: store is the 
        //root, inside that is shoes,
        //and then under shoes are three different brands
        XElement store = new XElement("store",
                            new XElement("shoes",
                            new XElement("brand","Nike", 
                            new XAttribute("price","65")),
                            new XElement("brand", "Stacy Adams", 
                            new XAttribute("price","120")),
                            new XElement("brand", "Florsheim", 
                            new XAttribute("price","90"))));
        //save file to drive to confirm it looks like healthy XML
        store.Save(@"c:\data\storefile.xml");
        //search down to the level of the price attribute, and compare that
        //value against the value entered in the search box by the user
        var shoeSearch = from shoes in store.Descendants("shoes").Descendants("brand")
        where (decimal)shoes.Attribute("price") > decimal.Parse(TextBox1.Text)select shoes;
        //display all the shoe brands, and the prices
        foreach(XElement shoeBrand in shoeSearch)
        {
            sampLabel.Text += $"<br>Brand:{shoeBrand}<br>Price:{(decimal)shoeBrand.Attribute("price"):C}";
        }
    }
} 

总结

在本章中,你学会了如何结合 LINQ 和 XML 来做一些更实际的事情。你构建了一个元素树,并编写了代码来保存商店的 XML 文件,搜索符合特定条件的项目,并从找到的项目中选择符合搜索条件的项目。

posted @ 2024-05-17 17:50  绝不原创的飞龙  阅读(12)  评论(0)    收藏  举报