[聚合文章] C# 7.0

.Net 2017-11-16 1 阅读

C# 7.0 可以在 VS 17 使用,这个版本我下载企业版上传百度云,有需要可以到文章最后去。

本文主要:C# 7.0 带来的新功能

  • out 返回值定义

  • Tuples

  • 模式匹配

  • ref 返回本地值

  • 内部函数

  • 全部地方可以支持辣么大

  • 在表达式扔异常

  • 广义异步返回类型

  • 数值常量语法

C# 7.0的功能主要是数据处理,让代码更简洁,让代码性能更高

让代码简单这个我觉得不如6.0,性能WR为了Iot做的。C#速度差,垃圾wr就让C#可以直接访问内存,让速度变快,这个下面没有说

C# 7.0 最好的是 使用 Tuple 。虽然之前也有,但是现在版本比较好用。实际抄袭了某脚本。

修改大的有 Case 。模式匹配,可以判断类型,其实这个使用是我们有类 a,类b、c继承a,这时使用就比较好,如何使用在下面会说。

如果觉得这个功能没有用,可以去 Visual studio 按反馈喷

如果好奇他是怎么弄,可以查看https://github.com/dotnet/roslyn

out 返回值定义

我们以前要使用 out 总是需要在外面定义我们变量。

首先定义一个 变量,使用函数,这样觉得需要多写代码

public void PrintCoordinates(Point p)
  {
    int x, y; // 在外面定义
    p.GetCoordinates(out x, out y);
    WriteLine($"({x}, {y})");
  }

在7.0我们可以使用在 out 定义我们变量,这样看起来不是在一个区域,但是可以减少我的代码

public void PrintCoordinates(Point p)
{
    p.GetCoordinates(out int x, out int y);
    WriteLine($"({x}, {y})");
}

在 out 定义类型,定义可以用var

看到这我才说这样有用,如果我们开始没有确定我们返回的是什么,然后直接定义,需要修改地方多,但是如果我们使用Var就可以让我们定义修改少,一般在写就需要先想我们需要用什么,不要总是改

如果我们使用一个返回为bool,那么可以在{使用out的值

public void PrintStars(string s)
{
    //转换,可以是数字,显示
    if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
    else { WriteLine("Cloudy - no stars tonight!"); }
}

== 下面代码被WR删了,以前有说到,现实wr没有做

如果有返回值我们不需要,可以out *,这样我们就不用知道这个返回值,原先不需要使用我还要想一个变量,然后vs说我这个没有使用,现在我们直接就不给他名

在我们下面有返回多个,这时不需要的可以用*

public void PrintStars(string s)
{
    //转换,可以是数字,显示
    if (int.TryParse(s, out *)) { WriteLine("转换成功"); }
    else { WriteLine("转换失败"); }
}

== 上面代码WR没有做,不需要的返回值是可以使用 _

模式匹配

模式匹配是包括 is 和 switch ,下面先说 is

C# 7.0可以使用 is 一部分代替 as

我们需要判断一个值是否是一个类型,如果是那么赋值,在以前,使用的代码需要两行

if(o is int)
    {

        int i=(int) o;
    }

还可以使用 as

int? i = o as int;

但是在新的C#,可以使用

o is int i

那么我们就可以直接使用i

在我们一个判断,如果我们存在了object o是int,那么我们就使用 int i=(int)o;

如果我们object不是int,那么转换object o是string, string s=(string)o;

这也就是对下面的语句

int.TryParse(s,out i);

我们可以简化,判断是不是int,如果是给i,这时就返回true

然后判断是不是string,是就转,成功使用i

if (o is int i || (o is string s && int.TryParse(s, out i)) { /* use i */ }

但是 is 的用法在于 switch

我们在Case可以选择类型

switch(shape) 
{
    case Circle c:
        WriteLine($"circle with radius {c.Radius}");
        break;
    case Rectangle s when (s.Length == s.Height):
        WriteLine($"{s.Length} x {s.Height} square");
        break;
    case Rectangle r:
        WriteLine($"{r.Length} x {r.Height} rectangle");
        break;
    default:
        WriteLine("<unknown shape>");
        break;
    case null:
        throw new ArgumentNullException(nameof(shape));
}

case 顺序很重要,可以看到可以判断类型,但是 case 还可以混合判断。

switch (item)
                {
                    default:
                        throw new InvalidOperationException("unknown item type"); 

                    case 0:
                        break;

                    case int val:
                        sum += val;
                        break;

                    case var @var when (@var != null && (int) (@var) == 45):
                        break;

                    //  The order of case clauses now matters!
                    case IEnumerable<object> subList when subList.Any():
                        sum += Sum(subList);
                        break;

                    case IEnumerable<object> subList:
                        break;

                    case null:
                        break;
                }

注意 default 在最后,即使他后面有语句,除非存在语句识别,那么最后会执行他。

Tuples

以前我们需要返回多个有点难,可以使用out参数,可以 Tuples<string,double>

我们做了修改,可以使用新的方法,这样我们返回多个就可以直接和某垃圾语言那返回

(string, string, string) LookupName(long id) // tuple return type
{
    ... // 返回多个数据,我们在数据拿到多个数据
    return (first, middle, last); // tuple literal
}

var names = LookupName(id);

我们这样用第一返回值: names.Item1 和原来几乎没有修改,这样对于返回值不好,因为我们难以去记,哪个返回值是什么

我们要给他一个好记的 变量,可以写在函数定义

(string first, string middle, string last) LookupName(long id)

我们使用第一个 names.first ,这样使用就容易,原因是可以给一个表达他是意思的变量。

返回可以使用 return (first, middle, last); ,必须和之前定义顺序一样,但如果定义了名称,可以使用

return last:last,first:first

这个方法是很好的,不需要和定义的顺序那样。

对于调用函数,可以使用一个变量,可以使用多个变量

(string first, string middle, string last) = LookupName(id1);
    var name = LookupName(id1);

可以看到两个代码,作用一样,但是第一个代码除了使用变量类型,同样可以使用 var

(var fist,var midd)=Lookup(id);

如果我们有多个var,那么我们可以简单 var (first, middle, last) = LookupName(id1); 定义所有变量

除了方法使用,可以在变量使用

var sumNew = (first: 1, count: 20);

这样就定义了一个,可以使用他的名称,不使用 item 原先的,也就是在定义,给他变量。

上面代码的意思:可以定义一个包括每项名称的变量,可以在使用时,用定义的变量

var sumNew = (first: 1, count: 20);
            Console.WriteLine($"first {sumNew.first}  count {sumNew.count}");

如果不想在定义写变量,那么可以修改 var ,作为变量

(int first, int count) sum = ( 1,  20);
      Console.WriteLine($"first {sum.first}  count {sum.count}");

这里,类型 int 不能写 var

如果想不到变量,那么只能使用

(int , int ) sum = ( 1,  20);
            Console.WriteLine($"first {sum.Item1}  count {sum.Item2}");

如果使用的不是 .net Framework 4.7 那么引用 ValueTuple 才可以。除了支持多个返回值,实际 ValueTuple 可以作为附加在类的新字段,一般在列表使用。

例如我有一个类 Foo 里面只有一个属性 Name ,但是在 ViewModel 需要他有 Check 属性表示是否选中。那么可以使用 ValueTuple 添加这个属性。

List<(Foo foo,bool check)> Foo

本地函数

我们可以在函数里面定义函数,这是本地函数

public int Fibonacci(int x)
{
    if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
    return Fib(x).current;
    //下面 本地函数
    (int current, int previous) Fib(int i)
    {
        if (i == 0) return (1, 0);
        var (p, pp) = Fib(i - 1);
        return (p + pp, p);
    }
}

以前有些函数只会使用一次,但是他的功能多,所以就把它写成方法,于是一个类就很多这种方法,只会在一个函数使用,但是写成方法,有时候开始看他,会觉得方法很多,不知道哪个方法在哪使用。

上面说的是那些没使用 vs 企业版的兄弟,其实有了企业版,没有这问题。

现在可以使用内部函数,在一个函数里定义函数,看上面的代码,写一个斐波纳算法,可以直接使用函数里函数,不需要定义方法。

这个用法在:迭代,异步

对于迭代器,抛出异常在使用,不是创建。

看下这代码

public static IEnumerable<char> AlphabetSubset(char start, char end)
{
    if ((start < 'a') || (start > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < 'a') || (end > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    for (var c = start; c < end; c++)
        yield return c;
}

在输入不合法,就会抛出异常,那么抛出异常的时候是什么

var resultSet = Iterator.AlphabetSubset('f', 'a');
Console.WriteLine("iterator created");
foreach (var thing in resultSet)
  {
    Console.Write($"{thing}, ");
  }

可以看到在 var resultSet = Iterator.AlphabetSubset('f', 'a'); 不会抛出异常,在 Console.Write($"{thing}, "); 抛出异常。

很难定位到是在哪的异常,出现异常和知道异常的,不在一个地方,这就是之前使用迭代的一个比较难发现的。

所以做法是新建一个方法迭代

public static IEnumerable<char> AlphabetSubset2(char start, char end)
{
    if ((start < 'a') || (start > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < 'a') || (end > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
    return alphabetSubsetImplementation(start, end);
}

private static IEnumerable<char> alphabetSubsetImplementation(char start, char end)
{ 
    for (var c = start; c < end; c++)
        yield return c;
}

这样就可以定位,但是问题是,可能错误调用 alphabetSubsetImplementation ,直接使用 他,不是使用 AlphabetSubset2 ,所以在新的C#,可以使用内部方法

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if ((start < 'a') || (start > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if ((end < 'a') || (end > 'z'))
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation()
    {
        for (var c = start; c < end; c++)
            yield return c;
    }
}

同时,在异步,如果出现异常,也是难以定位,所以可以用内部方法在异步前判断异常

public Task<string> PerformLongRunningWork(string address, int index, string name)
{
    if (string.IsNullOrWhiteSpace(address))
        throw new ArgumentException(message: "An address is required", paramName: nameof(address));
    if (index < 0)
        throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
    if (string.IsNullOrWhiteSpace(name))
        throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));

    return longRunningWorkImplementation();

    async Task<string> longRunningWorkImplementation()
    {
        var interimResult = await FirstWork(address);
        var secondResult = await SecondStep(index, name);
        return $"The results are {interimResult} and {secondResult}. Enjoy.";
    }
}

在使用异步函数前异常,不让开发者使用没有校验的 longRunningWorkImplementation ,这就是内部方法的使用。

但是可能有兄弟这样写,让我觉得这个语言太垃圾

public static void A()
        {
            A1();
            void A1()
            {
                void A2()
                {
                    void A3()
                    {
                        
                    }
                }

                A2();
                //A3();
            }

            A1();
        }

改进常量

我们有比较长数字,那么我们在定义比较难知道他是否写对,这导致第一次阅读代码容易误解。特别是当数值没有规律的时候。C# 7.0包含两个新功能:二进制常量和数值分割。

C# 7.0现在做了改进,可以使用下划线,下划线可以分割常量。这样做的目的是看起来比较好看,容易数我们写了多少数字,可以看我们是不是写错。

例如对于二进制数值的定义

public const int One =  0b0001;
public const int Two =  0b0010;
public const int Four = 0b0100;
public const int Eight = 0b1000;

定义的开始是 0b 这个字符,之后就是二进制的0、1两个字符组成的数值。

不仅可以定义二进制数值,还可以添加下划线,下划线可以分割字符数值,如下代码所示。

public const int Sixteen = 0b0001_0000; public const int ThirtyTwo = 0b0010_0000; public const int SixtyFour = 0b0100_0000; public const int OneHundredTwentyEight = 0b1000_0000;

数值分割下划线可以出现在除了字符串开始的任何位置,如定义一个比较大的整数,可以这样写

public const long BillionsAndBillions = 100_000_000_000;

同样,数值分割下划线支持decimal, float 和 double,对于浮点数值,可以在小数点之后任意位置添加。

public const double AvogadroConstant = 6.022_140_857_747_474e23; public const decimal GoldenRatio = 1.618_033_988_749_894_848_204_586_834_365_638_117_720_309_179M;

总之,你可以定义出你觉得比较容易阅读的数值。

var d = 123_456;
var x = 0xAB_CD_EF;

我们还可以定义2进制,原来是无法定义,但是所有的二进制还是存储用int,不是一个类型。

var b = 0b1010_1011_1100_1101_1110_1111;

这个功能在 Iot 经常需要使用二进制,如果是以前,可以使用 true 和 false、byte互转,写起来反人类,现在用这个方法就比较简单。二进制容易写错,所以上面的功能,可能是因为二进制做的。

ref returns 返回值

我们返回的是引用,现在返回可以是值,我们返回数组中的一个值,那么修改这个值,因为放进引用,我们输出数组是修改的值

public ref int Find(int number, int[] numbers)
{
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] == number) 
        {
            return ref numbers[i]; // return the storage location, not the value
        }
    }
    throw new IndexOutOfRangeException($"{nameof(number)} not found");
}

int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); 
place = 9; // 修改
WriteLine(array[4]); // 9

全部地方可以支持辣么大

以前支持辣么大的地方很少,关于辣么大,参见 https://docs.microsoft.com/en-us/dotnet/articles/csharp/lambda-expressions

现在可以在所有地方使用辣么大

// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;

private string label;

// Expression-bodied get / set accessors.
public string Label
{
    get => label;
    set => this.label = value ?? "Default label";
}

在表达式扔异常

以前,异常是定义,不可以放在表达式,所以下面代码出错

private string _name;

        public string Name
        {
            set
            {
                _name = value??throw new ArgumentException();
                
            }
            get { return Name; }
        }

不能通过判断 value 是空,抛出异常,可以看到我的代码红了

现在可以把异常放在表达式,下面代码在vs17可以运行

同时可以写在字段定义

private ConfigResource loadedConfig = LoadConfigResourceOrDefault() ?? 
    throw new InvalidOperationException("Could not load config");

广义异步返回类型

以前 Task<> 只能在方法使用,必须返回引用类型,也会出现新建线程或构造的性能问题,请看下面的代码,虽然已经有了cache,但是还是需要进入新的task。

private async Task<int> loadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cache = true;
    cacheResult = 100;
    return cacheResult;
}

现在可以使用 ValueTask<> 返回数值,减少构造Task的性能问题,在ValueTask可以判断是否已经缓存,如果有就直接返回,这个的主要用法是减少task的使用,但是不修改方法,还是异步。

public ValueTask<int> CachedFunc()
{
    return (cache) ? new ValueTask<int>(cacheResult) : new ValueTask<int>(loadCache());
}
private bool cache = false;
private int cacheResult;
private async Task<int> loadCache()
{
    // simulate async work:
    await Task.Delay(100);
    cache = true;
    cacheResult = 100;
    return cacheResult;
}

注意使用 System.Threading.Tasks.Extension

这个方法可以直接把数值转 ValueTask

虽然没有用,和之前的看不出有什么用,但是这个一个很大的工程。

public static async ValueTask<int> ValueTask(int[] numbers)
        {
            if (!numbers.Any())
            {
                return 0;
            }
            else
            {
                return await Task.Run(() => numbers.Sum());
            }
        }

上面代码其实无法体现出实际用处,在 http://www.cnblogs.com/GuZhenYin/p/6526041.html 博客, 笑对当空 说 这样可以在高频调用异步时不再花费更多的线程重复运行结果

https://docs.microsoft.com/en-us/dotnet/articles/csharp/csharp-7

https://blogs.msdn.microsoft.com/dotnet/2016/08/24/whats-new-in-csharp-7-0/

VS 17 下载

VS 17 企业版

链接:http://pan.baidu.com/s/1skXDc3z 密码:70d6

秘钥 Njvyc-bmhx2-g77mm-4xjmr-6q8qf

如果度盘链接没法使用,请联系我。

btsync:BTZR4YIPCLUUEL2BKDACVGLC3473MEWDN

代码 https://github.com/alugili/CSharp7Features

如果自己想下载离线包,可以到官网去下载一个应用,也就是在线安装版本。

然后使用 cmd 打开,假如我下载的是 vs_community.exe,那么我想安装语言为中文的包,包括常用开发,可以使用下面代码

vs_community.exe --layout E:\vs2017离线  --lang zh-CN  --add Component.GitHub.VisualStudio  --add Microsoft.VisualStudio.Component.CoreEditor --add Microsoft.VisualStudio.Workload.ManagedDesktop

需要解释上面的代码

vs_community.exe --layout 下载地址 --lang 语言,如果有多个,使用空格  --add 添加工作空间  --add Microsoft.VisualStudio.Component.CoreEditor --add Microsoft.VisualStudio.Workload.ManagedDesktop

语言参见:https://docs.microsoft.com/en-us/visualstudio/install/use-command-line-parameters-to-install-visual-studio#list-of-language-locales

工作空间可以到 https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids 查看,对不同的vs,需要使用不同的工作空间

Install on low bandwidth or unreliable network environments

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。