虽然自C#最初公布以来已超越15年了,但语言并没有那末古老。此中一个原因是它定期更新。
每隔两三年,就会公布一个带有其他语言功用的新版本。自2017年终公布C#7.0以来,使用次要语言版本进一步提高了节拍。
在一年的时间内,公布了三种新的次要语言版本(C#7.1,7.2和7.3)。
阅读更多C#7.1,7.2和7.3 - 新功用(更新)。
假如我们要查验 反省2002年为C#1.0编写的代码,它看起来与我们今日编写的代码有很大的不同。
大少数差别都源于使用那时不存在的语言构造。但是,随着语言开发,.NET框架中还添加了新的类,这些类应用了新的语言功用。所有这一切使得今日的C#更具暗示力和加倍敏锐。
让我们一路回忆历史,概述C#的主要版本。
关于每一个版本,我们将查验 反省最主要的更改,并将可以在其公布后编写的代码与之前必需编写的代码举行比拟。当我们到达C#1.0时,我们几乎无法将代码识别为C#。
C#版本和进化
C#7.0
在撰写本文时,最新的主要语言版本是7.0。随着它在2017年公布,它仍然是比来的,因此它的新功用不常常使用。
我们大少数人仍然习惯于编写C#代码而没有它带来的优势。
C#7.0的主题是形式匹配,它增多了对switch语句中查验 反省类型的同意:
switch (weapon)
{
case Sword sword when sword.Durability > 0:
enemy.Health -= sword.Damage;
sword.Durability--;
break;
case Bow bow when bow.Arrows > 0:
enemy.Health -= bow.Damage;
bow.Arrows--;
break;
}
在上面的紧凑代码中使用了多种新语言功用:
- 该状况说明查验 反省的值的类型兵器的变量。
- 在同一个语句中,我声明了一个匹配类型的新变量,可以在响应的代码块中使用。
- when要害字后面的语句的最后一局部指定了进一步限制代码履行的附加条件。
别的,is运算符扩展了形式匹配同意,因此它此刻可用于声明一个相似于case语句的新变量:
if (weapon is Sword sword)
{
// code with new sword variable in scope
}
在没有所有这些功用的语言的初期版本中,等效的代码块会更长。
if (weapon is Sword)
{
var sword = weapon as Sword;
if (sword.Durability > 0)
{
enemy.Health -= sword.Damage;
sword.Durability--;
}
}
else if (weapon is Bow)
{
var bow = weapon as Bow;
if (bow.Arrows > 0)
{
enemy.Health -= bow.Damage;
bow.Arrows--;
}
}
C#7.0中还添加了其他一些小功用。我们只会提到此中两个:
(i)Out变量容许在初次使用变量的中央声明变量作为办法的out参数。
if (dictionary.TryGetValue(key, out var value))
{
return value;
}
else
{
return null;
}
在添加此功用之前,我们必需提早声明值变量:
string value;
if (dictionary.TryGetValue(key, out value))
{
return value;
}
else
{
return null;
}
(ii)元组可用于依据需要即时将多个变量分组为单个值,例如,办法的返回值:
public (int weight, int count) Stocktake(IEnumerable
{
return (weapons.Sum(weapon => weapon.Weight), weapons.Count());
}
没有它们,即使我们只在一个中央需要它,我们也必需声明一种新的类型:
public Inventory Stocktake(IEnumerable
{
return new Inventory
{
Weight = weapons.Sum(weapon => weapon.Weight),
Count = weapons.Count()
};
}
要了解有关C#7.0新功用的更多信息,请查验 反省我的文章C#7 -上一版DNC杂志的新功用。要了解有关C#7次要版本的更多信息,请查验 反省我的文章C#7.1,7.2和7.3 - DNC杂志中的新功用(更新)
C#6.0
C#6.0于2015年公布。它恰恰完全重写了代号为Roslyn的编译器。此版本的一个主要局部是编译器效劳,从那时起,它在Visual Studio和其他编纂器中到手普及使用:
- Visual Studio 2015和2017用于语法突出显示,代码导航,重构和其他代码编纂功用。
- 许多其他编纂器,如Visual Studio Code,Sublime Text,Emacs等,在OmniSharp的辅佐下提供了相似的功用,OmniSharp是一套独立的C#东西,旨在集成到代码编纂器中。
- 许多第三方静态代码分析器使用语言效劳作为其基础。这些可以在Visual Studio中使用,也可以在构建进程中使用。
要了解有关Roslyn和编译器效劳的更多信息,请查验 反省我的文章.NET Compiler Platform(aka Roslyn) -DotNetCurry(DNC)杂志中的概述
语言只有一些变化。它们主要是语法糖,但此中许多仍然有用,足以在今日常常使用:
- Dictionary initializer可用于设置字典的初始值:
var dictionary = new Dictionaryint, string>
{
[1] = "One",
[2] = "Two",
[3] = "Three",
[4] = "Four",
[5] = "Five"
};
没有它,必需使用调集初始化递次:
var dictionary = new Dictionaryint, string>()
{
{ 1, "One" },
{ 2, "Two" },
{ 3, "Three" },
{ 4, "Four" },
{ 5, "Five" }
};
- nameof运算符返回符号的称号:
public void Method(string input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
// method implementation
}
这关于避免在代码中使用字符串十分有用,当重定名符号时,这些代码很随便掉往同步:
public void Method(string input)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
// method implementation
}
- Null条件运算符缩小了查验 反省空值的典礼:
var length = input?.Length ?? 0;
假如没有我,不只要要更多的代码来实现不异的目的,我们更有可以忘记完全添加这样的查验 反省:
int length;
if (input == null)
{
length =0;
}
else
{
length = input.Length;
}
- 静态导进容许干脆调用静态办法:
using static System.Math;
var sqrt = Sqrt(input);
在它介绍之前,有需要始终引用它的静态类:
var sqrt = Math.Sqrt(input);
- 字符串插值简化字符串格式:
var output = $"Length of '{input}' is {input.Length} characters.";
它不只避免了对String.Format的调用,并且还使格式化形式更容易于阅读:
var output = String.Format("Length of '{0}' is {1} characters.", input, input.Length);
更不必说在形式之外使用格式化形式参数会使它更有可以以毛病 过错的递次列出它们。
要了解有关C#6的更多信息,您可以阅读我的文章在DotNetCurry(DNC)杂志中将现有C#代码升级到C#6.0。
C#5.0
Microsoft在2012年公布了C#5.0,并引进了一个十分主要的新语言功用:异步伐用的异步/等候语法。
它让所有人都可以拜访异步编程。该功用伴同着.NET Framework 4.5顶用于输进和输出操作的多量新异步办法,该办法同时公布。
使用新语法,异步代码看起来十分相似于同步代码:
public async Taskint> CountWords(string filename)
{
using (var reader = new StreamReader(filename))
{
var text = await reader.ReadToEndAsync();
return text.Split(' ').Length;
}
}
假如您不熟习async和await要害字,请记住,对ReadToEndAsync办法的I / O调用长短堵塞的。该的await要害字开释线程其他任务,直到文件的读取异步完成。只有这样,履行才会持续返回同一个线程(大少数时候)。
要了解有关async / await的更多信息,请使用Async Await 查验 反省我的文章C#中的异步编程 - DotNetCurry(DNC)杂志中的最好实践。
假如没有async / await语法,不异的代码将难以编写和了解:
public Taskint> CountWords(string filename)
{
var reader = new StreamReader(filename);
return reader.ReadToEndAsync()
.ContinueWith(task =>
{
reader.Close();
return task.Result.Split(' ').Length;
});
}
请留心,我必需如何使用Task.ContinueWith办法手动编写任务持续。
我也不克不及再使用using语句来封锁流,因为没有await要害字来暂停办法的履行,可以在异步读取完成之前封锁流。
乃至这个代码在C#5.0公布时使用了添加到.NET框架的ReadToEndAsync办法。在此之前,只有该办法的同步版本可用。要在其持续时间内开释调用线程,必需将其包装就业务中:
public Taskint> CountWords(string filename)
{
return Task.Run(() =>
{
using (var reader = new StreamReader(filename))
{
return reader.ReadToEnd().Split(' ').Length;
}
});
}
虽然这容许调用线程(通常是主线程或UI线程)在I / O操作时期履行其他任务,但在此时期仍然阻拦来自线程池的别的一个线程。此代码似乎只是异步,但在其中心仍然是同步的。
要举行真正的异步I / O,需要使用FileStream类中更老,更基本的API :
public Taskint> CountWords(string filename)
{
var fileInfo = new FileInfo(filename);
var stream = new FileStream(filename, FileMode.Open);
var buffer = new byte[fileInfo.Length];
return Task.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, 0, buffer.Length, null)
.ContinueWith(_ =>
{
stream.Close();
return Encoding.UTF8.GetString(buffer).Split(' ').Length;
});
}
只有一个异步办法可用于读取文件,它只容许我们从块中读取文件中的字节,因此我们本身解码文本。
另外,上面的代码一次读取全部文件,这关于大文件不克不及很好地扩展。我们仍在使用FromAsync辅佐办法,该办法仅在.NET框架4中引进,同时还包孕Task类本身。在此之前,我们不克不及不在代码中的任何中央干脆使用异步编程模型(APM)形式,必需为每一个异步操作调用BeginOperation和EndOperation办法对。
难怪,在C#5.0之前极少使用异步I / O.
C#4.0
2010年,C#4.0公布。
它专一于动态绑定,使COM与动态语言的互操作性加倍复杂。由于Microsoft Office和许多其他大型应用递次此刻可以通过干脆使用.NET框架举行扩展,而不依靠于COM互操作性,因此我们认为当今大少数C#代码中极少使用动态绑定。
要了解有关动态绑定的更多信息,请参阅DNC杂志中的C#中的动态绑定文章。
尽管如此,还是有一个主要的功用同时被添加,它成为语言的一个组成局部,并且在今日常常被使用,而没有给它任何特殊的想法:可选和定名参数。它们是编写同一函数的许多重载的一个很好的替代办法:
public void Write(string text, bool centered = false, bool bold = false)
{
// output text
}
可以通过提供可选参数的任意组合来调用此单个办法:
Write("Sample text");
Write("Sample text", true);
Write("Sample text", false, true);
Write("Sample text", bold: true);
我们不克不及不在C#4.0之前编写三个不同的重载来接近这个:
public void Write(string text, bool centered, bool bold)
{
// output text
}
public void Write(string text, bool centered)
{
Write(text, centered, false);
}
public void Write(string text)
{
Write(text, false);
}
即使如此,此代码仅同意原始示例中的前三个调用。假如没有定名参数,我们必需创建一个具有不同称号的附加办法以同意最后的参数组合,即仅指定文本和粗体参数,但保存居中参数的默许值:
public void WriteBold(string text, bool bold)
{
Write(text, false, bold);
}
C#3.0
2007年的C#3.0是语言展开的别的一个主要里程碑。它引进的功用都围绕着使LINQ(语言集成查询)成为可以:
- 扩展办法似乎被称为类型的成员,尽管它们在别处定义。
- Lambda表达式为匿名办法提供了更短的语法。
- 匿名类型是ad-hoc类型,不必事先定义。
所有这些都有助于我们今日习惯的LINQ办法语法:
var minors = persons.Where(person => person.Age
.Select(person => new { person.Name, person.Age })
.ToList();
在C#3.0之前,没有办法在C#中编写这样的声明性代码。必需对功用举行需要的编码:
List
foreach(Person person in persons)
{
if (person.Age > 18)
{
minors.Add(new NameAndAge(person.Name, person.Age));
}
}
请留心我是如何使用完整类型在第一行代码中声明变量的。我们大少数人一直在使用的var 要害字也是在C#3.0中引进的。虽然此代码似乎不比LINQ版本长,但请记住,我们仍需要定义NameAndAge类型:
public class NameAndAge
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
private int age;
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public NameAndAge(string name, int age)
{
Name = name;
Age = age;
}
}
由于C#3.0中添加了别的两个特性,因此类代码比我们习惯的要冗长很多:
- 假如没有主动实现的属性,我必需手动声明同意字段,和每一个属性的一般getter和setter。
- 用于设置属性值的构造函数是必需的,因为在C#3.0之前没有初始化器语法。
C#2.0
我们已走到了2005年C#2.0公布的那一年。许多人认为这是该语言的第一个版本,足以在现实项目中使用。它引进了许多我们今日不克不及保存的功用,但此中最主要和最具影响力的功用之一固然是对泛型的同意。
我们没有人能想象没有泛型的C#。我们今日仍在使用的所有馆躲都是通用的:
Listint> numbers = new Listint>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
int sum = 0;
for (int i = 0; i
{
sum += numbers[i];
}
没有泛型,.NET框架中没有强类型调集。而不是上面的代码,我们坚持以下:
ArrayList numbers = new ArrayList();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
int sum = 0;
for (int i = 0; i
{
sum += (int)numbers[i];
}
虽然代码可以看起来很相似,但是有一个主要的区别:这段代码不是类型安全的。我可以轻松地将其他类型的值添加到调集中,而不只仅是int。别的,请留心我在使用之前如何转换从调集中检索的值。我必需这样做,因为它在调集中被键进为对象。
固然,这样的代码十分随便犯错。幸运的是,假如我想要类型安全,还有别的一种解决方案可用。我可以创建本身的类型调集:
public class IntList : CollectionBase
{
public int this[int index]
{
get
{
return (int)List[index];
}
set
{
List[index] = value;
}
}
public int Add(int value)
{
return List.Add(value);
}
public int IndexOf(int value)
{
return List.IndexOf(value);
}
public void Insert(int index, int value)
{
List.Insert(index, value);
}
public void Remove(int value)
{
List.Remove(value);
}
public bool Contains(int value)
{
return List.Contains(value);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(System.Int32))
{
throw new ArgumentException("Value must be of type Int32.", "value");
}
}
}
使用它的代码仍然相似,但它最少是类型安全的,就像我们习惯于泛型一样:
IntList numbers = new IntList();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);
int sum = 0;
for (int i = 0; i
{
sum += numbers[i];
}
但是,IntList调集只能用于存储int。假如我想存储不同类型的值,我必需实现一个不同的强类型调集。
并且生成的代码关于值类型的性能仍然明显较差,因为它们在存储到调集中之前被装箱到对象,并且在检索时被作废装箱。
如今没有其他许多功用,假如没有在C#2.0之前实现,我们就无法实现:
- 可以为值的值类型,
- 迭代器,
- 匿名办法
- ...
结论:
自1.0版以来,C#一直是.NET开发的主要语言,但从那时起它已走过了漫长的路途。由于版本中添加了它的功用,它与编程世界的新趋向坚持同步,并且仍然是同时出现的新语言的一个很好的替代品。
有时,它乃至设法开始一个新的趋向,就像它使用async和await要害字一样,后来被其他语言采用。由于同意可空引用类型和C#8.0承诺的许多其他新功用,因此不必担心语言会停止展开。