C# 3.0 版本
C# 3.0 新增的语法特性,如下:
- 自动实现的属性
- 匿名类型
- 查询表达式(LINQ)
- 表达式
- 表达式树
- 扩展方法
- 隐式类型本地变量
- 分部方法
- 对象和集合初始值设定项
自动实现属性
这个特性非常简单,代码如下:
public class PropertyExample
{
// C# 3.0之前
private string mNickName;
public string NickName
{
get { return mNickName; }
set { mNickName = value; }
}
// C# 3.0
public string Name { get; set; }
}
非常简单,看代码就懂了。
匿名类型
代码也很简单,如下:
var person = new {Title = "Name"};
Debug.Log(person.Title);
lambda 表达式
lambda 表达式非常简单,就是匿名方法的一种表现,如下:
using System;
public class LambdaExpressionExample
{
void Func()
{
}
void Main()
{
// C# 1.0
Action action1 = new Action(Func);
// C# 2.0
Action actionB = delegate { };
// C# 3.0
Action acionC = () => { };
}
}
表达式树
表达式树实际上是 Expression 这个 API 的使用,先看下代码,如下:
using System;
using System.Linq.Expressions;
using UnityEngine;
namespace Master
{
public class ExpressionTreeExample : MonoBehaviour
{
void Start()
{
// 创建表达式树
Expression<Func<int, bool>> expr = num => num < 5;
// 将表达式树编译为一个委托
Func<int, bool> result = expr.Compile();
// 执行表达式树的委托
Debug.Log(result(4));
}
}
}
// 输出结果
// True
通过表达式树可以构造一个委托出来,当执行委托的时候,就执行表达式树所表达的代码。
以上这段代码是通过 labmda 表达式来构建的表达式树,而最原始的表达式树的创建过程是通过 Expression 提供的 API,一个一个构建的,代码如下:
using System;
using System.Linq.Expressions;
using UnityEngine;
namespace Master
{
public class ExpressionTreeExample : MonoBehaviour
{
void Start()
{
// 创建一个 num 参数
var num = Expression.Parameter(typeof(int), "num");
// 创建一个 常数 5
var five = Expression.Constant(5, typeof(int));
// 创建一个表达式 num < 5
var numLessThan5 = Expression.LessThan(num, five);
// 创建一个 labmda 表达式 (num)=>num < 5
var labmda = Expression.Lambda<Func<int, bool>>(numLessThan5, new ParameterExpression[]
{
num
});
// 将表达式树编译为一个委托
Func<int, bool> result = labmda.Compile();
// 执行表达式树的委托
Debug.Log(result(4));
}
}
}
// 输出结果
// True
以上的构建过程同 lambda 形式的构建过程。
表达式树非常适合做方法参数的验证等工作。
扩展方法
扩展方法也非常简单,代码如下:
using UnityEngine;
namespace Master
{
public class StaticExtensionsExample : MonoBehaviour
{
void Start()
{
this.Show();
}
}
/// <summary>
/// 定义
/// 需要是静态类
/// </summary>
public static class MonoExtensions
{
/// <summary>
///
/// </summary>
/// <param name="self">需要有 this 关键字</param>
public static void Show(this MonoBehaviour self)
{
self.gameObject.SetActive(true);
}
}
}
隐式类型本地变量
using UnityEngine;
namespace Master
{
public class StaticExtensionsExample : MonoBehaviour
{
void Start()
{
// 隐式 (使用 var)
var age = 10;
// 显式
int age2 = 10;
}
}
}
分部方法
using UnityEngine;
namespace Master
{
public class StaticExtensionsExample : MonoBehaviour
{
void Start()
{
var obj = new TestPartialMethod();
obj.Click();
}
}
public partial class TestPartialMethod
{
public void Click(){ OnClick();}
/// <summary>
/// 不能有访问权限,实际上是 private 权限
/// </summary>
partial void OnClick();
}
public partial class TestPartialMethod
{
partial void OnClick()
{
Debug.Log("OnClick");
}
}
}
// 输出结果
// OnClick
对象和集合初始值设定项
using System.Collections.Generic;
using UnityEngine;
namespace Master
{
public class InitialExample : MonoBehaviour
{
void Start()
{
var obj = new SomeClass() {Age = 10};
var students = new List<string>() {"凉鞋", "匠人"};
var dictionary = new Dictionary<string, string>()
{
{"A", "Apple"},
{"B", "Banana"},
};
}
}
public class SomeClass
{
public int Age { get; set; }
}
}
扩展方法
因为扩展方法是 C# 的特色概念,也就是说 C# 有,但是别的语言没有(最近用于 Flutter 的 Dart 支持了扩展方法)。
这样就导致了很多在 C# 容易做的事情,在别的语言中就变得非常困难。
扩展方法可以让我们对现有的对象增加方法,而不用去修改对象类的代码、编译等。
扩展方法本质上是一个静态方法。
在 C# 中,最常见的扩展方法就是 LINQ 。
对于 Unity 开发者来说,第一次接触扩展方法,应该是在使用 DOTween 的时候。
DOTween 的示例代码如下:
transform.DOMove(new Vector3(2,2,2), 2)
.SetEase(Ease.OutQuint)
.SetLoops(4)
.OnComplete(myFunction);
当然不用扩展方法,也是可以做到以上这样的链式编程的,但是有了扩展方法,会更容易实现链式的 API 的,可以让每个类各司其职。
传统用法:扩展对象的方法
通过再封装的方式重命名 Unity 的 API,比如:
public static class BehaviourExtensions
{
public static GameObject Show(this GameObject selfObj)
{
selfObj.SetActive(true);
return selfObj;
}
public static T Show<T>(this T selfComponent) where T : Component
{
selfComponent.gameObject.Show();
return selfComponent;
}
}
// 使用方式:
// this.Show(); // this 是 MonoBehaivour
基础的链式 API
代码如下:
using UnityEngine;
namespace QFramework.Master
{
public class ExtensionsExample : MonoBehaviour
{
// 不用扩展方法的方式定义
public ExtensionsExample SayHello()
{
Debug.Log("Hello");
return this;
}
public ExtensionsExample SayGoodBye()
{
Debug.Log("Goodbye");
return this;
}
void Start()
{
this.SayHello()
.DoSomething()
.SayGoodBye();
}
}
/// <summary>
/// 扩展方法的方式定义
/// </summary>
public static class MonoExtensions
{
/// <summary>
/// 使用泛型约束
/// </summary>
/// <param name="self"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T DoSomething<T>(this T self) where T : MonoBehaviour
{
self.name = "DoSomething";
return self;
}
}
}
链式的 API 的核心就是返回自己(return this),可以把写死,也可以用泛型约束做成稍微通用一点的链式 API。
Fluent API
Fluent API,流式的 API,或者说有阶段的 API。
其实以上的两种扩展方法使用方式都属于 Fluent API 范畴,只不过这些 API 还不够 Fluent。
Fluent API 是两个老外提出的。
有阶段的链式 API,举个例子。代码如下:
using UnityEngine;
namespace QFramework.Master
{
public interface IStageStart : IStageA
{
}
public interface IStageA : IStageB
{
}
public interface IStageB : IStageBase
{
}
public interface IStageBase
{
void ExecuteStage();
}
public static class StageExtensions
{
public static IStageA EnterStageA<T>(this T self) where T : IStageStart
{
return self;
}
public static IStageB EnterStageB<T>(this T self) where T : IStageA
{
return self;
}
}
public class ExtensionsExample : MonoBehaviour, IStageStart
{
void Start()
{
this.EnterStageA()
.EnterStageB()
.ExecuteStage();
this.EnterStageB()
.ExecuteStage();
this.ExecuteStage();
// 这个会报编译错误 因为顺序不对
// this.ExecuteStage()
// .EnterStageB();
}
public void ExecuteStage()
{
}
}
}
核心就是通过泛型约束和接口的继承,来强制让 API 按照一定顺序的调用。
查询表达式(LINQ)
查询表达式是用上了之后会离不开的一个特性,它主要做的一个事情就是对数据集合的查询。
我们看下示例代码:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace QFramework.Master
{
public class LINQExample : MonoBehaviour
{
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
}
void Start()
{
var students = new List<Student>()
{
new Student() {Name = "凉鞋", Age = 18},
new Student() {Name = "hor", Age = 16},
new Student() {Name = "天赐", Age = 17},
new Student() {Name = "阿三", Age = 18}
};
// 1.基本的遍历
students.ForEach(s => Debug.Log(s.Name));
// 2.基本的条件过滤(Age > 5)
students.Where(s => s.Age > 5)
.ToList()
.ForEach(s => Debug.Log(s.Name));
// 与以上代码等价
(from s in students
where s.Age > 5
select s)
.ToList()
.ForEach(s => Debug.Log(s.Name));
// 3.基本的变换(student 转换成 name)
students.Select(s => s.Name)
.ToList()
.ForEach(name => Debug.Log(name));
// 与以上代码等价
(from s in students
select s.Name)
.ToList()
.ForEach(name => Debug.Log(name));
// 4.基本的分组(使用学生的名字分组)
students.GroupBy(s => s.Name)
.ToList()
.ForEach(group => Debug.Log(group.Count()));
// 与以上代码等价
(from s in students
group s by s.Name)
.ToList()
.ForEach(group => Debug.Log(group.Count()));
// 等等
}
}
}
LINQ 这个东西,很重要也很有用,它的理念是函数式编程,底层实现类似于上一篇文章的阶段性链式 API,不过比接口继承 + 泛型约束的方式更复杂一些。
理解 LINQ 是学习 UniRx 的基础,也是使用 .Net Core 的 Entity Framework Core 的基础。
LINQ 有各个语言的版本,如果做前端,那么 linq.js 是数据查询的必备的一个库。
LINQ 扩展
- LINQ = IEnumerable + Operator + foreach/ForEach/Single/First/ToList/ToDictionary/ToHashSet等等
IEnumerable 实际上就是集合,C# 中的 Array、Dictionary、List、Stack、Queue 都是集合,都是可以用 LINQ 的。
Operator 是操作符:比如 Where、Select、GroupBy、Distinct 都是操作符。
而以上公式的第三个部分就是具体的操作了,一般情况下,经过 LINQ 操作符操作后得到的是一个集合,这个集合可以直接用 foreach 进行遍历,也可以转换成 List 或则 Dictionary 等数据结构。
如果想要从头到尾只用一行代码,那么就可以直接 ToList ,然后调用 List 的 ForEach,笔者在上一小节的示例代码就是这么做的。
主要记住这个核心,那么剩下的就只需要去一个个掌握 操作符(Operator)就可以了。
具体的每个操作符是什么意思可以看下微软的官方文档,如下:
进入操作符大全之后具体看这里即可:
一个个点进去,就能看到每个 API 的简介和示例了。
底层实现
LINQ 的底层实现主要用了以下特性:
- 扩展方法
- 泛型约束
- 表达式树(源码中,没有看到表达式树相关的 API,但是微软的表达式树文档上说 表达式树是 LINQ 的基础)
而 LINQ 的两个核心接口是,IEnumerable 和 IEnumerator。
所有的集合都实现了 IEnumerable,比如 List、Dictinoary、Stack 等等。
所有的操作符都实现了 IEnumerator,比如 Where、Select、GroupBy 等等。
大部分操作符同时实现了 IEnumerable 和 IEnumurator,这样才可以对集合再进行一次操作。
总结
- 核心接口时 IEnumable 和 IEnumerator
- 使用的核心特性为 泛型约束 和 扩展方法

总结
C# 1.0 主要是让 C# 变成了一个合格的面向对象语言,在此之上还提供了委托 Attribute 特性,这两个特性一个是使 C# 朝着函数式编程的方向上发展,另一个则使 C# 可以对程序添加声明性信息,这时候已经有了 Attribute 和 反射 这一黄金搭档。
完全掌握 C# 1.0,算是对 C# 这门语言达到了入门程度了,这时候用 C# 写一些逻辑是没问题的,只不过实现业务会有点不方便,但是都可以写的,基本上 C# 1.0 所支持的特性是我们平时编程所使用到的大部分特性。
C# 2.0,主要提供了泛型和迭代器还有匿名方法,提供了泛型之后就需要支持协变和逆变,匿名方法的支持可以让 C# 的回调更加简洁(delegte {})。
完全掌握 C# 2.0 ,算是对 C# 这门语言达到了基础程度了,在写业务上不会遇到太大的语法特性问题。而 C# 2.0 中的的匿名方法和泛型的 API(比如 List
C# 3.0,主要提供了 LINQ 和 扩展方法,这两个算是革命性的特性,有了 LINQ 处理数据的代码就变得更加简洁精炼了,而有了扩展方法使得对系统的扩展能力大幅增强。
完全掌握 C# 3.0 ,已经算是进阶的使用者了,LINQ 虽然在 Unity 上使用会有点效率问题,但是整体上它的“函数式编程的思想“会根深蒂固地影响我们写的每一行代码,LINQ 大幅简化了我们对数据操作的代码,而扩展方法可以让我们可以写出更合理的代码。
大概就是这样的一个过程。
而之后 C# 4.0 到 C# 8.0(截止到 2020 年 1 月),只有一个比较重要的特性,就是 Task 以及 async 和 await 的关键字支持。
构建 C# 的知识体系,简单的版本如下:
C# 1.0:
- 类(class)
- 结构(struct)
- 接口(interface)
- 事件(event)
- 属性(property)
- 委托(delegates)
- 表达式
- 语句
- 特性(有时候也叫属性)(Attribute)
C# 2.0:
- 泛型
- 分部类型(partial)
- 匿名方法
- 可以为 null 的值类型
- 迭代器
- 协变和逆变
- getter/setter 单独可访问性
- 方法组转换(委托)
- 静态类
- 委托推断
C# 3.0 :
- 自动实现的属性
- 匿名类型
- 查询表达式(LINQ)
- Lambda 表达式
- 表达式树
- 扩展方法
- 隐式类型本地变量
- 分部方法
- 对象和集合初始值设定项
以上没有反射这个特性,因为反射不是特性而是 .Net 的 API,当然要支持反射,语言层面上也是要做一些支持的。
