c#入门初探
零. 写在前面
0.1 解决方案、项目、程序集、命名空间
0.1.1项目
- 一个项目可以就是你开发的一个软件。
- 在.Net下,一个项目可以表现为多种类型,如控制台应用程序,Windows应用程序,类库(Class Library),Web应用程序,Web Service,Windows控件等等。
- 如果经过编译,从扩展名来看,应用程序都会被编译为.exe文件,而其余的会被编译为.dll文件。
- .exe文件,就表明它是可以被执行的,表现在程序中,这些应用程序都有一个主程序入口点,即方法Main()。
- 而类库,Windows控件等,则没有这个入口点,所以也不能直接执行,而仅提供一些功能,给其他项目调用。
创建项目
- 依次执行File-new-Project来创建一个新的项目
- 例如创建控制台应用程序。注意在此时,Visual Studio除了建立了一个控制台项目之外,该项目同时还属于一个解决方案(Solution)
- 一个项目可以表现为多种类型:控制台应用程序、类库、web应用程序等
0.1.2 解决方案
- 解决方案其实是一个容器,其下边可以包含多个项目。
- 如果你只需要开发一个Hello World的项目,解决方案自然毫无用处。
- 但是,一个稍微复杂一点的软件,都需要很多模块来组成,为了体现彼此之间的层次关系,利于程序的复用,往往需要多个项目,每个项目实现不同的功能,最后将这些项目组合起来,就形成了一个完整的解决方案。
- 形象地说,解决方案就是一个容器,在这个容器里,分成好多层,好多格,用来存放不同的项目。
- 一个解决方案与项目是大于等于的关系。建立解决方案后,会建立一个扩展名为.sln的文件。
- 在解决方案里添加项目,不能再用“new”的方法,而是要在“File”菜单中,选择“Add Project”。添加的项目,可以是新项目,也可以是已经存在的项目。
0.1.3 程序集Assembly
- 一个项目也就是一个程序集,也可以看成是一个完整的模块(Module),或者称为是包(Package)
- 因此,一个程序集也可以体现为一个dll文件,或者exe文件。
- c#的类库就是dll形式的程序集。
- 程序集还包括可执行的托管应用程序(窗体程序和控制台程序)
- 类库和可执行的程序集之间的最大区别就是类库没有入口而可执行程序集必须有入口。
- 程序集可以由单一的文件组成,也可以多文件组成。
组成程序集的四元素
- 程序集清单 元数据集合
- 类型元数据 买书程序集中类型的数据
- msil码
- 资源集
0.1.4 命名空间(finish)
- 引入它,主要是为了避免一个项目中,可能会存在的相同对象名的冲突
- 在项目里我们也可以定义很多不相同的命名空间。但为了用户便于使用,最好在一个项目中,其命名空间最好是一体的层次结构。
- 在Visual Studio里,我们可以在项目中新建一个文件夹,默认情况下,该文件夹下对象的命名空间,应该是“项目的命名空间.文件夹名”。当然,我们也可以在namespace中修改它
0.2 c#类库
-
类库是相对于控制台应用程序来讲的,区别就是类库没有入口函数,而应用程序有入口函数。
-
除了在项目中把类放在不同的文件中之外,还可以把它们放在完全不同的项目中。如果一个项目什么都不包含,只包含类(以及其他相关的类型定义,但没有入口点),该项目就称为类库。
-
类库就是我们所说的动态链接库(DLL)
-
在C#中,我们可以把我们做的一些类封装成一个类库,然后把类库模糊化处理,就可以共享给别人用了。
0.3 WebHost
0.3.1、始于WebHost
WebHost像母亲,它承载和孕育了ASP.NET Core下几乎所有的对象,从代码层面入手来看的话可以说是ASP.NET Core的入口,没有它,后面的一切都没有办法开始,WebHost的启动一共分为四阶段。
- 准备阶段: var builder = new WebHostBuilder(),然后给WebHostBuilder各种填参数
- 构建阶段: var host = builder.Builder() ,主要负责依懒注入的初始化,以及host的初始化
- 启动阶段: host.start();
- 运行阶段
0.3.2、program.cs
- ASP.NET Core 程序基于一个命令行的程序运行,程序的入口在program.cs的main方法。
public static void Main(string[] args){
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build()
.Run();
}
- WebHost是一个internal类,我相信NETCore团队不希望大家随意的去new它,而是通过WebHostBuilder去构建。
- 在ASP.NET Core1x中,我们必须要自己创建WebHostBuilder。
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
0.3.3 ConfigureAppConfiguration
- 这个方法专门用来为 WebHostBuilder添加配置,包括appsettings.json的、命令行参数以及环境变量。
0.3.4 UseStartup<>
- Startup.cs这个类主要做两件事情的配置Service DI和http管道,这些都是在WebHost启动之前就需要确定下来的。而UseStartup就是将我们定义的Startup.cs和 IStartup绑定起来,让WebHost可以找得到。 怎么绑定呢? 当然还是依赖注入:
- Program.cs文件里面有个main函数,这里是项目开启入口
- Startup.cs文件主要是初始化设置的地方
- program和startup关联的,首先程序进入program里面的main函数,在WebHostBuilder运行run之前会通过UseStartup进入到startup文件的各个方法中,顺序是:Startup(构造器初始化加载一些配置文件)-》ConfigureServices(添加各个服务的依赖关系)-》Configure
一. C# 环境
C# 是 .Net 框架的一部分,且用于编写 .Net 应用程序。下面先了解一下 C# 与 .Net 框架之间的关系。
- .Net 框架应用程序是多平台的应用程序。框架的设计方式使它适用于下列各种语言:C#、C++、Visual Basic、Jscript、COBOL 等等。
C# 的集成开发环境
c#的开发工具有:
- Visual Studio 2010 (VS)
- Visual C# 2010 Express (VCE)
- Visual Web Developer
二. C# 程序结构
一个 C# 程序主要包括以下部分:
- 命名空间声明(Namespace declaration):一个 namespace 是一系列的类
- 一个 class: 类一般包含多个方法
- Class 方法
- Class 属性
- 一个 Main 方法
- 语句(Statements)& 表达式(Expressions)
- 注释
===
以下几点值得注意:
- C# 是大小写敏感的。
- 所有的语句和表达式必须以分号(;)结尾。
- 程序的执行从 Main 方法开始。
- 与 Java 不同的是,文件名可以不同于类的名称。
2.1 调试 & 编译 C# 程序
启动 Visual Studio。
- 在菜单栏上,选择 File -> New -> Project。
- 从模板中选择 Visual C#,然后选择 Windows。
- 选择 Console Application。
- 为您的项目制定一个名称,然后点击 OK 按钮。
- 新项目会出现在解决方案资源管理器(Solution Explorer)中
- 在代码编辑器(Code Editor)中编写代码。
- 点击 Run 按钮或者按下 F5 键来运行程序。会出现一个命令提示符窗口(Command Prompt window),显示 Hello World。
调试快捷键
- F6: 生成解决方案
- Ctrl+F6: 生成当前项目
- F7: 查看代码
- Shift+F7: 查看窗体设计器
- F5: 启动调试
- Ctrl+F5: 开始执行(不调试)
- Shift+F5: 停止调试
- Ctrl+Shift+F5: 重启调试
- F9: 切换断点
- Ctrl+F9: 启用/停止断点
- Ctrl+Shift+F9: 删除全部断点
- F10: 逐过程
- Ctrl+F10: 运行到光标处
- F11: 逐语句
断点调试
设置断点的方法:
- 在要设置断点的代码行旁边的灰色空白中单击;
- 在弹出的快捷菜单中选择“断点”→“插入断点”命令
- 单击要设置断点的代码行,选择菜单中的“调试”→“切换断点(G)”
一种是每次执行一行:F10;
一种是每次执行一行,但遇到函数调用就会跳到被调用的函数里:F11;
一种是直接执行当前函数里剩下的指令,返回上一级函数:Shift+F11。
2.2 注释代码的方法(单行//,多行/**/都适用)
方法1:
- step1: 选中要注释的代码;
- step2: 按Ctrl+k;
- step3: 再按Ctrl+C
方法2:
- 用编辑器右上角‘选中注释行’按钮
2.3 取消注释
方法1
- step1: 选中要注释的代码;
- step2: 按Ctrl+k;
- step3: 再按Ctrl+U;
方法2:
- 用编辑器右上角‘取消选中注释行’按钮
2.4 文档注释///
三. 数据契约
[DataContract]:
服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。
[DataMember]:
来标记被序列化的字段
四. using 关键字
- using 关键字表明程序使用的是给定命名空间中的名称。例如,我们在程序中使用 System 命名空间,其中定义了类 Console。我们可以只写:
Console.WriteLine ("Hello there");
- 我们可以写完全限定名称,如下:
System.Console.WriteLine("Hello there");
五. 命名空间
- 一个 namespace 是一系列的类。HelloWorldApplication 命名空间包含了类 HelloWorld。
- 引入它,主要是为了避免一个项目中,可能会存在的相同对象名的冲突
- 在项目里我们也可以定义很多不相同的命名空间。但为了用户便于使用,最好在一个项目中,其命名空间最好是一体的层次结构。
- 在Visual Studio里,我们可以在项目中新建一个文件夹,默认情况下,该文件夹下对象的命名空间,应该是“项目的命名空间.文件夹名”。当然,我们也可以在namespace中修改它
5.1 定义命名空间
-
命名空间的设计目的是提供一种让一组名称与其他名称分隔开的方式。在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。
-
命名空间的定义是以关键字 namespace 开始,后跟命名空间的名称,如下所示:
namespace namespace_name
{
// 代码声明
}
- 下面的程序演示了命名空间的用法:
using System;
namespace first_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class namespace_cl
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
first_space.namespace_cl fc = new first_space.namespace_cl();
second_space.namespace_cl sc = new second_space.namespace_cl();
fc.func();
sc.func();
Console.ReadKey();
}
}
- 当上面的代码被编译和执行时,它会产生下列结果:
Inside first_space
Inside second_space
在同一个命名空间里的类实例化不需要写命名空间名
- 您也可以使用 using 命名空间指令,这样在使用的时候就不用在前面加上命名空间名称。该指令告诉编译器随后的代码使用了指定命名空间中的名称。下面的代码演示了命名空间的应用。让我们使用 using 指定重写上面的实例:
using System;
using first_space;
using second_space;
namespace first_space
{
class abc
{
public void func()
{
Console.WriteLine("Inside first_space");
}
}
}
namespace second_space
{
class efg
{
public void func()
{
Console.WriteLine("Inside second_space");
}
}
}
class TestClass
{
static void Main(string[] args)
{
abc fc = new abc();
efg sc = new efg();
fc.func();
sc.func();
Console.ReadKey();
}
}
5.2 嵌套命名空间
- 命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间,如下所示:
namespace namespace_name1
{
// 代码声明
namespace namespace_name2
{
// 代码声明
}
}
- 您可以使用点(.)运算符访问嵌套的命名空间的成员,如下所示:
using System;
using SomeNameSpace;
using SomeNameSpace.Nested;
namespace SomeNameSpace
{
public class MyClass
{
static void Main()
{
Console.WriteLine("In SomeNameSpace");
Nested.NestedNameSpaceClass.SayHello();
}
}
// 内嵌命名空间
namespace Nested
{
public class NestedNameSpaceClass
{
public static void SayHello()
{
Console.WriteLine("In Nested");
}
}
}
}
- 当上面的代码被编译和执行时,它会产生下列结果:
In SomeNameSpace
In Nested
5.3 命名空间System.IO.Compression里的方法ZipFile.ExtractToDirectory
5.4 Convert类常用的类型转换方法Convert.ToInt32()
5.5 System.Text命名空间包含与字符串处理和编码相关的类型
5.6 System.Collections.Generic命名空间包含用于处理集合的泛型类型
5.7 System.Linq命名空间,ToList()扩展方法,ToDictionary(),ToArray()
5.8 System.IO里Path类
- 对包含文件或目录路径信息的 String 实例执行操作。 这些操作是以跨平台的方式执行的。
包含的方法:
- Combine(String,String,String):将几个字符组合成一个路径
- HasExtension()方法:确定路径是否包括文件扩展名。
- IsPathRooted()方法:获取一个值,该值指示指定的路径字符串是否包含根。
- GetFullPat()方法:返回指定路径字符串的绝对路径。
- GetTempPath()方法:返回当前用户的临时文件夹的路径。
- GetTempFileName():在磁盘上创建磁唯一命名的零字节的临时文件并返回该文件的完整路径
- GetFileNameWithoutExtension(): 返回不具有扩展名的指定路径字符串的文件名。
5.9 AppContext 类
- 属性:
- BaseDirectory:获取程序集解析程序用于探测程序集的基目录的路径名。
- TargetFrameworkName:获取当前应用程序所针对的框架版本的名称。
- 参数:
- sourceArchiveFileName
Type: System.String
要解压缩存档的路径。 - destinationDirectoryName Type: System.String 放置解压缩文件的目录的路径,指定为相对或绝对路径。 相对路径是指相对于当前工作目录的路径。
- entryNameEncoding Type: System.Text.Encoding
在存档中读取或写入项名时使用的编码。 仅当需要针对具有不支持项名的 UTF-8 编码的 zip 归档工具和库的互操作性进行编码时,为此参数指定一个值。
5.10 Directory类
- 是用于文件夹操作
包含的方法:
- CreateDirectory():创建一个文件夹
- Delete():删除一个文件夹
- Move():移动文件夹的位置
- Exists():判断文件夹是否存在,返回布尔类型
- GetFiles():获取目录下的所有文件的路径,返回到字符串数组
- GetDirectories():获取目录下所有文件夹的路径,返回字符串数组
5.11 ConfigurationBuilder 类
表示将由自定义配置生成器实现扩展的基类。
- 命名空间: System.Configuration
- 程序集: System.Configuration
六. 类(Class)
6.1 类的定义
- 类的定义是以关键字 class 开始,后跟类的名称。类的主体,包含在一对花括号内。
- 变量是类的属性或数据成员,用于存储数据
6.2 成员函数和封装
-
函数是一系列执行指定任务的语句。类的成员函数是在类内声明的。
-
类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。
-
成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量只能使用公共成员函数来访问。
6.3 构造函数
- 类的构造函数是类的一个特殊的成员函数,当创建类的新对象时执行。
构造函数的名称与类的名称完全相同,它没有任何返回类型。
using System;
namespace LineApplication
{
class Line
{
private double length; // 线条的长度
public Line()
{
Console.WriteLine("对象已创建");
}
public void setLength( double len )
{
length = len;
}
public double getLength()
{
return length;
}
static void Main(string[] args)
{
Line line = new Line();
// 设置线条长度
line.setLength(6.0);
Console.WriteLine("线条的长度: {0}", line.getLength());
Console.ReadKey();
}
}
}
结果如下:
对象已创建
线条的长度: 6
6.4 参数化构造函数
- 默认的构造函数没有任何参数。但是如果你需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。这种技术可以帮助你在创建对象的同时给对象
6.5 析构函数
-
类的析构函数是类的一个特殊的成员函数,当类的对象超出范围时执行。
-
析构函数的名称是在类的名称前加上一个波浪****形(~)作为前缀,它不返回值,也不带任何参数。
-
析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
6.6 静态成员
-
我们可以使用 static 关键字把类成员定义为静态的。当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
-
关键字 static 意味着类中只有一个该成员的实例
-
静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取
-
静态变量可在成员函数或类的定义外部进行初始化。你也可以在类的定义内部初始化静态变量。
-
你也可以把一个成员函数声明为 static。
+** 这样的函数只能访问静态变量**。静态函数在对象被创建之前就已经存在.
例如:
using System;
namespace StaticVarApplication
{
class StaticVar
{
public static int num; //这里把类成员num定义为静态的,默认初值为0
public void count()
{
num++;
}
public int getNum()
{
return num;
}
}
class StaticTester
{
static void Main(string[] args)
{
StaticVar s1 = new StaticVar(); //s1,s2是两个对象,但只有一个num的副本
StaticVar s2 = new StaticVar();
s1.count();
s1.count();
s1.count();
s2.count();
s2.count();
s2.count();
Console.WriteLine("s1 的变量 num: {0}", s1.getNum());
Console.WriteLine("s2 的变量 num: {0}", s2.getNum());
Console.ReadKey();
}
}
}
执行结果如下:
s1 的变量 num: 6
s2 的变量 num: 6
-
类成员num定义为静态的,默认初值为0;虽然s1,s2是实例化两个对象,但只有一个num的副本,故调用count()时,无论哪个实例调用,都只有哪个静态num实现自增1.
-
你也可以把一个成员函数声明为static。
-
这样的函数只能访问静态变量。
-
静态函数在对象被创建之前就已经存在。下面的实例演示了静态函数的用法:
-
将类成员函数声明为public static无需实例化即可调用类成员函数
using System;
namespace StaticVarApplication
{
class StaticVar
{
public static int num;
public void count()
{
num++;
}
public static int getNum() //成员函数为public static
{
return num;
}
}
class StaticTester
{
static void Main(string[] args)
{
StaticVar s = new StaticVar();
s.count();
s.count();
s.count();
Console.WriteLine("变量 num: {0}", StaticVar.getNum()); // 将类成员函数声明为public static无需实例化即可调用类成员函数
Console.ReadKey();
}
}
}
结果如下:
变量 num: 3
- 反之,如果不声明为static,即使和Main方法从属于同一个类,也必须经过实例化
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Program s = new Program(); //实例化
int num = s.Add(2, 3); //编译通过
Console.WriteLine(num);
}
public int Add(int x, int y)
{
return x + y;
}
}
}
七. c#方法
- 一个方法是把一些相关的语句组织在一起,用来执行一个任务的语句块。每一个 C# 程序至少有一个带有 Main 方法的类。要使用一个方法,包含定义方法和调用方法:
7.1 定义方法
- 当定义一个方法时,从根本上说是在声明它的结构的元素。在 C# 中,定义方法的语法如下:
<Access Specifier> <Return Type> <Method Name>(Parameter List)
{
Method Body
}
下面是方法的各个元素:
- Access Specifier:访问修饰符,这个决定了变量或方法对于另一个类的可见性。
- Return type:返回类型,一个方法可以返回一个值。返回类型是方法返回的值的数据类型。如果方法不返回任何值,则返回类型为 void。
- Method name:方法名称,是一个唯一的标识符,且是大小写敏感的。它不能与类中声明的其他标识符相同。
- Parameter list:参数列表,使用圆括号括起来,该参数是用来传递和接收方法的数据。参数列表是指方法的参数类型、顺序和数量。参数是可选的,也就是说,一个方法可能不包含参数。
7.2 调用方法
- 您可以使用方法名调用方法。
- 您也可以使用类的实例从另一个类中调用其他类的公有方法。
带public关键字的方法可以使用类的实例从类的外部进行访问
如:方法 FindMax 属于 NumberManipulator 类,您可以从另一个类 Test 中调用它。
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public int FindMax(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
}
class Test
{
static void Main(string[] args)
{
/* 局部变量定义 */
int a = 100;
int b = 200;
int ret;
NumberManipulator n = new NumberManipulator();
//调用 FindMax 方法
ret = n.FindMax(a, b);
Console.WriteLine("最大值是: {0}", ret );
Console.ReadLine();
}
}
}
- 递归方法调用:一个方法可以自我调用。这就是所谓的 递归。下面的实例使用递归函数计算一个数的阶乘:
7.3 参数传递
- 当调用带有参数的方法时,您需要向方法传递参数。在 C# 中,有三种向方法传递参数的方式:
- 值参数: 这种方式复制参数的实际值给函数的形式参数,实参和形参使用的是两个不同内存中的值。在这种情况下,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。
- 这是参数传递的默认方式。在这种方式下,当调用一个方法时,会为每个值参数创建一个新的存储位置。
实际参数的值会复制给形参,实参和形参使用的是两个不同内存中的值。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全
- 引用参数: 这种方式复制参数的内存位置的引用给形式参数。这意味着,当形参的值发生改变时,同时也改变实参的值。
- 引用参数是一个对变量的内存位置的引用。当按引用传递参数时,与值参数不同的是,它不会为这些参数创建一个新的存储位置。引用参数表示与提供给方法的实际参数具有相同的内存位置。ref 关键字声明引用参数
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void swap(ref int x, ref int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 temp 赋值给 y */
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
int b = 200;
Console.WriteLine("在交换之前,a 的值: {0}", a);
Console.WriteLine("在交换之前,b 的值: {0}", b);
/* 调用函数来交换值 */
n.swap(ref a, ref b);
Console.WriteLine("在交换之后,a 的值: {0}", a);
Console.WriteLine("在交换之后,b 的值: {0}", b);
Console.ReadLine();
}
}
}
- 输出参数: 这种方式可以返回多个值。
- return 语句可用于只从函数中返回一个值。但是,可以使用 输出参数 来从函数中返回两个值。输出参数会把方法输出的数据赋给自己,其他方面与引用参数相似。
using System;
namespace CalculatorApplication
{
class NumberManipulator
{
public void getValue(out int x )
{
int temp = 5;
x = temp;
}
static void Main(string[] args)
{
NumberManipulator n = new NumberManipulator();
/* 局部变量定义 */
int a = 100;
Console.WriteLine("在方法调用之前,a 的值: {0}", a);
/* 调用函数来获取值 */
n.getValue(out a);
Console.WriteLine("在方法调用之后,a 的值: {0}", a);
Console.ReadLine();
}
}
}
八. 封装
-
封装:被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。
-
C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。
-
一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:
- public:所有对象都可以访问;
- Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象。任何公有成员可以被外部的类访问。
- private:对象本身在对象内部可以访问;
- Private 访问修饰符允许一个类将其成员变量和成员函数对其他的函数和对象进行隐藏。只有同一个类中的函数可以访问它的私有成员。即使是类的实例也不能访问它的私有成员。
- protected:只有该类对象及其子类对象可以访问
- Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数。这样有助于实现继承。我们将在继承的章节详细讨论这个。更详细地讨论这个。
- internal:同一个程序集的对象可以访问;
- protected internal:访问限于当前程序集或派生自包含类的类型。
九. 可空类型(Nullable)
- C# 提供了一个特殊的数据类型,nullable 类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。
例如:
- Nullable< Int32 >,读作"可空的 Int32",可以被赋值为 -2,147,483,648 到 2,147,483,647 之间的任意值,也可以被赋值为 null 值。
- 类似的,Nullable< bool > 变量可以被赋值为 true 或 false 或 null。
C# 单问号 ? 与 双问号 ??
- ? : 单问号用于对 int,double,bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 NullAble 类型的。
- ?? : 双问号 可用于判断一个变量在为 null 时返回一个指定的值。
接下来我们详细说明。
语法
- 声明一个 nullable 类型(可空类型)的语法如下:
< data_type> ? <variable_name> = null;
Null 合并运算符( ?? )
- Null 合并运算符用于定义可空类型和引用类型的默认值。Null 合并运算符为类型转换定义了一个预设值,以防可空类型的值为 Null。Null 合并运算符把操作数类型隐式转换为另一个可空(或不可空)的值类型的操作数的类型。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
using System;
namespace CalculatorApplication
{
class NullablesAtShow
{
static void Main(string[] args)
{
double? num1 = null;
double? num2 = 3.14157;
double num3;
num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34
Console.WriteLine("num3 的值: {0}", num3);
num3 = num2 ?? 5.34;
Console.WriteLine("num3 的值: {0}", num3);
Console.ReadLine();
}
}
}
输出结果为:
num3 的值: 5.34
num3 的值: 3.14157
十. 数组(Array)
- 数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。
10.1 数组声明
datatype[] arrayName;
其中,
- datatype 用于指定被存储在数组中的元素的类型。
- [ ] 指定数组的秩(维度)。秩指定数组的大小
- arrayName 指定数组的名称。
如:
double[] balance;
10.2 初始化数组
- 一个数组不会在内存中初始化数组。当初始化数组变量时,您可以赋值给数组。
- 数组是一个引用类型,所以您需要使用 new 关键字来创建数组的实例。
例如:
double[] balance = new double[10];
10.3 赋值给数组
- 可以通过使用索引号赋值给一个单独的数组元素
double[] balance = new double[10];
balance[0] = 4500.0;
- 可以在声明数组的同时给数组赋值
double[] balance = { 2340.0, 4523.69, 3421.0};
- 您也可以创建并初始化一个数组
int [] marks = new int[5] { 99, 98, 92, 97, 95};
- 当您创建一个数组时,C# 编译器会根据数组类型隐式初始化每个数组元素为一个默认值。例如,int 数组的所有元素都会被初始化为 0。
10.4 访问数组元素
- 元素是通过带索引的数组名称来访问的。这是通过把元素的索引放置在数组名称后的方括号中来实现的。
使用 foreach 循环
十一. 泛型
- 泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
例 :
using System;
using System.Collections.Generic;
namespace GenericApplication
{
public class MyGenericArray<T>
{
private T[] array;
public MyGenericArray(int size)
{
array = new T[size + 1];
}
public T getItem(int index)
{
return array[index];
}
public void setItem(int index, T value)
{
array[index] = value;
}
}
class Tester
{
static void Main(string[] args)
{
// 声明一个整型数组
MyGenericArray<int> intArray = new MyGenericArray<int>(5);
// 设置值
for (int c = 0; c < 5; c++)
{
intArray.setItem(c, c*5);
}
// 获取值
for (int c = 0; c < 5; c++)
{
Console.Write(intArray.getItem(c) + " ");
}
Console.WriteLine();
// 声明一个字符数组
MyGenericArray<char> charArray = new MyGenericArray<char>(5);
// 设置值
for (int c = 0; c < 5; c++)
{
charArray.setItem(c, (char)(c+97));
}
// 获取值
for (int c = 0; c < 5; c++)
{
Console.Write(charArray.getItem(c) + " ");
}
Console.WriteLine();
Console.ReadKey();
}
}
}
输出结果为:
0 5 10 15 20
a b c d e
11.1 泛型(Generic)的特性
===
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
- 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
- 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 您可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
11.2 泛型(Generic)方法
- 通过类型参数声明泛型方法
十二. List
- List类是ArrayList类的泛型等效类。它的大部分用法都与ArrayList相似,因为List类也继承了IList接口。最关键的区别在于,在声明List集合时,我们同时需要为其声明List集合内数据的对象类型。
12.1 List的基础、常用方法:
- List
mList = new List ();
例:
List<string> mList = new List<string>();
- List
testList =new List (IEnumerable collection);
以一个集合作为参数创建List:
string[] temArr = { "Ha", "Hunter", "Tom", "Lily", "Jay", "Jim", "Kuku", "Locu"};
List<string> testList = new List<string>(temArr);
12.2 List的进阶、强大方法:
- List. Add(T item)添加一个元素
- List. AddRange(IEnumerable
collection)添加一组元素 - Insert(intindex, T item);在index位置添加一个元素
十三.结构体
- 在 C# 中,结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。
- struct 关键字用于创建结构体。
13.1 定义结构体(struct)
struct Books
{
public string title;
public string author;
public string subject;
public int book_id;
};
13.2 C# 结构的特点
- 结构可带有方法、字段、索引、属性、运算符方法和事件。
- 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义默认的构造函数。默认的构造函数是自动定义的,且不能被改变。
- 与类不同,结构不能继承其他的结构或类。
- 结构不能作为其他结构或类的基础结构。
- 结构可实现一个或多个接口。
- 结构成员不能指定为 abstract、virtual 或 protected。
- 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
- 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。
13.3 类 vs 结构
- 类是引用类型,结构是值类型。
- 结构不支持继承。
- 结构不能声明默认的构造函数。
- 结构体中声明的字段无法赋予初值,类可以:
- 结构体的构造函数中,必须为结构体所有字段赋值,类的构造函数无此限制:
十四. 枚举
- 枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。 - 声明枚举的一般语法:
enum <enum_name>
{
enumeration list
};
其中,
- enum_name 指定枚举的类型名称。
- enumeration list 是一个用逗号分隔的标识符列表。
- 枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0.
例:
using System;
namespace EnumApplication
{
class EnumProgram
{
enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
static void Main(string[] args)
{
int WeekdayStart = (int)Days.Mon;
int WeekdayEnd = (int)Days.Fri;
Console.WriteLine("Monday: {0}", WeekdayStart);
Console.WriteLine("Friday: {0}", WeekdayEnd);
Console.ReadKey();
}
}
}
输出结果为:
Monday: 1
Friday: 5
===
- 若把Sun=2,则输出3,7
- 当然枚举列表中的符号无关紧要,可以随便取名咯
十五. 属性(Property)
- 属性(Property) 是类(class)、结构(structure)和接口(interface)的命名(named)成员。类或结构中的成员变量或方法称为 域(Field)。属性(Property)是域(Field)的扩展,且可使用相同的语法来访问。它们使用 访问器(accessors) 让私有域的值可被读写或操作。
- 属性(Property)不会确定存储位置。相反,它们具有可读写或计算它们值的 访问器(accessors)
- 抽象类可拥有抽象属性,这些属性应在派生类中被实现:
例如,
有一个名为 Student 的类,带有 age、name 和 code 的私有域。我们不能在类的范围以外直接访问这些域,但是我们可以拥有访问这些私有域的属性。
十六.访问器(Accessors)
- 属性(Property)的访问器(accessor)包含有助于获取(读取或计算)或设置(写入)属性的可执行语句。访问器(accessor)声明可包含一个 get 访问器、一个 set 访问器,或者同时包含二者。例如
using System;
namespace tutorialspoint
{
class Student
{
private string code = "N.A";
private string name = "not known";
private int age = 0;
// 声明类型为 string 的 Code 属性
public string Code
{
get
{
return code;
}
set
{
code = value;
}
}
// 声明类型为 string 的 Name 属性
public string Name
{
get
{
return name;
}
set
{
name = value;
}
}
// 声明类型为 int 的 Age 属性
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
public override string ToString()
{
return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
}
}
class ExampleDemo
{
public static void Main()
{
// 创建一个新的 Student 对象
Student s = new Student();
// 设置 student 的 code、name 和 age
s.Code = "001";
s.Name = "Zara";
s.Age = 9;
Console.WriteLine("Student Info: {0}", s);
// 增加年龄
s.Age += 1;
Console.WriteLine("Student Info: {0}", s);
Console.ReadKey();
}
}
}
十七.特性
- 特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
- 特性(Attribute)的名称和值是在方括号内规定的,放置在它所应用的元素之前
- .Net 框架提供了两种类型的特性:预定义特性和自定义特性。
17.1 预定义特性(Attribute)
- .Net 框架提供了三种预定义特性:**
- AttributeUsage
- Conditional
- Obsolete :这个预定义特性标记了不应被使用的程序实体
==
- c#特性可以用于各种类型和成员:
- 加在类前边--类特性
- 加在方法声明前面--方法特性
- 特性的最主要目的就是自描述,限定,特性只需要在类或方法需要的时候加上去就行了**
17.2 创建自定义特性(Attribute)
- 创建并使用自定义特性包含四个步骤:
- 声明自定义特性
- 构建自定义特性
- 在目标程序元素上应用自定义特性
- 通过反射访问特性