进取者 發表於 2019-6-10 21:28:00

C# 9.0新特性

<h1 id="candidatefeaturesforcsharp9">CandidateFeaturesForCSharp9</h1>
<p>看到标题,是不是认为我把标题写错了?是的,C# 8.0还未正式发布,在官网它的最新版本还是Preview 5,通往C#9的漫长道路却已经开始.前写天收到了活跃在C#一线的<code>BASSAM ALUGILI</code>给我分享C# 9.0新特性,我在他文章的基础上进行翻译,希望能对大家有所帮助.</p>
<p><img src="https://raw.githubusercontent.com/iblogspost/CandidateFeaturesForCSharp9/master/BassamAlugiliTTranslationInvitation.png" alt="BassamAlugiliTTranslationInvitation" loading="lazy"></p>
<p>这是世界上第一篇关于C#9候选功能的文章。阅读完本文后,你将会为未来可能遇到的C# 9.0新特性做好更充分的准备。</p>
<p>这篇文章基于,</p>
<ul>
<li>C# 9.0候选新特性</li>
</ul>
<h2 id="原生大小的数字类型">原生大小的数字类型</h2>
<p>这次引入一组新类型(nint,nuint,nfloat等)'n'表示<code>native(原生)</code>,该特性允许声明一个32位或64位的数据类型,这取决于操作系统的平台类型。</p>
<pre><code class="language-c#">nint nativeInt = 55; take 4 bytes when I compile in 32 Bit host.   
nint nativeInt = 55; take 8 bytes when I compile in 64 Bit host with x64 compilation settings.
</code></pre>
<p>xamarin中已存在类似的概念,</p>
<ul>
<li>xamarin原生类型</li>
</ul>
<h2 id="records-and-pattern-based-with-expression">Records and Pattern-based With-Expression</h2>
<p>这个功能我等待了很长时间,Records是一种轻量级的不可变类型,它可以是方法,属性,运算符等,它允许我们进行结构的比较, 此外,默认情况下,Records属性是只读的。</p>
<p>Records可以是值类型或引用类型。</p>
<p><strong>Example</strong></p>
<pre><code class="language-c#">public class Point3D(double X, double Y, double Z);   
public class Demo   
{   
public void CreatePoint()   
{   
var p = new Point3D(1.0, 1.0, 1.0);
}
}
</code></pre>
<p>这些代码会被编译器转化如下形式.</p>
<pre><code class="language-c#">public class Point3D   
{   
private readonly double &lt;X&gt;k__BackingField;   
private readonly double &lt;Y&gt;k__BackingField;   
private readonly double &lt;Z&gt;k__BackingField;   
public double X {get {return &lt;X&gt;k__BackingField;}}   
public double Y{get{return &lt;Y&gt;k__BackingField;}}   
public double Z{get{return &lt;Z&gt;k__BackingField;}}   
   
public Point3D(double X, double Y, double Z)   
{   
&lt;X&gt;k__BackingField = X;   
&lt;Y&gt;k__BackingField = Y;   
&lt;Z&gt;k__BackingField = Z;   
}   
   
public bool Equals(Point3D value)   
{   
return X == value.X &amp;&amp; Y == value.Y &amp;&amp; Z == value.Z;   
}   
   
public override bool Equals(object value)   
{   
Point3D value2;   
return (value2 = (value as Point3D)) != null &amp;&amp; Equals(value2);   
}   
   
public override int GetHashCode()   
{   
return ((1717635750 * -1521134295 +EqualityComparer&lt;double&gt;.Default.GetHashCode(X)) * -1521134295 + EqualityComparer&lt;double&gt;.Default.GetHashCode(Y)) * -1521134295 +EqualityComparer&lt;double&gt;.Default.GetHashCode(Z);   
}   
}   
   
Using Records:   
   
public class Demo   
{   
public void CreatePoint()   
{   
Point3D point3D = new Point3D(1.0, 1.0, 1.0);   
}   
}
</code></pre>
<p>Records迎合了基于表达式形式编程的特性,使得我们可以这样使用它.</p>
<pre><code class="language-c#">var newPoint3D = Point3D.With(x: 42);
</code></pre>
<p>这样我们创建的新Point(new Point3D)就像现有的一个(point3D)一样并把X的值更改为42。</p>
<p>这个特性于基于<code>pattern matching</code>也非常有效,我会在我的下一篇文章中介绍这一点.</p>
<p>那么我们为什么要使用Records而不是用结构体呢?为了回答这些问题,我引用了了Reddit的一句话:</p>
<p>“结构体是你必须要有一些约定来实现的东西。你不必手动地去让它只读,你也不用去实现他们的比较逻辑,但如果你不这样做,那你就失去了使用结构体的意义,编译器不会强制执行这些约束"。</p>
<p>Records类型由是编译器实现,这意味着您必须满足所有这些条件并且不能错误, 因此,它们不仅可以减少重复代码,还可以消除一大堆潜在的错误。</p>
<p>此外,这个功能在F#中存在了十多年,其他语言如(Scala,Kotlin)也有类似的概念。</p>
<p><strong>F#</strong></p>
<pre><code class="language-F#">type Greeter(name: string) = member this.SayHi() = printfn "Hi, %s" name
</code></pre>
<p><strong>Scala</strong></p>
<pre><code class="language-scala">class Greeter(name: String)   
{   
   def SayHi() = println("Hi, " + name)   
}
</code></pre>
<p><strong>Kotlin</strong></p>
<pre><code class="language-kotlin">class Greeter(val name: String)   
{   
fun sayhi()   
{   
println("Hi, ${name}");   
}   
}
</code></pre>
<p>在没有Records之前,我们要实现类似的功能,C#代码要这么写</p>
<p><strong>C#</strong></p>
<pre><code class="language-c#">public class Greeter
{
private readonly string _name;
public Greeter(string name)
{
_name = name;
}
public void Greet()
{
Console.WriteLine($ "Hello, {_name}");
}
}
</code></pre>
<p>有了Records之后,我们可以将C#代码大大地减少了,</p>
<pre><code class="language-c#">ublic class Greeter(name: string)   
{   
public void Greet()   
{   
Console.WriteLine($ "Hello, {_name}");   
}   
}
</code></pre>
<p>Less code!=I love it!</p>
<h2 id="type-classes">Type Classes</h2>
<p>此功能的灵感来自Haskell,它是我最喜欢的功能之一。正如我两年前在我文章中所说,C#将实现更多的函数式编(FP)程概念,Type Classes就是FP概念之一。在函数式编程中,Type Classes允许您在类型上添加一组操作,但不实现它。由于实现是在其他地方完成的,这是一种多态,它比面向对象编程语言中的class更灵活。</p>
<p>Type Classes和C#接口具有相似的用途,但它们的工作方式有所不同,在某些情况下,由于处理固定类型而不是继承层次结构,因此Type Classes更易于使用。</p>
<p>此这特性最初与“extending everything”功能一起引入,您可以将它们组合在一起,如Mads Torgersen给出的例子所示。</p>
<p>我引用了官方提案中的一些结论:</p>
<p>“一般来说,”shape“(shape是Type Classes的一个新的关键字)声明非常类似于接口声明,除了以下情况,</p>
<ul>
<li>它可以定义任何类型的成员(包括静态成员)</li>
<li>可以通过扩展实现</li>
<li>只能在指定的地方当作一种类型使用(作用域)“</li>
</ul>
<p>Haskell中 Type Classes示例。</p>
<pre><code class="language-haskell">class Eq a where   
(==) :: a -&gt; a -&gt; Bool   
(/=) :: a -&gt; a -&gt; Bool
</code></pre>
<p>“Eq”是类名,而==,/ =是类中的操作。类型“a”是类“Eq”的实例。</p>
<p>如果我们将上述例子用C#接口实现将会是这样.</p>
<pre><code class="language-c#">interface Num&lt;A&gt;   
{   
A Add(A a, A b);   
A Mult(A a, A b);   
A Neg(A a);   
}   
   
struct NumInt : Num&lt;int&gt;   
{   
public int Add(int a, int b) =&gt; a + b;   
public int Mult(int a, int b) =&gt; a * b;   
public int Neg(int a) =&gt; -a;   
}
</code></pre>
<p>如果我们用Type Classes实现C# 功能会是这样</p>
<pre><code class="language-c#">shape Num&lt;A&gt;   
{   
A Add(A a, A b);   
A Mult(A a, A b);   
A Neg(A a);   
}   
   
instance NumInt : Num&lt;int&gt;   
{   
int Add(int a, int b) =&gt; a + b;   
int Mult(int a, int b) =&gt; a * b;   
int Neg(int a) =&gt; -a;   
}
</code></pre>
<p>通过上面例子,可以看到接口和shape的语法类似 ,那我们再来看看Mads Torgersen给出的例子</p>
<blockquote>
<p>Note:shape不是一种类型。相反,shape的主要目的是用作通用约束,限制类型参数以具有正确的形状,同时允许通用声明的主体使用该形状,</p>
</blockquote>
<p>原始来源</p>
<pre><code class="language-c#">public shape SGroup&lt;T&gt;      
{      
static T operator +(T t1, T t2);      
static T Zero {get;}      
}
</code></pre>
<p>这个声明说如果一个类型在T上实现了一个+运算符并且具有0静态属性,那么它可以是一个SGroup <t>。</t></p>
<p>给int添加静态成员Zero</p>
<pre><code class="language-c#">public extension IntGroup of int: SGroup&lt;int&gt;   
{   
public static int Zero =&gt; 0;   
}
</code></pre>
<p>定义一个AddAll方法</p>
<pre><code class="language-c#">public static AddAll&lt;T&gt;(T[] ts) where T: SGroup&lt;T&gt; // shape used as constraint   
{   
var result = T.Zero; // Making use of the shape's Zero property   
foreach (var t in ts) { result += t; } // Making use of the shape's + operator   
return result;   
}
</code></pre>
<p>让我们用一些整数调用AddAll方法,</p>
<pre><code class="language-c#">int[] numbers = { 5, 1, 9, 2, 3, 10, 8, 4, 7, 6 };      
WriteLine(AddAll(numbers)); // infers T = int
</code></pre>
<p>这就是Type class 的妙处,慢慢消化感受一下??</p>
<h2 id="dictionary-literals">Dictionary Literals</h2>
<p>引入更简单的语法来创建初始化的Dictionary &lt;TKey,TValue&gt;对象,而无需指定Dictionary类型名称或类型参数。使用用于数组类型推断的现有规则推断字典的类型参数。</p>
<pre><code class="language-c#">// C# 1..8   
var x = new Dictionary &lt;string,int&gt; () { { "foo", 4 }, { "bar", 5 }};   
// C# 9   
var x = ["foo":4, "bar": 5];
</code></pre>
<p>该特性使C#中的字典工作更简单,并删除冗余代码。此外,值得一提的是,在F#和Swift等其他编程语言中也使用了类似的字典语法。</p>
<h2 id="params-span-">Params Span <t></t></h2>
<p>允许params语法使用Span <t>这个帮助来实现没有任何堆分配的params参数传递。此功能可以使params方法的使用更加高效。</t></p>
<p>新的语法如下,</p>
<pre><code class="language-c#">void Foo(params Span&lt;int&gt; values);
</code></pre>
<h2 id="struct允许使用无参构造函数">struct允许使用无参构造函数</h2>
<p>到目前为止,在C#中不允许在结构体声明中使用无参构造函数,在C#9中,将删除此限制。</p>
<p><strong>StackOverflow示例</strong></p>
<pre><code class="language-c#">public struct Rational   
{   
private long numerator;   
private long denominator;   
   
public Rational(long num, long denom)   
{ /* Todo: Find GCD etc. */ }   
   
public Rational(long num)   
{   
numerator = num;   
denominator = 1;   
}   
   
public Rational() // This is not allowed   
{   
numerator = 0;   
denominator = 1;   
}   
}
</code></pre>
<p>链接到StackOverflow示例</p>
<p>其实CLR已经允许值类型数据具有无参构造函数,只是C# 对这个功能进行了限制,在C# 9.0中可能会消除这种限制.</p>
<h2 id="固定大小的缓冲区">固定大小的缓冲区</h2>
<p>这些提供了一种通用且安全的机制,用于向C#语言声明固定大小的缓冲区。</p>
<p>目前,用户可以在不安全的环境中创建固定大小的缓冲区。但是,这需要用户处理指针,手动执行边界检查,并且只支持一组有限的类型(bool,byte,char,short,int,long,sbyte,ushort,uint,ulong,float和double)。该特性引入后将使固定大小的缓冲区变得安全安全,如下例所示。</p>
<p>可以通过以下方式声明一个安全的固定大小的缓冲区,</p>
<pre><code class="language-c#">public fixed DXGI_RGB GammaCurve;
</code></pre>
<p>该声明将由编译器转换为内部表示,类似于以下内容,</p>
<pre><code class="language-c#">   
public ConsoleApp1.&lt;Buffer&gt;e__FixedBuffer_1024&lt;DXGI_RGB&gt; GammaCurve;   
   
// Pack = 0 is the default packing and should result in indexable layout.   
   
struct &lt;Buffer&gt;e__FixedBuffer_1024&lt;T&gt;   
{   
private T _e0;   
private T _e1;   
// _e2 ... _e1023   
private T _e1024;   
public ref T this =&gt; ref (uint)index &lt;= 1024u ?   
ref RefAdd&lt;T&gt;(ref _e0, index):   
throw new IndexOutOfRange();   
}
</code></pre>
<h2 id="uft8字符串文字">Uft8字符串文字</h2>
<p>它是关于定义一种新的字符串类型UTF8String,它将是,</p>
<pre><code class="language-c#">System.UTF8String myUTF8string ="Test String";
</code></pre>
<h2 id="baset">Base(T)</h2>
<p>此功能用于解决默认接口方法中的覆盖冲突问题:</p>
<pre><code class="language-c#">interface I1
{
    void M(int) { }
}

interface I2
{
    void M(short) { }
}

interface I3
{
    override void I1.M(int) { }
}

interface I4 : I3
{
    void M2()
    {
      base(I3).M(0) // Which M should be used here? What does this do?
    }
}
</code></pre>
<p>更多信息,</p>
<ul>
<li><em>https://github.com/dotnet/csharplang/issues/2337</em></li>
<li><em>https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-02-27.md</em></li>
</ul>
<h2 id="摘要">摘要</h2>
<p>您已经阅读了第一个C#9候选特性。正如您所看到的,许多新功能受到其他编程语言或编程范例的启发,而不是自我创新,这些特性大部分在在社区中得到了广泛认可,所以引入C# 后应该也会给大家带来不错的体验.</p>
<p>原文 : https://www.c-sharpcorner.com/article/candidate-features-for-c-sharp-9/</p><br><br>
来源:https://www.cnblogs.com/CoderAyu/p/11000386.html
頁: [1]
查看完整版本: C# 9.0新特性