C# 3.0 版本 C# 3.0 新增的语法特性,如下:
自动实现的属性 匿名类型 查询表达式(LINQ) 表达式 表达式树 扩展方法 隐式类型本地变量 分部方法 对象和集合初始值设定项 自动实现属性 这个特性非常简单,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class PropertyExample { private string mNickName; public string NickName { get { return mNickName; } set { mNickName = value ; } } public string Name { get ; set ; } }
非常简单,看代码就懂了。
匿名类型 代码也很简单,如下:
1 2 var person = new {Title = "Name" };Debug.Log(person.Title);
lambda 表达式 lambda 表达式非常简单,就是匿名方法的一种表现,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System;public class LambdaExpressionExample { void Func () { } void Main () { Action action1 = new Action(Func); Action actionB = delegate { }; Action acionC = () => { }; } }
表达式树 表达式树实际上是 Expression 这个 API 的使用,先看下代码,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 )); } } }
通过表达式树可以构造一个委托出来,当执行委托的时候,就执行表达式树所表达的代码。
以上这段代码是通过 labmda 表达式来构建的表达式树,而最原始的表达式树的创建过程是通过 Expression 提供的 API,一个一个构建的,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System;using System.Linq.Expressions;using UnityEngine;namespace Master { public class ExpressionTreeExample : MonoBehaviour { void Start () { var num = Expression.Parameter(typeof (int ), "num" ); var five = Expression.Constant(5 , typeof (int )); var numLessThan5 = Expression.LessThan(num, five); var labmda = Expression.Lambda<Func<int , bool >>(numLessThan5, new ParameterExpression[] { num }); Func<int , bool > result = labmda.Compile(); Debug.Log(result(4 )); } } }
以上的构建过程同 lambda 形式的构建过程。
表达式树非常适合做方法参数的验证等工作。
扩展方法 扩展方法也非常简单,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 using UnityEngine;namespace Master { public class StaticExtensionsExample : MonoBehaviour { void Start () { this .Show(); } } public static class MonoExtensions { public static void Show (this MonoBehaviour self ) { self.gameObject.SetActive(true ); } } }
隐式类型本地变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using UnityEngine;namespace Master { public class StaticExtensionsExample : MonoBehaviour { void Start () { var age = 10 ; int age2 = 10 ; } } }
分部方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 using UnityEngine;namespace Master { public class StaticExtensionsExample : MonoBehaviour { void Start () { var obj = new TestPartialMethod(); obj.Click(); } } public partial class TestPartialMethod { public void Click () { OnClick();} partial void OnClick () ; } public partial class TestPartialMethod { partial void OnClick () { Debug.Log("OnClick" ); } } }
对象和集合初始值设定项 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 的示例代码如下:
1 2 3 4 transform.DOMove(new Vector3(2 ,2 ,2 ), 2 ) .SetEase(Ease.OutQuint) .SetLoops(4 ) .OnComplete(myFunction);
当然不用扩展方法,也是可以做到以上这样的链式编程的,但是有了扩展方法,会更容易实现链式的 API 的,可以让每个类各司其职。
传统用法:扩展对象的方法 通过再封装的方式重命名 Unity 的 API,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; } }
基础的链式 API 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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(); } } public static class MonoExtensions { 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,举个例子。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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(); } public void ExecuteStage () { } } }
核心就是通过泛型约束和接口的继承,来强制让 API 按照一定顺序的调用。
查询表达式(LINQ) 查询表达式是用上了之后会离不开的一个特性,它主要做的一个事情就是对数据集合的查询。
我们看下示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 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 } }; students.ForEach(s => Debug.Log(s.Name)); 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)); students.Select(s => s.Name) .ToList() .ForEach(name => Debug.Log(name)); (from s in students select s.Name) .ToList() .ForEach(name => Debug.Log(name)); 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)在业务的开发上会用的比较多,而需要设计泛型 API 的地方一般都是比较偏底层的部分。
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,当然要支持反射,语言层面上也是要做一些支持的。