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

139 阅读42分钟

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 文件和 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。