性感的 C#






4.79/5 (154投票s)
在这篇文章中,我将通过代码示例逐一解释这些热门且性感的特性。在某些章节中,我使用问答(Q&A)模式,以便读者更好地理解。
目录
- 3.1. 扩展方法
- 3.2. 匿名类型
- 3.3. 委托
- 3.4. Lambda 表达式
- 3.5. Async-Await 对
- 3.6. 泛型
- 4. 结论
1. 简介
C# 是一种非常流行的编程语言。它在 .NET 领域尤其受欢迎。主要原因在于 C# 语言包含了许多有用的特性。它实际上是一种多范式编程语言。
问: 为什么我们称 C# 为多范式编程语言?
答: C# 具有以下特点:
- 强类型
- 面向对象
- 函数式
- 声明式编程
- 命令式编程
- 基于组件的编程
- 动态编程
因此,我们可以说它是一种多范式编程语言。C# 有许多有趣的特性,看起来非常热门和性感。正因为如此,我们也可以说它是一种性感的语言。在这篇文章中,我将通过代码示例逐一解释这些热门且性感的特性。在某些章节中,我使用问答(Q)和回答(A)模式,以便读者更好地理解。
2. 背景
2000 年 7 月,C# 诞生。Anders Hejlsberg 是这种现代语言之父。C# 诞生时就带有许多编程特性,并且每发布一个新版本,都会带来新的特性。在现实生活中,我看到许多 C# 开发者没有正确理解或使用某些性感的特性。如果他们能够正确理解这些特性,那么他们就可以创建更可重用、更强大、更性感的代码,这将为他们的软件产品增加更多价值。
3. 性感特性
我再次强调,C# 中有许多编程特性,所有这些都非常有用,可以帮助我们用简单的代码编写复杂的逻辑。每个开发者都应该了解所有这些特性。但并非所有特性都是性感的特性。问题可能来了,我根据什么标准说一个特性性感而另一个不性感。我没有一套严格固定的规则来评判性感的特性。在我的开发经验中,我感觉有些特性确实比其他特性更吸引人、更有趣、更具吸引力。我只把这些特性称为性感特性。我在这篇文章中重点关注所有这些特性。现在我将通过代码示例逐一解释它们。
3.1. 扩展方法
C# 从 3.0 版本开始引入了扩展方法。简单来说,你可以说它是一种特殊的静态方法,允许我们向现有类型添加方法。神奇之处在于它无需重新编译代码即可添加。这意味着你可以扩展任何类型,无论你是否拥有该类型的源代码。所以总结一下就是,你可以在不修改其源代码的情况下扩展内置的 .Net 类型或任何第三方类型。印象深刻吗?
你需要遵循以下规则来创建扩展方法:
- 方法应该是静态的。
- 方法访问修饰符应该是 public。
- 方法必须位于静态类中。
- 方法命名空间必须包含在使用块中,它将在那里使用。
问: 我想创建一个名为 "CountSpace" 的扩展方法,它将返回空格数,并且它应该适用于 .NET 字符串对象。我该如何编写?
答: 首先看代码块
public static class ExtensionMethods
{
public static int CountSpace(this string str)
{
if (string.IsNullOrEmpty(str)) return 0;
return str.Count(c => c == ' ');
}
public static string ToString(this string str)
{
return "codeproject.com";
}
}
[TestMethod]
public void Count_No_of_Space_In_A_Sentence()
{
string data = "this is a beautiful laptop.";
int totalSpace = data.CountSpace();
Assert.IsTrue(totalSpace == 4);
}
你只需遵循 3 个步骤:
- 创建一个名为 ExtensionMethods 的静态类
- 在这个类中,创建一个名为 CountSpace 的静态方法,带有一个 string 类型的参数,并使用 this 关键字。这将为你创造奇迹。 CountSpace 方法将从其参数中计算空格数并返回其值。
- 如果 ExtensionMethods 类的命名空间未包含在你的 using 方法中,则首先添加它,然后编写你的测试代码,例如我的 Count_No_of_Space_In_A_Sentense。
你需要记住一件事,你的 CountSpace 方法不仅可以与 Extestion Method 一起使用,还可以像静态方法一样使用。
[TestMethod]
public void Count_No_of_Space_In_A_Sentence_As_Static_Method()
{
string data = "this is a beautiful laptop.";
int totalSpace = ExtensionMethods.CountSpace(data);
Assert.IsTrue(totalSpace == 4);
}
但需要注意的是,这不是推荐的使用方法。
3.1.1. 使用前须知
- 扩展方法的作用域是命名空间级别。
- 不支持扩展方法重写。
- 扩展方法可以在接口级别使用。
- 如果对象实例方法和扩展方法签名相同,则实例方法优先。因此,需要谨慎使用扩展方法。
- 如果你为一个不维护源代码的类型编写扩展方法,则总是存在扩展方法被破坏的风险。
3.2. 匿名类型
C# 从 3.0 版本引入了匿名类型,它是一种特殊的类,编译器在运行时创建它。因此你也可以称它为编译器生成的类。匿名类型有助于封装公共只读属性,而无需显式定义类型。在许多情况下,额外的类创建可能会显得多余,特别是当你知道该类只使用一次,将来没有机会重用或没有可管理性好处时。在这些情况下,匿名类型将非常有用。
匿名类型的主要特点如下:
- 匿名类型不应是其他类型的子类型。它隐式派生自内置对象类型。
- 它不应是其他类型的父类型。这意味着它不应可扩展。
- 编译器提供匿名类型名称,开发者无法控制它。
- 只允许公共属性成为匿名类型的成员。
- 需要使用 var 或 dynamic 关键字来获取匿名类型对象的引用。
问. 我想创建匿名类型。我该如何创建?
A.
要创建匿名类型,你只需使用 "new" 构造函数方法,如下所示
var obj = new {Id=1, Name="Mr. Bill"};
你可以使用 dynamic 替换 var 关键字。
dynamic obj = new{Id=1, Name="Mr. Bill"};
[TestMethod]
public void Check_Anonymous_Type()
{
var obj = new { Id = 1, Name = "Mr. Bill" };
Assert.IsTrue(obj.Id == 1);
Assert.IsTrue(obj.Name.Equals("Mr. Bill"));
}
问: 是否可以将匿名类型作为方法的参数或返回类型?
答: 是的,可以将创建的匿名类型用作参数或返回类型。在方法参数或返回类型中,你需要定义的类型应该是
- object 或
- dynamic
如果使用 object 作为类型,你需要使用反射来检索属性值。因此,在这种情况下,使用 dynamic 是一种明智的方法。
以下代码示例将展示如何将动态类型与匿名类型一起用作方法参数和返回类型。
[TestMethod]
public void Check_Anonymous_Type_As_Argument_As_ReturnType()
{
var obj = new { Id = 1, Name = "Mr. Bill" };
var result = ProcessAnonymousType(obj);
Assert.IsTrue(result.Code == "101");
}
private dynamic ProcessAnonymousType(dynamic employee)
{
int id = employee.Id;
string name = employee.Name;
var newEmployee = new { Code = (id + 100).ToString(), Name = name };
return newEmployee;
}
问: 匿名类型可能在哪些场景下使用?
答: 如果你想从另一个类型创建一个子类型(实际上它也是一个新类型),那么无需创建显式类型,你可以创建匿名类型。特别是你会发现在许多 linq 表达式中,主对象包含许多属性,但你只需要其中的几个来工作。在这种情况下,你可以使用匿名类型。
[TestMethod]
public void Create_Anonymous_Type_Using_Linq()
{
var empList = new List<Employee>();
empList.Add(new Employee { Id = 1, Name = "Mr. A" });
empList.Add(new Employee { Id = 2, Name = "Mr. B" });
empList.Add(new Employee { Id = 3, Name = "Mr. C" });
var people = empList.Select(e => new { e.Id, e.Name });
foreach (var person in people)
{
int id = person.Id;
string name = person.Name;
}
Assert.IsTrue(people.Count() == 3 && people.First().Id == 1);
}
private class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
这里我创建了一个 people 列表,它是从现有的 empList(Employee 列表)创建的。实际上 people 是一个匿名类型对象的列表,它只有 Id 和 Name 两个属性。这些属性是从 Employee 对象的 Id 和 Name 属性创建的。也可以创建 Employee 类中不存在的额外属性。这就是匿名类型的强大之处。
3.3. 委托
委托特性首次在 C# 1.0 中引入。
问: 什么是委托?
答: 简短定义:“委托是一种特殊引用类型,它定义方法签名而不是对象”。虽然委托是一种类型,所以你可以从这种类型创建对象,并关联兼容的方法与此对象。委托实际上代表一个函数,因此人们通常称其为函数指针。
问: 你能用代码示例再解释一下吗?
答: 请看以下代码块:
public delegate string GetEmployeeNameDelegate(int employeeId);
[TestMethod]
public void Named_Delegate_Test()
{
var employee = new GetEmployeeNameDelegate(GetFullTimeEmployeeName);
/*
another way you can call as follows.
GetEmployeeNameDelegate employee = GetFullTimeEmployeeName;
framework will create employee delegate object and bind it
with GetFullTimeEmployeeName method
*/
string employeeName = employee(1);
Assert.IsTrue(employeeName.Equals("FullTimeEmployee-1"));
employee = new GetEmployeeNameDelegate(GetPartTimeEmployeeName);
employeeName = employee(1);
Assert.IsTrue(employeeName.Equals("PartTimeEmployee-1"));
}
public string GetFullTimeEmployeeName(int empId)
{
return string.Concat("FullTimeEmployee-", empId);
}
public string GetPartTimeEmployeeName(int empId)
{
return string.Concat("PartTimeEmployee-", empId);
}
GetEmployeeNameDelegate 是一种委托类型,它表示一个方法签名,该签名应该包含
- 1 个 int(整数)类型参数
- string 返回类型
GetFullTimeEmployeeName 和 GetPartTimeEmployeeName 是两个与此 GetEmployeeNameDelegate 委托匹配的方法。我们可以将这两个方法绑定到此委托,并通过委托执行方法。分析代码块后,你会明白委托是另一种支持流行的 OOP 特性——多态性(运行时)的特性。
使用委托的另一种方式
[TestMethod]
public void Anonymous_Delegate_Test()
{
GetEmployeeNameDelegate getEmployeeName = delegate(int x)
{
return string.Concat("Employee", x);
};
string employeeName = getEmployeeName(1);
Assert.IsTrue(employeeName.Equals("Employee1"));
}
您可以在上面的代码块中看到,内联代码块绑定到 GetEmployeeNameDelegate 类型,并通过委托执行该代码块。这是一种不同的使用委托的方式,与之前的方式不同。之前我们直接将方法绑定到委托,而上面的代码块我们没有绑定任何方法,而是绑定了内联代码块。这种内联方法绑定称为匿名方法。在这里我们可以说我们找到了两种类型的委托
- 命名委托,直接绑定到任何方法。
- 匿名方法/委托,绑定到内联代码块。
在现实生活中,我们可能会遇到许多场景,其中创建额外的方法似乎是多余的。特别是你会发现有些代码块只使用一次,并且你非常确定这些代码块永远不会被重用,或者不需要进行功能分解。在这些情况下,创建额外的方法似乎是多余的,我们可以在那里使用匿名方法。
问: 我想知道匿名方法何时转换为委托?
答: 好的,这对每个开发者来说都非常重要。实际上,在编译时,匿名方法会转换为委托类型。
3.3.1. 回调
根据定义,当代码块作为参数发送给另一个方法执行时,称为回调。回调的执行有两种方式
- 同步方式
- 异步方式
代码块可能是一个方法或内联代码块。我们不能直接将一个方法作为参数发送给另一个方法。我们需要借助委托来将代码块/方法发送给另一个方法。
[TestMethod]
public void Method_As_Method_Argument_Test()
{
GetEmployeeNameDelegate getEmployeeName = GetFullTimeEmployeeName;
string employeeName = ProcessEmployeeName(getEmployeeName);
Assert.IsTrue(employeeName.Equals("Full Name is: FullTimeEmployee-1"));
}
public string ProcessEmployeeName(GetEmployeeNameDelegate employeeNameDelegate)
{
string name = employeeNameDelegate(1);
return string.Concat("Full Name is: ", name);
}
这里我发送了一个 getEmployeeName 委托类型对象,它将 GetFullTimeEmployeeName 方法作为另一个 ProcessEmployeeName 方法的参数。然后,ProcessEmployeeName 方法通过委托执行 GetFullTimeEmployeeName,接收结果并处理它,然后将最终结果返回给其调用者。
3.3.2. 多播委托
我们已经看到一个方法绑定到一个委托。这意味着我们看到了委托和方法之间的一对一关系。当委托执行时,底层绑定的方法会执行。但是一个委托也可以绑定多个方法。这意味着
1 个委托 = n 个方法
如果出现这种情况,我们可以称之为多播委托。只需记住,每个方法都应该具有相同的签名。
问: 如何创建多播委托?
答: C# 有 2 个运算符,通过它们可以将方法绑定和解绑到多播委托。它们是
- +=
- -=
+= 运算符用于绑定方法,-= 用于解绑方法。
[TestMethod]
public void MultiCast_Delegate_Test()
{
GetEmployeeNameDelegate multiCastDelegateObject = GetFullTimeEmployeeName;
//bind another new method
multiCastDelegateObject += GetPartTimeEmployeeName;
//It will return 2nd method string but execute both methods
string employeeNames = multiCastDelegateObject(1);
employeeNames = string.Empty;
IEnumerable<Delegate> delegateList = multiCastDelegateObject.GetInvocationList();
foreach (Delegate delegateObject in delegateList)
{
var del = delegateObject as GetEmployeeNameDelegate;
employeeNames += del(1);
}
Assert.IsTrue("FullTimeEmployee-1PartTimeEmployee-1" == employeeNames);
//un-bind last method
multiCastDelegateObject -= GetPartTimeEmployeeName;
employeeNames = string.Empty;
foreach (Delegate delegateObject in multiCastDelegateObject.GetInvocationList())
{
var del = delegateObject as GetEmployeeNameDelegate;
employeeNames += del(1);
}
Assert.IsTrue("FullTimeEmployee-1" == employeeNames);
}
问: 如何调用多播委托?
答: 您可以像调用简单委托一样调用多播委托。
delegateObject(); Or delegateObject.Invoke(); //synchornous call
但当 delegateObject 返回值并且你需要捕获这些值时,问题就会出现。如果你只是简单地执行:
string result = delegateObject();
如果 delegateObject 绑定了 3 个不同的方法,并且每个方法都返回字符串数据,那么 result 变量将只存储最后一个方法的返回值。
问: 解决方案是什么?
答: 为了捕获所有返回值,你需要用不同的方式执行委托:
- 你需要从委托调用列表中检索所有委托。
- 将其转换为所需的类型
- 执行每个委托
- 单独存储每个返回值或将所有返回值连接在一起。
string employeeNames = string.Empty;
IEnumerable<Delegate> delegateList = multiCastDelegateObject.GetInvocationList();
foreach (Delegate delegateObject in delegateList)
{
var del = delegateObject as GetEmployeeNameDelegate;
employeeNames += del(1);
}
Assert.IsTrue("FullTimeEmployee-1PartTimeEmployee-1" == employeeNames);
3.3.3. 委托用于异步方法执行:
假设你有一个长时间运行的方法,你目前正在同步执行它。现在你决定为了更好的性能,你将异步执行此方法。你如何以非常简单的方式做到这一点?在这种情况下,委托是你的朋友。委托对象有一个名为 BeginInvoke 的方法。它将帮助异步执行任何同步方法。
有几种方法可以调用 BeginInvoke() 方法进行异步调用。我在这里展示一个非常简单的方法
[TestMethod] public void AsyncMethod_Using_Delegate() { Action ac = Synchronous_Method_Will_Call_Asynchronously; IAsyncResult asycnResult = ac.BeginInvoke(null, null); //your next code block run parally... ac.EndInvoke(asycnResult); } public void Synchronous_Method_Will_Call_Asynchronously() { Thread.Sleep(15000); }
3.3.4. 委托与接口
委托和接口是 C# 的两个不同概念,但它们都具有共同的特性
- 两者都只包含声明类型。
- 不同的方法/类可以实现它们。
- 两者都支持运行时多态性。
由于这些共同特性,在现实生活中,你可能会发现使用 Delegate 和 Interface 都可以解决相同或类似的问题。
问: 你能用代码示例解释一下吗?
答: 是的,当然。策略设计模式就是一个例子。策略模式解决的问题是函数输出相同,但完成函数的方式不同,并且运行时/动态方式可能会切换。策略模式通过接口实现。声明一个接口,并编写该接口的 2 或 3 个实现类。运行时你需要定义你将使用哪个实现来生成期望的输出。在这种实现中,你可以用委托替换接口并获得相同的输出。你可能会在现实生活中遇到类似的情况。
问: 何时使用接口,何时使用委托?
答: 对于选择接口或委托,没有明确的直接规则。以下是一些可以作为指导方针的要点。
委托 | 接口 |
封装静态方法。 | 对相关方法进行分组。 |
一个类可能需要一个方法的多个实现。 | 需要从一个类转换到另一个类。 |
调用者只对特定方法感兴趣。 | 类只需要方法的实现。 |
发布-订阅模式实现。 |
最后,如果我们总结委托的特性,我们可以指出以下几点:
- 委托是类型安全的方法指针。
- 委托允许我们从一个方法向另一个方法发送代码块。
- 委托支持运行时多态性。
- 委托可用作回调方法。
- 委托可用作链,即多播委托。
- 委托可以在另一个方法中创建方法(匿名方法)。
- 在某些情况下,委托可以替代接口。
3.4. Lambda 表达式
Lambda 表达式是匿名函数的一种特殊形式。使用 lambda 后,我们可以创建
- 委托 或
- 表达式树
创建委托后,我们可以直接执行代码。但是如果创建表达式树,我们需要多一个步骤。那就是首先编译表达式树,编译完成后它会创建委托,然后这个委托运行代码。
问: 为什么表达式树需要额外一步?
答: 表达式树不是代码块,而是数据块。既然是数据块,就无法直接执行。因此需要编译,编译完成后,使用 Compile() 方法创建可执行的委托。
问: 你能用代码示例详细解释一下吗?
答: 请看以下代码块
3.4.1. 委托创建
[TestMethod]
public void Use_Lambda_Create_Delegate()
{
CalculationDelegate addition = (x, y) => x + y;
int result = addition(2, 3);
Assert.IsTrue(result == 5);
}
3.4.2. 表达式树创建
[TestMethod]
public void Use_Lambda_Create_ExpressionTree()
{
Expression<CalculationDelegate> additionExpressionTree = (x, y) => x + y;
CalculationDelegate additionDelegate = additionExpressionTree.Compile();
int result = additionDelegate(3, 2);
Assert.IsTrue(result == 5);
}
3.4.3. Lambda 类型
Lambda 有 2 种类型:
- 表达式 Lambda
- 语句 Lambda
1. 表达式 Lambda:Lambda 使用一个称为 lambda 运算符的运算符。它看起来像 => 并读作“使得”
(input parameters) => expression;
- => 的左侧称为参数。
- => 的右侧称为表达式。
代码示例
具有 2 个输入参数的 Lambda 表达式
private delegate bool EqualityCheckDelegate(int x, int y);
[TestMethod]
public void Expression_Lamda()
{
EqualityCheckDelegate delegateObject = (x, y) => x == y;
bool matched = delegateObject.Invoke(2, 2);
Assert.IsTrue(matched);
}
如果未定义参数,也可以使用 () 空括号
private delegate bool EqualityCheckDelegateNoParam();
[TestMethod]
public void Expression_Lamda_No_param()
{
EqualityCheckDelegateNoParam delegateObject = () => 2 == 2;
bool matched = delegateObject.Invoke();
Assert.IsTrue(matched);
}
2. 语句 Lambda:语句 Lambda 类似于表达式 Lambda,但以大括号开头并定义其代码块。如果委托有返回类型,则需要像在方法中一样手动在 lambda 语句内部返回值。
(input parameters) => { statement(s); };
示例
[TestMethod]
public void Statement_Lamda()
{
EqualityCheckDelegate delegateObject = (x, y) =>
{
if (x == y)
return true;
else
return false;
};
bool matched = delegateObject.Invoke(5, 5);
Assert.IsTrue(matched);
}
3.4.4. 异步 Lambda
如果我们创建带有异步处理的 lambda 表达式或语句,我们可以称之为 async lambda。它实际上是一种非常简单的创建异步委托的方法。
对于异步处理,我们需要两个工具:
- Async 修饰符
- Await 语句
示例
[TestMethod] public void Async_Lambda() { Func<Task> task = async () => { await Task.Delay(5000); }; //Your another logic code... task().Wait(); }
我将在本文的另一部分详细解释 async 和 await。
3.4.5. 类型推断
如果你分析代码,你会发现我们没有在 lambda 参数部分定义参数类型。
问: 编译器如何知道参数类型?
答: 编译器根据 lambda 主体推断参数类型。如果你查看 linq api
IEnumerable<Employee> employeeList = GetEmployees();
IEnumerable<Employee> newEmployeeList = employeeList.Where(e => e.Salary > 10000);
这里的输入参数 e 被编译器推断为 Employee 类型的对象,在 lambda 主体中你可以使用 Employee 对象的任何公共成员。需要记住一个重要点是,lambda 表达式本身没有类型。
3.4.6. Lambda 表达式规则
lambda 表达式遵循一些我们应该知道的通用规则
- Lambda 参数的数量应与其委托参数的数量相等。 n 个 lambda 参数 = n 个委托参数;
- 每个 Lambda 参数必须与其对应的委托参数隐式可转换。
- 如果 lambda 返回任何值,则值类型必须与其委托返回类型隐式可转换。
3.4.7. 变量作用域
[TestMethod]
public void Lambda_Variable_Scope()
{
var salary = new Salary();
int newSalary = salary.AddAndGetSalary(100);
Assert.IsTrue(newSalary == 100);
int newSalary2 = salary.IncreaseSalary(100);
Assert.IsTrue(newSalary2 == 200);
}
private class Salary
{
public Func<int,int> IncreaseSalary = null;
public int AddAndGetSalary(int addedAmount)
{
int salaryAmount = 0;
salaryAmount += addedAmount;
IncreaseSalary = (int addedNewAmount) =>
{
salaryAmount += addedNewAmount;
return salaryAmount;
};
return salaryAmount;
}
}
如果你分析上面的代码,你会发现局部变量 salaryAmount 的作用域定义在方法 AddAndGetSalary 中。这意味着如果 AddAndGetSalary 方法执行完成,局部变量 salaryAmount 将在垃圾回收器的帮助下自动从内存中移除。但在这里,在 lambda 语句内部,这个局部变量被用作其外部变量的引用,并增加了它的值。
当我直接调用时
salary.AddAndGetSalary(100);
它返回 100。
但在此之后当我执行
salary.IncreaseSalary(100);
它返回 200。这意味着它存储了之前的 100。这是有趣的部分。尽管 salaryAmount 变量的作用域被销毁了,但该变量仍然在内存中。
问: 这怎么可能?
答: 原因是委托仍然引用这个变量。
问: 我以前在 JavaScript 中看到过。这是闭包吗?
答: 是的。这个概念称为闭包。现在我谈论的是 C# 闭包。实际上,闭包是一个独立于语言的概念。简而言之,我们可以定义闭包:内部作用域/方法引用其外部作用域/方法的局部变量。Lambda 使用闭包来管理这一点。闭包概念对于所有开发者来说都非常重要,特别是那些使用 lambda 表达式的开发者,应该清楚这个概念。
3.5. Async-Await 对
Async 和 await 实际上是 2 个不同的代码标记。它们标记什么?它们标记任务完成后代码需要重新启动的位置。Async 与方法一起作为修饰符使用,表示方法体内部存在 await 语句,代码语句可能需要在任务完成后重新启动。
Microsoft 从 .NET 4 版本开始引入了基于任务的异步模式(TAP)。此 TAP 基于 2 种类型使用:
- 任务
- Task<TResult>
Task 对象代表正在执行的代码,它将在未来提供结果。Task 对象具有一些特性
- 任务调度
- 建立父任务和子任务之间的关系。
- 支持协作取消
- 无需外部等待句柄,即可发出等待信号。
- 持续附加任务。
Microsoft 建议对于当前的开发,TAP 是异步编程的最佳方法。
C# 5 版本引入了 async 和 await 对,并与 TAP 一起使用。
[TestMethod]
public void Async_Await()
{
var list = new List<Task>();
for (int i = 0; i < 3; i++)
{
Task t = WriteFileAsync(i);
list.Add(t);
}
Task.WaitAll(list.ToArray());
}
private async Task WriteFileAsync(int x)
{
await Task.Run(new Action(WriteFile));
}
private readonly object _locker = new object();
private void WriteFile()
{
Thread.Sleep(4000);
lock (_locker)
{
File.AppendAllLines("D:\\ABC.log", new[] { DateTime.Now.ToString("hh:mm:ss:t") });
}
}
为了开发响应更快的应用程序,基于 async 的异步处理非常有用。一些有用的场景:
- 文件输入/输出
- 从 Web 下载数据
- 网络流
- 响应式 UI(不阻塞主 UI 线程)
在使用 async-await 对进行异步编程之前,您应该了解以下内容
- 根据强约定,您将 async 作为方法前缀,在该方法中使用 async 修饰符。
- 您应该知道编译器通过状态机工作流处理和管理与异步处理相关的所有事情。
- 在异步方法中,至少应该存在一个 await,否则方法将同步工作。
3.5.1. 异常处理
[TestMethod]
public void ExceptionHandling_With_Async_Wrong_Way()
{
var tId = Thread.CurrentThread.ManagedThreadId;
bool exceptionCatched = false;
Task t = null;
try
{
t = LongProcessAsync();
}
catch(Exception ex)
{
exceptionCatched = true;
}
t.Wait();
Assert.IsTrue(exceptionCatched);
}
private async Task LongProcessAsync()
{
await Task.Run(new Action(LongProcess));
}
private void LongProcess()
{
var tId = Thread.CurrentThread.ManagedThreadId;
throw new InvalidOperationException("operaton is invalid. Please verify your code.");
Thread.Sleep(5000);
}
如果你查看并运行上述代码块,结果将是测试用例失败。为什么会失败?因为我们没有以正确的方式设置 try 块。我故意从 LongProcess() 方法中抛出 InvalidOperationException,但 try 块没有捕获这个异常。原因在于代码在 2 个不同的线程中运行,并且在不连接线程的情况下无法捕获从另一个线程抛出的异常。(我在这里使用了一个变量 tId,通过它我们将测试 2 个线程是否不同。)Task.Wait() 方法将连接线程,所以如果我们将 try 块设置为 Wait() 方法,那么就可以捕获异常。
[TestMethod]
public void ExceptionHandling_With_Async_Right_Way()
{
var tId = Thread.CurrentThread.ManagedThreadId;
bool exceptionCatched = false;
Task t = LongProcessAsync();
try
{
t.Wait();
}
catch (AggregateException ex)
{
exceptionCatched = true;
}
Assert.IsTrue(exceptionCatched);
}
另一个观察!如果您分析代码,会发现 LongProcess() 方法抛出了 InvalidOperationException,但在 catch 块中使用了 AggregateException。原因在于,当异步方法抛出任何异常时,它首先被封装在 AggregateException 中,然后才抛出。因此,如果您需要实际异常,则需要探索 AggregateException.InnerException 属性。
3.5.2. 取消异步进行中的任务
问题是如何取消正在进行/运行的任务? CancellationTokenSource 是一个对象,它帮助我们取消正在进行中的任务。
问: 你能给我看一些代码示例吗?
答: 以下是代码:
private CancellationTokenSource _cancelTokenSource;
[TestMethod]
public void Cancel_OnGoing_Async_Task()
{
Task t = LongProcessAsync2();
CancelTask();
if (!_cancelTokenSource.IsCancellationRequested)
{
t.Wait();
}
}
private void CancelTask()
{
_cancelTokenSource.Cancel();
}
private async Task LongProcessAsync2()
{
_cancelTokenSource = new CancellationTokenSource();
await Task.Run(new Action(LongProcess2));
}
private void LongProcess2()
{
Thread.Sleep(15000);
}
以前,取消正在运行的代码一直是一个挑战。使用 CancellationTokentSource 对象,我们可以随时取消正在进行的任务,而无需面临太多挑战。感谢 CancellationTokentSource 的出色支持!
3.6. 泛型
public class MyFirstGenericClass<T> where T : struct
{
public T GetDouble(T value)
{
var input = (Convert.ChangeType(value, typeof(int)));
int newA = int.Parse(input.ToString());
newA *= 2;
return (T)(Convert.ChangeType(newA, typeof(T)));
}
}
C# 从版本 2 开始引入了泛型,后来其功能得到了增强。泛型引入了类型参数的概念。基于此类型参数,可以创建新的类或方法,它们可以与各种类型一起工作,而无需显式声明这些类型。
您可以将泛型应用于以下项目:
- 类
- 方法
- 接口
- 委托
- 事件
问: 使用泛型我们可以获得哪些好处?
答: 使用泛型我们可以获得以下好处
- 简洁的代码。
- 最大限度地重用代码。
- 类型安全。
- 在运行时使用反射提取泛型类型信息。
- 生成高质量代码。
问: 需要知道泛型类型是如何构建的吗?
答: 在编译时,当泛型类型或泛型方法转换为 MSIL(Microsoft Intermediate Language)时,它会包含元数据,其中定义了泛型类型参数。泛型类型的实际构建过程基于参数约束,可以是
- 值类型 或
- 引用类型。
值类型和引用类型的泛型类型构建方式不同。
3.6.1. 泛型类型参数约束
我们可以在创建泛型类型时应用约束。创建此泛型类型的对象时,客户端代码必须遵守这些约束,否则会产生编译时错误。下表将展示一些约束:
约束 | 描述 |
---|---|
T: class | 所有引用类型,即类、接口、委托 |
T: new() | 类型参数必须具有无参数构造函数。 |
T: struct | 任何类型的值类型,即 int、long 等 |
3.6.2. 多个泛型类型参数
为泛型类型定义多个类型参数没有问题。
public class MultiParametersGenericClass<T1, T2>
{
public void Process(T1 value1, T2 value2)
{
//code block...
}
}
3.6.3. 泛型方法
[TestMethod]
public void Generic_Method_Test()
{
int returnValue = GenericMethod<int>(5);
Assert.IsTrue(returnValue == 10);
}
public T GenericMethod<T>(T inputValue) where T:struct
{
var r =Convert.ToInt32(inputValue) * 2;
return (T)Convert.ChangeType(r, typeof(T));
}
您可以像声明和使用泛型类一样声明和使用泛型方法。当您调用泛型方法时,您需要传递您的类型参数。
3.6.4. 泛型集合
.NET 中为您定义了许多泛型集合。如果您愿意,也可以编写自己的泛型集合。以下将列出 .NET 框架中的一些泛型集合。
- IList<T>
- List<T>
- ICollection<T>
- Dictionary<K, T>
- IEnumerable<T>
- Stack<T>
- Queue<T>
3.6.5. 泛型委托
我们可以将泛型类型参数用于委托。在这种情况下,将与此委托绑定的方法必须匹配。
private delegate T MyFirstGenericDelegagte<T>(T item) where T : struct;
[TestMethod]
public void Generic_Delegate_Test()
{
MyFirstGenericDelegagte<int> delObject = GetDouble;
int returnValue = delObject(5);
Assert.IsTrue(returnValue == 10);
}
private int GetDouble(int x)
{
return x*2;
}
3.6.6. 泛型仓储代码示例
以下我展示了使用泛型实现的泛型仓储模式:
public abstract class EntityBase { public long Id { get; set; } protected EntityBase() { } public abstract void Validate(); } public class Customer : EntityBase { public string CustomerName { get; set; } public override void Validate() { } } public interface IRepository<T> where T : EntityBase { T GetById(long id); void SaveOrUpdate(T entity); } public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase { protected readonly string _conntectionString; protected RepositoryBase() { _conntectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString; } protected RepositoryBase(string connectionString) { _conntectionString = connectionString; } public abstract T GetById(long id); public abstract void SaveOrUpdate(T entity); } internal class CustomerRepository : RepositoryBase<Customer> { public override Customer GetById(long id) { var prmId = new SqlParameter("@Id", id); //return base.GetByIdInternal(SP_CUSTOMER_GET_BY_ID, prmId); return new Customer(); } public override void SaveOrUpdate(Customer entity) { var prmId = new SqlParameter("@Id", entity.Id); var prmCustomerName = new SqlParameter("@CustomerName", entity.CustomerName); var paramList = new List<SqlParameter> {prmId, prmCustomerName}; //base.SaveOrUpdateInternal(SP_CUSTOMER_SAVE_UPDATE, paramList); } }
4. 结论
从 C# 众多精心定义的特性中选择一组特性,并声称这些特性说服了我,这非常困难。很明显,并非所有人都同意我仅仅因为这些少数特性就声称 C# 是一种性感的语言。但我再次重申,这是我个人的偏好,有些人可能会同意,有些人可能会不同意。每个人都应该享有平等的权利。
很多人可能会觉得“性感”这个词可能不适合编程语言。但我认为“性感”这个词可以用在任何更吸引人、更具吸引力、更有趣的事物上。所以我用了这个词。每个人都有权同意或不同意我的观点。
源代码
我附上了一个使用 Visual Studio 2012 和 .NET framework 4.5 版本的单元测试项目。参考文献
- http://stackoverflow.com/questions/8694921/delegates-vs-interfaces-in-c-sharp
- http://msdn.microsoft.com/en-us/library/0yw3tz5k.aspx
- http://msdn.microsoft.com/en-us/library/bb397687.aspx
- http://dailydotnettips.com/2011/08/26/async-lambda-expression/
- http://visualstudiomagazine.com/articles/2011/08/01/pfcov_task-based-async.aspx
- https://codeproject.org.cn/Articles/127291/C-5-0-vNext-New-Asynchronous-Pattern
- http://msdn.microsoft.com/en-us/library/hh873173(v=vs.110).aspx
- http://msdn.microsoft.com/en-us/library/512aeb7t.aspx
- http://msdn.microsoft.com/en-us/library/f4a6ta2h.aspx
- http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx