逛青楼 發表於 2020-2-29 20:42:00

从未来看 C#

<h1 id="前言">前言</h1>
<p>如今 C# 虽然发展到了 8.0 版本,引入了诸多的函数式特性,但其实在 C# 未来的规划当中,还有很多足以大规模影响现有 C# 代码结构和组成的特性,本文中将会对就重要的特性进行介绍,并用代码示例展示这些特性。</p>
<p>以下特性将会在 C# 9.0、10.0 或者更高版本提供。</p>
<h1 id="records">Records</h1>
<p>Records 是一种全新的简化的 C# <code>class</code> 和 <code>struct</code> 的形式。</p>
<p>现在当我们需要声明一个类型用来保存数据,并且支持数据的解构的话,需要像如下一样写出大量的样板代码:</p>
<pre><code class="language-csharp">class Point : IEquatable&lt;Point&gt;
{
    public readonly double X;
    public readonly double Y;

    public Point(double X, double Y)
    {
      this.X = X;
      this.Y = Y;
    }

    public static bool operator==(Point left, Point right) { ... }

    public bool Equals(Point other) { ... }
    public override bool Equals(object other) { ... }
    public override int GetHashCode() { ... }
    public void Deconstruct(out double x, out double y) { ... }
}
</code></pre>
<p>十分复杂。引入 Records 之后,上面的样板代码只需简化成一句话:</p>
<pre><code class="language-csharp">data class Point(double X, double Y);
</code></pre>
<p>并且 Records 支持数据的变换、解构和模式匹配:</p>
<pre><code class="language-csharp">var pointA = new Point(3, 5);
var pointB = pointA with { Y = 7 };
var pointC = new Point(3, 7);

// 当 Y = 5 时为 X,否则为 Y
var result = pointB switch
{
    (var first, 5) =&gt; first,
    (_, var second) =&gt; second
};

// true
Console.WriteLine(pointB == pointC);
</code></pre>
<p>当然,<code>record</code> 是 <code>immutable</code> 的,并且是可以合并(继承)的,也可以标记为 <code>sealed</code> 或者 <code>abstract</code>:</p>
<pre><code class="language-csharp">sealed data class Point3D(double X, double Y, double Z) : Point(X, Y);
</code></pre>
<p>上面的这种 <code>record</code> 声明方式是基于位置声明的,即 <code>Point(first, second)</code>,<code>fisrt</code> 所代表的第一个位置将成为 <code>X</code>,<code>second</code> 所代表的第二个位置将成为 <code>Y</code>。</p>
<p>还有一种声明方式是基于名称的:</p>
<pre><code class="language-csharp">data class Point { double X; double Y };
var point = new Point { X = 5, Y = 6 };
</code></pre>
<h1 id="discriminated-unions">Discriminated Unions</h1>
<p>Discriminated unions 又叫做 enum class,这是一种全新的类型声明方式,顾名思义,是类型的 “枚举”。</p>
<p>例如,我们需要定义形状,形状有矩形、三角形和圆形,以前我们需要先编写一个 <code>Shape</code> 类,然后再创建 <code>Rectangle</code>、<code>Triangle</code> 和 <code>Circle</code> 类继承 <code>Shape</code> 类,现在只需要几行就能完成,并且支持模式匹配和解构:</p>
<pre><code class="language-csharp">enum class Shape
{
    Retangle(double Width, double Height);
    Triangle(double Bottom, double Height);
    Circle(double Radius);
    Nothing;
}
</code></pre>
<p>然后我们就可以使用啦:</p>
<pre><code class="language-csharp">var circle = new Circle(5);
var rec = new Rectangle(3, 4);

if (rec is Retangle(_, 4))
{
    Console.WriteLine("这不是我想要的矩形");
}

var height = GetHeight(rec);

double GetHeight(Shape shape)
    =&gt; shape switch
    {
      Retangle(_, height) =&gt; height,
      Triangle(_, height) =&gt; height,
      _ =&gt; throw new NotSupportedException()
    };
</code></pre>
<p>利用此特性,我们可以轻而易举的实现支持模式匹配的、type sound 的可空数据结构:</p>
<pre><code class="language-csharp">enum class Option&lt;T&gt;
{
    Some(T value);
    None;
}

var x = Some(5);
// Option&lt;never&gt;
var y = None;

void Foo(Option&lt;T&gt; value)
{
    var bar = value switch
    {
      Some(var x) =&gt; x,
      None =&gt; throw new NullReferenceException()
    };
}
</code></pre>
<h1 id="union-and-intersection-types">Union and Intersection Types</h1>
<p>当我们想要表示一个对象是两种类型其一时,将可以使用联合类型来表达:</p>
<pre><code class="language-csharp">public type SignedNumber = short | int | long | float | double | decimal;
public type ResultModel&lt;T&gt; = DataModel&lt;T&gt; | ErrorModel;
</code></pre>
<p>这在 Web API 中非常有用,当我们的接口可能返回错误的时候,我们不再需要将我们的数据用以下方式包含在一个统一的模式中:</p>
<pre><code class="language-csharp">public class ResultModel&lt;T&gt;
{
    public string Message { get; set; }
    public int Code { get; set; }
    public T Data { get; set; }
}
</code></pre>
<p>我们将能够做到,不依赖异常等流程处理的方式做到错误时返回错误信息,请求正常处理时返回真实所需的数据:</p>
<pre><code class="language-csharp">public async ValueTask&lt;DataModel | ErrorModel&gt; SomeApi()
{
    if (...) return new DataModel(...);
    return new ErrorModel(...);
}
</code></pre>
<p>还有和类型,用来表示多个类型之和,我们此前在设计接口时,如果需要一个类型实现了多个接口,则需要定义一个新接口去实现之前的接口:</p>
<pre><code class="language-csharp">interface IA { ... }
interface IB { ... }
interface IAB : IA, IB { }

void Foo(IAB obj) { ... }
</code></pre>
<p>有了和类型之后,样板代码 <code>IAB</code> 将不再需要:</p>
<pre><code class="language-csharp">void Foo(IA &amp; IB obj) { ... }
</code></pre>
<p>或者我们也可以这样声明新的类型:</p>
<pre><code class="language-csharp">type IAB = IA &amp; IB;
</code></pre>
<h1 id="bottom-type">Bottom Type</h1>
<p>Bottom type 是一种特殊的类型 <code>never</code>,<code>never</code> 类型是任何类型的子类,因此不存在该类型的子类。一个 <code>never</code> 类型的什么都不表示。</p>
<p>Union types 带来一个问题,就是我们有时候需要表达这个东西什么都不是,那么 <code>never</code> 将是一个非常合适的选择:</p>
<pre><code class="language-csharp">type Foo = Bar | Baz | never;
</code></pre>
<p>另外,<code>never</code> 还有一个重要的用途:控制代码流程,一个返回 <code>never</code> 的函数将结束调用者的逻辑,即这个函数不会返回:</p>
<pre><code class="language-csharp">void | never Foo(int x)
{
    if (x &gt; 5) return;
    return never;
}

void Main()
{
    Foo(6);
    Console.WriteLine(1);
    Foo(4);
    Console.WriteLine(2);
}
</code></pre>
<p>上述代码将只会输出 1。</p>
<h1 id="concepts">Concepts</h1>
<p>Concepts 又叫做 type classes、traits,这个特性做到可以在不修改原有类型的基础上,为类型实现接口。</p>
<p>首先我们定义一个 <code>concept</code>:</p>
<pre><code class="language-csharp">concept Monoid&lt;T&gt;
{
    // 加函数
    T Append(this T x, T y);
    // 零属性
    static T Zero { get; }
}
</code></pre>
<p>然后我们可以为这个 <code>concept</code> 创建类型类的实例:</p>
<pre><code class="language-csharp">instance IntMonoid : Monoid&lt;int&gt;
{
    int Append(this int x, int y) =&gt; x + y;
    static int Zero =&gt; 0;
}
</code></pre>
<p>这样我们就为 <code>int</code> 类型实现了 <code>Monoid&lt;int&gt;</code> 接口。</p>
<p>当我们想实现一个函数用来将一个 <code>int</code> 数组中的所有元素求和时,只需要:</p>
<pre><code class="language-csharp">public T Sum&lt;T, inferred M&gt;(T[] array) where M : Monoid&lt;T&gt;
{
    T acc = M.Zero;
    foreach (var i in array) acc = acc.Append(i);
    return acc;
}
</code></pre>
<p>注意到,类型 <code>M</code> 会根据 <code>T</code> 进行自动推导得到 <code>Monoid&lt;int&gt;</code>。</p>
<p>这样我们就能做到在不需要修改 <code>int</code> 的定义的情况下为其实现接口。</p>
<h1 id="higher-kinded-polymorphism">Higher Kinded Polymorphism</h1>
<p>Higher kinded polymorphism,又叫做 templated template,或者 generics on generics,这是一种高阶的多态。</p>
<p>举个例子,比如当我们需要表达一个类型是一个一阶泛型类型,且是实现了 <code>ICollection&lt;&gt;</code> 的容器之一时,我们可以写:</p>
<pre><code class="language-csharp">void Foo&lt;T&gt;() where T : &lt;&gt;, ICollection&lt;&gt;, new();
</code></pre>
<p>有了这个特性我们可以轻而易举的实现 <code>monads</code>。</p>
<p>例如我们想要做一个将 <code>IEnumerable&lt;&gt;</code> 中所有元素变成某种集合类型的时候,例如 <code>ToList()</code> 等,我们就不需要显式地实现每一种需要的类型的情况(例如 <code>List&lt;&gt;</code>):<code>List&lt;T&gt; ToList(this IEnumerable&lt;T&gt; src)</code>了。</p>
<p>我们只需要这么写:</p>
<pre><code class="language-csharp">T&lt;X&gt; To&lt;T, X&gt;(this IEnumerable&lt;X&gt; xs) where T : &lt;&gt;, ICollection&lt;&gt;, new()
{
    var result = new T&lt;X&gt;();
    foreach (var x in xs) result.Add(x);
    return result;
}
</code></pre>
<p>当我们想要把一个 <code>IEnumerable&lt;int&gt; x</code> 转换成 <code>List&lt;int&gt;</code> 时,我们只需简单的调用:<code>x.To&lt;List&lt;&gt;&gt;()</code> 即可。</p>
<h1 id="simple-programs">Simple Programs</h1>
<p>该特性允许编写 C# 代码时,无需 <code>Main</code> 函数,直接像写脚本一样直接在文件中编写逻辑代码,以此简化编写少量代码时却需要书写大量样板代码的问题:</p>
<p>以前写代码:</p>
<pre><code class="language-csharp">namespace Foo
{
    class Bar
    {
      static async Task Main(string[] args)
      {
            await Task.Delay(1000);
            Console.WriteLine("Hello world!");
      }
    }
}
</code></pre>
<p>现在写代码:</p>
<pre><code class="language-csharp">await Task.Delay(1000);
Console.WriteLine("Hello world!");
</code></pre>
<h1 id="expression-blocks">Expression Blocks</h1>
<p>该特性允许创建表达式块:</p>
<pre><code class="language-csharp">Func&lt;int, int, bool&gt; greaterThan = (a, b) =&gt; if (a &gt; b) a else b;

// true
greaterThan(5, 4);
</code></pre>
<p>因此有了以上特性,我们可以利用表达式实现更加复杂的东西。</p>
<h1 id="后记">后记</h1>
<p>以上特性都是对代码布局和组成影响非常大的特性,并且不少特性几年前就已经被官方实现,但是因为存在尚未讨论解决的问题,迟迟没有发布进产品。</p>
<p>除此之外,还有几十个用于改进语言和方便用户使用等等的小特性也在未来的规划当中,此处不进行介绍。</p>
<p>未来的 C# 和今天的 C# 区别是很大的,作为一门多范式语言,C# 正在朝远离 Pure OOP 的方向渐行渐远,期待这门语言变得越来越好。</p><br><br>
来源:https://www.cnblogs.com/hez2010/p/12385967.html
頁: [1]
查看完整版本: 从未来看 C#