第1章 C#概述

C#和.NET Framework简介


       C#是一种通用的,类型安全的面向对象编程语言,它基于C风格语言(C、C++和JAVA)。其目标是提高程序员的生产力,为此,需要在简单性、表达性和性能之间进行权衡。C#语言的首席架构师Anders Hejlsberg随该语言的第一个版本一直走到了今天(他也是Turbo Pascal的发明者和Delphi的架构师)。C#语言和平台无关,且可以与诸多平台下的编译器和框架(尤其是Windows下的Microsoft .NET Framework)协同工作。

 

1.1 面向对象

        C#实现了面向对象编程的广泛特性,包括了(封装、继承、多态)。封装表示在对象周围创建一个边界,将他的外部(公开)行为与内部(私有)实现细节隔离。

       C#在面向对象方面的特性包括:


     1、统一的类型系统

          C#中的基础构建块是一种被称为类型的数据与函数的封装单元。C#有一个统一的类型系统,其中所有的类型最终共享一个公共的基类。这意味着所有的类型,不管他们是表示业务对象,还是数字等的基本类型,都共享相同的基本功能集。例如:任何类型都可以通过调用他的ToString方法转换为一个字符串。


    2、类与接口

         在纯粹的面向对象泛型中,唯一的类型就是类。但是C#中还有其他几种类型,其中一种就是接口(类似Java中的接口)。接口与类相似,但是他只有某种类型的定义,而不是实现它。在需要使用多继承时,他是非常有用的(与C++和Eiffel等语言不同,C#不支持类得多继承(单根性))。


    3、属性、方法和事件

         在纯粹的面对象泛型中,所有的函数都是方法(smalltalk中就是这样)。在C#中方法只是一种函数成员,也包含一些属性和事件以及其他组成部分。属性是封装了一部分对象状态的函数成员,如按钮的颜色和标签的文本。事件是简化了对象状态变化处理的函数成员。

1.2 类型安全性

    C#首先是一种类型安全的语言,这意味着类型的只有通过他们的定义的协议进行交互,从而保证每一个类型内部一致性。例如:C#不允许将字符串类型作为整型处理。

       更具体的说,C#支持静态类型化,这意味着这种语言会在编译时执行静态类型安全性检查的。另外一种是动态类型安全性,.NET CLR(CLR常用简写词语,CLR是公共语言运行库(Common Language Runtime)和Java虚拟机一样也是一个运行时环境,它负责资源管理(内存分配和垃圾收集等),并保证应用和底层操作系统之间必要的分离。CLR存在两种不同的翻译名称:公共语言运行库和公共语言运行时。)在运行时执行动态安全性检查。

       静态类型化能够在程序之前除去大量的错误。他将大量的运行单元测试转移到编译器中,验证程序中所以类型之间都是相互适合的。这样大型程序就更容易管理、更具可预测性和健壮性。而且,静态类型化是一些诸如:Visual Studio和IntelliSence等工具有助于编写程序,因为他知道某个特定变量的类型是什么,因此也知道能够调用哪些方法来处理这个变量。

注:C#允许部分代码通过新的dynamic关键字来动态制定类型。然而,C#在大多数情况下是一种静态类型化的语言。

      C#之所以被称为强类型语言,是因为他的类型规则(以静态或动态的方法执行)是非常严格的。例如:不能够使用一个浮点型参数来调用一个定义是接受的整数函数,除非显示的将这个浮点型数转换为整数。这又助于编码错误。

      强类型也是C#代码能够在沙箱运行的原因之一。沙箱的安全性的所用所有方面都由主机控制的一种环境。在沙箱中,一定注意不能随意忽略一个对象的类型规则从而破坏其状态。

 

1.3 平台支持

    C#一般用来编写运行在Windows平台的的代码。虽然Microsoft通过ECMA(ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通过ECMA-262标准化的脚本程序设计语言。这种语言在万维网上应用广泛,它往往被称为JavaScriptJScript,但实际上后两者是ECMA-262标准的实现和扩展。)实现了C#语言和CLR的标准化,但是专门用来支持非Windows平台C#资源(包括Microsoft的内部或者外部)总量相对较少。这意味着,如果很注重多平台支持,那么诸如Java等语言可能是更明智的选择。因此,C#可以在以下情况用于编写跨平台代码:

1.   C#代码运行在服务器上,生成可运行在任意平台的DHTML。这正是ASP.NET采用的方法。

2.   C#代码运行在一个非Microsoft Common Language Runtime 的运行环境中。最典型的就是Mono项目,它具有自己的C#编译器和运行时环境,可运行在Linux、Solaris、Mac OS X和Windows上。

3.   C#代码运行在一台支持Microsoft Silverlight(支持Windows和MAC OS X)的主机上。这是一种与Adobe Flash Player类似的新技术。

 


 

1.4 内存管理

    C#依靠运行时环境来执行自动的内存管理。CLR有一个垃圾回收器,他是作为程序一部分运行的,负责回收不在被引用的对象所占用的内存。这让程序员不需要显示的释放对象分配的内存,从而避免了诸如C++等语言中 错误使用指针造成的内存问题。C#并没有去除指针:他只是使大多数编程任务不在需要指针。对于性能至关重要的热点和互操作性方面,还是可以使用指针,但是之允许在显示标记为不安全的代码块使用

    


 

1.5 C#与CLR的关系

        c#依赖于一个运行环境,它包括许多特性,如自动管理内存和异常处理。C#的设计与CLR的设计非常接近,CLR提供了这些运行时特性(但c#技术不依赖于CLR)。而且C#的类型系统与CLR的类型系统非常接近(例如:都共享相同的基础类型的定义)。


 

1.6 CLR和.NET Framework

    .NET Framework 由名为Common Language Runtime(CLR)的运行时环境和大量程序库组成的。这些程序库由核心库和应用库组成,应用库依赖于核心库。下面就是这些程序库的可视化概况:

                                                                                                        第1章   C#概述

 

      CLR是执行托管代码的运行环境。C#是几种将源代码编译为托管代码语言之一。托管代码会被打包成程序集,他可以是.exe或者.dll(程序库)的形式,包括了类型信息和元数据。

      托管代码用Intermediate Language 或者IL表示。当CLR加载一个程序集时,他将会将IL转换为该主机的原生代码,如X86.这个转换是通过CLR的JT(JUST-IN-TIME)编译器完成的。程序集几乎保留了所有的源语言的解构,这使得他很容易被检测,也很容易动态的生成代码。

提示:Red Gate 的.NET Reflector是个重要分析程序集内容的工具(你可以将他看作为反编译器使用)。

   CLR是无数运行时的主机。这些服务包括了内存管理、程序加载和安全服务。CLR是与语言无关的。他允许开发人员使用多种语言(例如:C#、Visual Basic、.NET、Managed C++、Delp.NET、Chrome .NET和J#)开发应用程序。.NET Framework 只支持基础所有Windows平台或Web的应用程序的程序库组成。

1.7 C#与Windows Runtime

    C#5.0还实现了Windows runtime(WinRT)库的互操作。WinRT是一个可扩展接口和运行环境,他可以用面向对象和语言无关的方式访问库。win8带有这个运行库,属于Microsoft组件对象模型或COM的扩展版本。(最低win8)

1.8 C#简史

下文将倒序介绍C#各个版本的新特性以方便熟悉旧版本语言的读者。

1.8.1 C# 7.0新特性

一、out输出参数

在以前使用out输出参数的时候,必须先定义变量,然后才能使用,例如:

先定义一个方法,方法参数是out类型的输出参数:

1 private void DoNoting(out int x, out int y)
2 {
3      x = 1;
4      y = 2;
5 }

以前版本的写法:

1 // 必须先定义i、j,才能使用out参数
2 int i = 0;
3 int j = 0;
4 this.DoNoting(out i, out j);
5 Console.WriteLine($"i+j={i+j}");

 在C#7.0中,可以不用先定义,就能够直接使用了:

1 this.DoNoting(out int x, out int y);
2 Console.WriteLine($"x+y={x + y}");
3 this.DoNoting(out var l, out var m);

 结果:

 第1章   C#概述

二、模式

第1章   C#概述
 1 /// <summary>
 2 /// 具有模式的 IS 表达式
 3 /// </summary>
 4 /// <param name="o"></param>
 5 public void PrintStars(object o)
 6 {
 7       if (o is null) return;     // 常量模式 "null"
 8       if (!(o is int i)) return; // 类型模式 定义了一个变量 "int i" i的值就是o的值 相当于is类型判断
 9       Console.WriteLine($"i={i}");
10 }
第1章   C#概述

使用方法:

1 this.PrintStars(null);
2 this.PrintStars(3);

 结果:

第1章   C#概述

除了可以像上面那样使用外,还可以使用下面的方式:

第1章   C#概述
 1 private void Switch(string text)
 2 {
 3             int k = 100;
 4             switch (text)
 5             {
 6                 case "Tom" when k > 10:   // text="Tom"且k<10才会输出Tom
 7                     Console.WriteLine("Tom");
 8                     break;
 9                 case "Joe" when text.Length < 10:  //text="Joe"且text的长度<10才会输出Joe
10                     Console.WriteLine("Joe");
11                     break;
12                 case string s when s.Length > 7://模式 定义变量s,s就是text的值
13                     Console.WriteLine(s);
14                     break;
15                 default:
16                     Console.WriteLine("default");
17                     break;
18                 case null:
19                     Console.WriteLine("null");
20                     break;
21             }
22 }
第1章   C#概述

 调用:

1 this.Switch(null);
2 this.Switch("TomTomKevin");
3 this.Switch("JoeForest");

第1章   C#概述

三、元组

先来看下面的两个方法:

第1章   C#概述
 1 /// <summary>
 2 /// 使用默认参数名称
 3 /// </summary>
 4 /// <param name="id"></param>
 5 /// <returns></returns>
 6 private (string, string, string) LookupName(long id) // tuple return type
 7 {
 8       return ("first", "middle", "last");
 9 }
10 
11 /// <summary>
12 /// 不使用默认参数名称
13 /// </summary>
14 /// <param name="id"></param>
15 /// <returns></returns>
16 private (string first, string middle, string last) LookupNameByName(long id) // tuple return type
17 {
18      return ("first", "middle", "last");
19      //return (first: "first", middle: "middle", last: "last");
20 }
第1章   C#概述

 调用:

第1章   C#概述
 1 // 使用默认参数名称:Item1、Item2、Item3
 2 Console.WriteLine($"使用默认参数名称");
 3 var result = this.LookupName(1);
 4 Console.WriteLine(result.Item1);
 5 Console.WriteLine(result.Item2);
 6 Console.WriteLine(result.Item3);
 7 // 不使用默认参数名称
 8 Console.WriteLine($"不使用默认参数名称");
 9 var result2 = this.LookupNameByName(1);
10 Console.WriteLine(result2.first);
11 Console.WriteLine(result2.middle);
12 Console.WriteLine(result2.last);
13 // 也可以使用默认参数名称
14 Console.WriteLine($"也可以使用默认参数名称");
15 Console.WriteLine(result2.Item1);
16 Console.WriteLine(result2.Item2);
17 Console.WriteLine(result2.Item3);
第1章   C#概述

 结果:

第1章   C#概述

四、数字分割

如果数字太长,可以按照一定的位数用“_”进行分割,例如:

1 long big = 100_000_0000;
2 Console.WriteLine($"big={big}");

 第1章   C#概述

 
分类: C#

1.8.1.1 数字字面量的改进

C# 7中,数字字面量可以使用下划线来改善可读性、它们称为数字分隔符而被编译器忽略:
int million = 1_000_000;
二进制字面量可以使用0b前缀进行标识:
var b = 0b1010_1011_1100_1101_1110_1111;

1.8.1.2 输出变量及参数忽略

C# 7中,调用含有out参数的方法将更加容易。首先,可以非常自然地声明输出变量:
bool successful = int.TryParse ("123", out int result);
Console.WriteLine (result);
当调用含有多个out参数的方法时,可以使用下划线字符忽略你并不关心的参数:
SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _);
Console.WriteLine (x);

1.8.1.3 模式

is运算符也可以自然地引入变量了,称为模式变量(请参见3.2.2.5节):
void Foo (object x)
{
if (x is string s)

Console.WriteLine (s.Length);

}
switch语句同样支持模式,因此我们不仅可以选择常量还可以选择类型(请参见2.11.3.5节);可以使用when子句来指定一个判断条件;或是直接选择null:
switch (x)
{
case int i:

Console.WriteLine ("It‘s an int!");
break;

case string s:

Console.WriteLine (s.Length);   // We can use the s variable
break;

case bool b when b == true: // Matches only when b is true

Console.WriteLine ("True");
break;

case null:

Console.WriteLine ("Nothing");
break;

}

1.8.1.4 局部方法

局部方法是声明在其他函数内部的方法(请参见3.1.2.4节):
void WriteCubes()
{
Console.WriteLine (Cube (3));
Console.WriteLine (Cube (4));
Console.WriteLine (Cube (5));

int Cube (int value) => value value value;
}
局部方法仅仅在其包含函数内可见,它们可以像Lambda表达式那样捕获局部变量。

1.8.1.5 更多的表达式体成员

C# 6引入了以“胖箭头”语法表示的表达式体的方法、只读属性、运算符以及索引器。而C# 7更将其扩展到了构造函数、读/写属性和终结器中:
public class Person
{
string name;

public Person (string name) => Name = name;

public string Name
{

get => name;
set => name = value ?? "";

}

~Person () => Console.WriteLine ("finalize");
}

1.8.1.6 解构器

C# 7引入了解构器模式。构造器一般接受一系列值(作为参数)并将其赋值给字段,而解构器则正相反,它将字段反向赋值给变量。以下示例为Person类书写了一个解构器(不包含异常处理):
public void Deconstruct (out string firstName, out string lastName)
{
int spacePos = name.IndexOf (‘ ‘);
firstName = name.Substring (0, spacePos);
lastName = name.Substring (spacePos + 1);
}
解构器以特定的语法进行调用:
var joe = new Person ("Joe Bloggs");
var (first, last) = joe; // Deconstruction
Console.WriteLine (first); // Joe
Console.WriteLine (last); // Bloggs

1.8.1.7 元组

也许对于C# 7来说最值得一提的改进当属显式的元组(tuple)支持(请参见4.10节)。元组提供了一种存储一系列相关值的简单方式:
var bob = ("Bob", 23);
Console.WriteLine (bob.Item1); // Bob
Console.WriteLine (bob.Item2); // 23
C#的新元组实质上是使用System.ValueTuple<...>泛型结构的语法糖。多亏了编译器的“魔力”,我们还可以对元组的元素进行命名:
var tuple = (Name:"Bob", Age:23);
Console.WriteLine (tuple.Name); // Bob
Console.WriteLine (tuple.Age); // 23
有了元组,函数再也不必通过一系列out参数来返回多个值了:
static (int row, int column) GetFilePosition() => (3, 10);

static void Main()
{
var pos = GetFilePosition();
Console.WriteLine (pos.row); // 3
Console.WriteLine (pos.column); // 10
}
元组隐式地支持解构模式,因此很容易解构为若干独立的变量。因此,上述Main方法中的GetFilePosition返回的元组将存储于两个局部变量row和column中:
static void Main()
{
(int row, int column) = GetFilePosition(); // Creates 2 local variables
Console.WriteLine (row); // 3 
Console.WriteLine (column); // 10
}

1.8.1.8 throw表达式

在C# 7之前,throw一直是一个语句。现在,它也可以作为表达式出现在表达式体函数中:
public string Foo() => throw new NotImplementedException();
throw表达式也可以出现在三无判断运算符中:
string Capitalize (string value) =>
value == null ? throw new ArgumentException ("value") :
value == "" ? "" :
char.ToUpper (value[0]) + value.Substring (1);

1.8.1.9 其他改进

C#还包含一系列针对特定的场景进行专门的微小优化的功能(请参见2.8.5节和2.8.6节)。同时,我们可以在异步方法声明中包含返回类型而非Task/Task。

1.8.2 C# 6.0新特性

随Visual Studio 2015一起发布的C# 6.0采用了下一代的,完全使用C#编写的编译器,即“Roslyn”项目。新的编译器将一整条编译流水线通过程序库进行开放,使得对各种源代码进行分析成为可能(见第27章)。编译器本身是开源的,可以从github.com/dotnet/roslyn获得其源代码。
此外,C# 6.0为了改善代码的清晰性引入了一系列小而精的改进。
null条件(“Elvis”)运算符(请参见2.10节)可以避免在调用方法或访问类型的成员之前显式地编写用于null判断的语句。在以下示例中,result将会为null而不会抛出NullReferenceException:
System.Text.StringBuilder sb = null;
string result = sb?.ToString(); // result is null
表达式体函数(expression-bodied function)(请参见3.1.2节)可以以Lambda表达式的形式书写仅仅包含一个表达式的方法、属性、运算符以及索引器,使代码更加简短:
public int TimesTwo (int x) => x * 2;
public string SomeProperty => "Property value";
属性初始化器(property initializer,参见第3章)可以对自动属性进行初始赋值:
public DateTime TimeCreated { get; set; } = DateTime.Now;
这种初始化也支持只读属性:
public DateTime TimeCreated { get; } = DateTime.Now;
只读属性也可以在构造器中进行赋值,这令创建不可变(只读)类型变得更加容易了。
索引初始化器(index initializer)(见第4章)可以一次性初始化具有索引器的任意类型:
var dict = new Dictionary()
{
[3] = "three",
[10] = "ten"
};
字符串插值(string interploation)(参见2.6.2节)用更加简单的方式替代了string.Format:
string s = $"It is {DateTime.Now.DayOfWeek} today";
异常过滤器(exception filters)(请参见4.5节)可以在catch块上再添加一个条件:
string html;
try
{
html = new WebClient().DownloadString ("http://asef");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}
using static(参见2.12节)指令可以引入一个类型的所有静态成员,这样就可以不用书写类型而直接使用这些成员:
using static System.Console;
...
WriteLine ("Hello, world"); // WriteLine instead of Console.WriteLine
nameof(见第3章)运算符返回变量、类型或者其他符号的名称。这样在Visual Studio中就可以避免变量重命名造成不一致的代码:
int capacity = 123;
string x = nameof (capacity); // x is "capacity"
string y = nameof (Uri.Host); // y is "Host"
最后值得一提的是,C# 6.0可以在catch和finally块中使用await。

1.8.3 C# 5.0新特性

C# 5.0最大的新特性是通过两个关键字,async和await,支持异步功能(asynchronous function)。异步功能支持异步延续(asynchronous continuation),从而简化响应式和线程安全的富客户端应用程序的编写。它还有利于编写高并发和高效的I/O密集型应用程序,而不需要为每一个操作绑定一个线程资源。
第14章将详细介绍异步功能。

1.8.4 C# 4.0新特性

C# 4.0增加的新特性有:
动态绑定
可选参数和命名参数
用泛型接口和委托实现类型变化
改进COM互操作性
动态绑定(参见第4章和第20章)将绑定过程(解析类型与成员的过程)从编译时推迟到运行时。这种方法适用于一些需要避免使用复杂反射代码的场合。动态绑定还适合于实现动态语言以及COM组件的互操作。
可选参数(见第2章)允许函数指定参数的默认值,这样调用者就可以省略一些参数,而命名参数则允许函数的调用者按名字而非按位置指定参数。
类型变化规则在C# 4.0进行了一定程度的放宽(见第3章和第4章),因此泛型接口和泛型委托类型参数可以标记为协变(covariant)或逆变(contravariant),从而支持更加自然的类型转换。
COM互操作性(见第25章)在C# 4.0中进行了三个方面的改进。第一,参数可以通过引用传递,并无须使用ref关键字(特别适用于与可选参数一同使用)。第二,包含COM 互操作(interop)类型的程序集可以链接而无须引用。链接的互操作类型支持类型相等转换,无须使用主互操作程序集(Primary Interop Assembly),并且解决了版本控制和部署的难题。第三,链接的互操作类型中的函数若返回COM变体类型,则会映射为dynamic而不是object,因此无须进行强制类型转换。

1.8.5 C# 3.0新特性

C# 3.0增加的特性主要集中在语言集成查询(Language Integrated Query, LINQ)上。LINQ令C#程序可以直接编写查询并以静态方式检查其正确性。它可以查询本地集合(如列表或XML文档),也可以查询远程数据源(如数据库)。C# 3.0中和LINQ相关的新特性还包括隐式类型局部变量、匿名类型、对象构造器、Lambda表达式、扩展方法、查询表达式和表达式树。
隐式类型局部变量(var关键字,见第2章)允许在声明语句中省略变量类型,然后由编译器推断其类型。这样可以简化代码并支持匿名类型(见第4章)。匿名类型是一些即时创建的类,它们常用于生成LINQ查询的最终输出结果。数组也可以隐式类型化(见第2章)。
对象初始化器(见第3章)允许在调用构造器之后以内联的方式设置属性,从而简化对象的构造过程。对象初始化器不仅支持命名类型也支持匿名类型。
Lambda表达式(见第4章)是由编译器即时创建的微型函数,适用于创建“流畅的”LINQ查询(见第8章)。
扩展方法(见第4章)可以在不修改类型定义的情况下使用新的方法扩展现有类型,使静态方法变得像实例方法一样。LINQ表达式的查询运算符就是使用扩展方法实现的。
查询表达式(见第8章)提供了编写LINQ查询的更高级语法,大大简化了具有多个序列或范围变量的LINQ查询的编写过程。
表达式树(见第8章)是赋值给一种特殊类型Expression的Lambda表达式的DOM(文档对象模型,Document Object Model)模型。表达式树使LINQ查询能够远程执行(例如在数据库服务器上),因为它们可以在运行时进行转换和翻译(例如变成SQL语句)。
C# 3.0还添加了自动化属性和分部方法。
自动化属性(见第3章)对在get/set中对私有字段直接读写的属性进行了简化,并将字段的读写逻辑交给编译器自动生成。分部方法(Partial Method,见第3章)可以令自动生成的分部类(Partial Class)自定义需要手动实现的钩子函数,而该函数可以在没有使用的情况下“消失”。

1.8.6 C# 2.0新特性

C# 2提供的新特性包括泛型(见第3章)、可空类型(nullable type)(见第4章)、迭代器(见第4章)以及匿名方法(Lambda表达式的前身)。这些新特性为C# 3引入LINQ铺平了道路。
C# 2还添加了分部类、静态类以及许多细节功能,例如对命名空间别名、友元程序集和定长缓冲区的支持。
泛型需要在运行时仍然能够确保类型的正确性,因此需要引入新的CLR(CLR 2.0)才能达成该目标。

第1章 C#概述

上一篇:团队作业4——第一次项目冲刺(Alpha版本) Day 2


下一篇:MVC 路由规则