作者:胡飞玲 时间: 2020-10-16
CSharp 语言规范 GitHub 库参见:https://github.com/dotnet/csharplang
CSharp 语言路线图及开发中的特性参见: [https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md](https://github.com/dotnet/roslyn/blob/master/docs/Language Feature Status.md)
*旧版:*我们使用 out 变量的时候,需要在外部先申明,然后才能传入方法,类似如下:
string message = "";
bool result = AddData(out message);
新版:
bool result = AddData(out string message);
用途:更优雅地返回多个值
适用一个函数多个返回值,需要通过 nuget 引用 System.ValueTuple 示例如下:
private void TuplesExample()
{
//1、var匿名来获取
var data = GetData();
Console.WriteLine(data.result);
Console.WriteLine(data.data.Count);
Console.WriteLine(data.message);
//2、var匿名来获取
var (result1,data1,message1) = GetData();
// 3、不用var匿名来获取
(bool result2, ArrayList data2, string message2) = GetData();
}
//方法定义为多个返回值,并命名
private (bool result, ArrayList data, string message) GetData()
{
bool result = true;
string info = "成功";
ArrayList pList = new ArrayList();
return (result, pList, info);
}
需要通过 nuget 引用 System.ValueTuple
使用方法: 条件控制语句(obj is type variable){}
private int PatternMatchingExample(List<object> values)
{
int sum = 0;
//is 表达式
values.ForEach(obj => {
if (obj is short num)
sum += num;
else if (obj is string str && int.TryParse(str, out int result))
sum += result;
else if (obj is List<object> list)
sum += PatternMatchingExample(list);
});
}
使用方法:case type variable:
private int PatternMatchingExample(List<object> values)
{
int sum = 0;
values.ForEach(obj =>
{
switch (obj)
{
case short num:
sum += num;
break;
case string str when int.TryParse(str, out int result):
sum += result;
break;
case List<object> list:
sum += PatternMatchingExample(list);
break;
}
});
return sum;
}
不必担心值类型的引用传递:CSharp7.0 以前的 ref 只能用于函数参数,现在可以用于本地引用和作为引用返回
用法:
需要添加关键字 ref,定义引用时需要,返回引用时也需要
引用声明和初始化必须在一起,不能拆分
引用一旦声明,就不可修改,不可重新再定向
函数无法返回超越其作用域的引用
private void RefLocalExample()
{
int[] a = { 0, 1, 2, 3, 4, 5 };
// x不是一个引用,函数将值赋值给左侧变量x
int x = GetLast(a);
Console.WriteLine($"x:{x}, a[2]:{a[a.Length - 1]}");
x = 99;
Console.WriteLine($"x:{x}, a[2]:{a[a.Length - 1]} \n");
// 返回引用,需要使用ref关键字,y是一个引用,指向a[a.Lenght-1]
ref int y = ref GetLast(a);
Console.WriteLine($"y:{y}, a[2]:{a[a.Length - 1]}");
y = 100;
Console.WriteLine($"y:{y}, a[2]:{a[a.Length - 1]}");
}
public static ref int GetLast(int[] a)
{
int number = 18;
// 错误声明: 引用申明和初始化分开是错误的
//ref int n1;
//n1 = number;
// 正确声明: 申明引用时必须初始化,声明和初始化在一起
// 添加关键字ref表示n1是一个引用,
ref int n1 = ref number;
// n1指向number,不论修改n1或number,对双方都有影响,相当于双方绑定了。
n1 = 19;
Console.WriteLine($"n1:{n1}, number:{number}");
number = 20;
Console.WriteLine($"n1:{n1}, number:{number}");
// 语法错误,引用不可被重定向到另一个引用
//n1 = ref a[2];
// 语法正确,但本质是将a[2]的值赋值给n1引用所指,n1仍指向number
n1 = a[2];
Console.WriteLine($"n1:{n1}, number:{number}, a[2]:{a[2]}");
number = 21;
Console.WriteLine($"n1:{n1}, number:{number}, a[2]:{a[2]}");
// 错误:n1引用number,但number生存期限于方法内,故不可返回
// return ref n1;
// 正确:n2引用a[2],a[2]生存期不仅仅限于方法内,所以可以返回。
ref int n2 = ref a[a.Length - 1];
return ref n2; // 需要ref返回一个引用
//return ref a[a.Length - 1]; // 也可以直接返回一个引用
}
函数作用域新玩法:函数里定义函数了,通常这个函数里的函数只能在外层函数里访问
private void LocalFunctionsExample()
{
int num = OperData(100);
int OperData(int data)
{
return data * 10;
}
}
// CSharp7.0
// 1. 类成员方法体是一句话的,都可以改成使用表达式,使用 Lambda 箭头来表达
// 2. CSharp6.0 中,这种写法只适用于只读属性和方法
// 3. CSharp7.0 中,这种写法可以用于更多类成员,包括构造函数、析构函数、属性访问器等
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public void SetAge(int age) => Age = age; //方法
public string password;
public string Password
{ //属性访问器
get => password;
set => password = value;
}
public void Init(string name, string password, int age)
{
Name = name;
Password = password;
Age = age;
}
public User(string name) => Init(name, "", 18); //构造函数
public User(string name, string password) => Init(name, password, 18);
}
新增的运算符??(两个半角问号)用于在引用为 null 的时候抛出异常:
// 1. CSharp7.0 以前,throw 是一个语句,因为是语句,所以在某些场合不能使用。
// 包括如:条件表达式(? :)、null 合并运算符(??)、一些 Lambda
// 2. CSharp7.0 可以使用了, 语法不变,但是可以放置在更多的位置
private string name;
public string Name
{
get => name;
set => name = value ??
throw new ArgumentNullException("Name must not be null");
}
// CSharp7.0
// 1. 7.0 以前异步方法只能返回 void、Task、Task<T>,现在允许定义其他类型来返回
// 2. 主要使用情景:从异步方法返回 Task 引用,需要分配对象,某些情况下会导致性能问题。
// 遇到对性能敏感问题的时候,可以考虑使用 ValueTask<T>替换 Task<T>。
public class CaCheContext
{
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()
{
await Task.Delay(5000);
cache = true;
cacheResult = 100;
return cacheResult;
}
}
private void NumericExample()
{
// CSharp7.0
// CSharp7.0增加了两个新特性:二进制字面量(ob开头)、数字分隔符(_)
int b = 123_456_789; // 作为千单位分隔符
float e = 3.1415_926f; // 其他包括float、double、decimal同样可以使用
long h = 0xff_42_90_b8; // 在十六进制中使用
int c = 0b10101010; // 增加了表示二进制的字面量, 以0b开头
int d = 0b1011_1010; // 二进制字面量里加入数字分割符号_
}
public class Person
{
public string Name { get; set; } = "张三";
public int Age { get; set; } = 20;
}
以前版本,字符串格式化用要使用 string.Format,在 CSharp6.0 中,可以直接使用$进行字符串的嵌入
private void StringFormatExample()
{
//旧版
Console.WriteLine(string.Format("当前时间:{0}", DateTime.Now.ToString()));
//新特性
Console.WriteLine($"当前时间:{DateTime.Now.ToString()}");
}
1)使用 using 导入静态类
2)导入静态类以后,可以像使用普通方法一样,直接使用 如:
private void ImportStaticExample()
{
Console.WriteLine($"文件是否存在:{Exists("c:\test.txt")}");
}
在 CSharp6.0 中有了空值运算符 ? ,对象为空时,访问属性也不出现“未将对象引用设置到对象实例”
private void NullExample()
{
Person person = null;
string name = person ?.Name;
}
在 CSharp6.0 中,可以通过索引的方式给字段进行初始化
private void InitExample()
{
IDictionary<string, string> dictNew = new Dictionary<string, string>()
{
["一"] = "first",
["二"] = "second"
};
}
CSharp6.0 中,可以设置在满足某种条件的情况下,才能进入异常捕获:
private void ExceptionExample()
{
int num = 1;
try
{
while (true)
num += 1;
}
catch(Exception ex) when(num>10)
{
Console.WriteLine("num >10 ");
}
}
类似 typeof,应用场景:如打印日志,或反射时通过属性名称获取对应值等等
private void NameofExample()
{
string personName = "李四";
Console.WriteLine($"{nameof(personName)}:{personName}");
//打印结果:personName:李四
}
comboBox1.Text :=: this.User.Name;
这个的加入给一些设计增加了强大功能,泛型早在 CSharp2.0 加入后就有着强大的应用,一般稍微设计比较好的框架,都会用到泛型,CSharp5.0 加入带参数泛型构造函数,则在原有基础上对 CSharp 泛型完善了很多。
int? x = null;
int? y = x + 40;
Console.WriteLine(y);//结果:40,不会报错
通过 async 和 await 两个关键字,引入了一种新的基于任务的异步编程模型(TAP)。在这种方式下,可以通过类似同步方式编写异步代码,极大简化了异步编程模型
private async void AsyncExample()
{
var webClient = new WebClient();
var result = await webClient.DownloadStringTaskAsync("www.baidu.com");
Console.WriteLine(result);
}
private void ObjectInitExample()
{
dynamic dyn = 1.234;
Console.WriteLine(dyn.GetType());
dyn = "ojlovecd";
Console.WriteLine(dyn.GetType());
//在运行时dyn会知道自己是个什么类型。
//这里的缺点在于编译的时候无法检查方法的合法性,写错的话就会出运行时错误。
dyn = new User { Name = "", Age = 20 };
Console.WriteLine(dyn.Name);
}
private void OptionalArgumentsExample()
{
//可选参数
OperData("测试");
OperData("测试",100);
//命名参数让我们可以在调用方法时指定参数名字来给参数赋值,可以忽略参数的顺序
OperData(data: 100, message: "测试");
}
private void OperData(string message,int data=10)
{
Console.WriteLine(data);
}
System.Numerics.Complex 复数
System.Numerics.BigInteger 大数
System.Tuple 对象
Directory.EnumerateDirectories
Directory.EnumerateFiles
Directory.EnumerateFileSystemEntries
String.IsNullOrWhiteSpace()
Linq 主要包含 4 个组件——Linq to Objects、Linq to XML、Linq to DataSet 和 Linq to SQL,这 4 个组件对应 4 种查询的对象:内存集合中的对象,XML,SQL 和 Dataset
User user = new User { Name = "AA", Age = 22 };
List<User> userList = new List<User>{
new User{Name="AA",Age=22},
new User{Name="BB",Age=25},
};
public Class Point
{
public int X { get; set; }
public int Y { get; set; }
}
//使用一个临时的对象,不用定义
//往往这样的对象只需要使用在局部的场合,使用以后就不再使用了,
//例如从数据库中查询出来的临时结果
private void AnonymousExample()
{
//创建的people的Name和Age是只读的,不能去修改
var people = new { Name = "AA", Age = 10 };
Console.WriteLine(people.Name);
}
这个其实就是扩展方法的运用,编译器提供了相关的语法便利,下面两端代码是等价的:
from g in
from c in customers
group c by c.Country
select new { Country = g.Key, CustCount = g.Count() }
customers.
GroupBy(c => c.Country).
Select(g => new { Country = g.Key, CustCount = g.Count() })