【编程语言】C# 从菜鸟到高手

1 Overview

1.1 CLI

  • common language infrastructure
  • The .NET Framework, .NET Core, Mono, DotGNU and Portable.NET are implementations of the CLI.
  • 能更容易的设计跨语言交互的组件和应用程序,也就是不同语言的对象能相互通信
  • CLR 某种意义上可理解为 Java 的 JVM,而 MSIL 相当于 Java 中的字节码;

1.2 .NET Framework 是什么

.NET Framework 由一个巨大的代码库组成,适用于 C#,C++,VB,Jscript,COBOL等等,这些语言可以访问框架,彼此之间也可以相互访问:

1.3 C# 语言特征

C# is a multi-paradigm programming language encompassing strong typing, imperative, declarative, functional, generic, object-oriented (class-based), and component-oriented programming disciplines.

  • 布尔条件(Boolean Conditions)
  • 自动垃圾回收(Automatic Garbage Collection)
  • 标准库(Standard Library)
  • 组件版本(Assembly Versioning)
  • 属性(Properties)和事件(Events)
  • 委托(Delegates)和事件管理(Events Management)
  • 易于使用的泛型(Generics)
  • 索引器(Indexers)
  • 条件编译(Conditional Compilation)
  • 简单的多线程(Multithreading)
  • LINQ 和 Lambda 表达式
  • 集成 Windows

1.4 Programming paradigms

编程范式

  1. imperative programming
  2. declarative programming
  3. structured programming, block-structured programming
  4. modular programming
  5. object-oriented programming
  6. 命令式编程语言与声明式编程语言,前者更关注于怎么做,how,后者更关注于做什么,what;
  7. Procedural programming is a type of imperative programming; 程序由若干过程(subroutine,function,)组成;
  8. proc, def, sub, func, …
  9. From the 1960s onwords, structured programming and modular programming in general have been promoted as a techniques to improve the maintainability and overall quality of imperative programs. And then is object-oriented programming.
  10. The hardware implementation of almose all computers is imperative.

<待续>

2 C# 基础

2.1 关键字

  1. namespace, using : Java 中通过 package,import 来管理命名空间, 可嵌套,如 System.Console
  2. 标识符可以以 @ 开头,比如@if,第一个字符不能是数字

保留关键字:

  • checked/unchecked
  • decimal : 128 bit 十进制值
  • default
  • delegate
  • event
  • explicit/implicit
  • extern
  • fixed
  • foreach : 遍历数组 foreach (int j in intArray)
  • in
  • internal
  • is : 判断对象是否为某类型, if(xiaoming is JingCha)
  • as : 强制类型转换, StringReader r = obj as StringReader;
  • lock
  • namespace
  • object
  • operator : 运算符重载
  • out
  • params : 不定参数
  • readonly
  • ref
  • sealed : 密封类不能被继承,Java 中为 final
  • sizeof :获取一个类型或变量的存储大小,sizeof(stype) == 2
  • stackalloc : 在栈上分配空间,用于 unsafe code
  • struct
  • typeof: 获取 class 的类型,typeof(StreamReader)
  • unsafe
  • virtual : virtual 方法可以在不同的继承类中有不同的实现,C# 通过抽象类和虚方法来实现动态多态性
  • volatile
  • & : 返回变量的地址

上下文关键字:

  • add
  • alias
  • ascending/descending
  • dynamic : 运行时进行类型检查,而不是编译期
  • from
  • get
  • global
  • group
  • into
  • join
  • let
  • orderby
  • partial
  • remove
  • select
  • set

2.2 类型

2.2.1 引用

  引用是一个指向实际存储空间的一个指针,C#的引用类型有: object,dynamic,string

  object 可以理解为 void *,在使用时,需要进行类型转换。当一个值类型转换为对象类型时,称为装箱;当一个对象类型转换为值类型时,称为拆箱。

object obj;
obj = 100

  在 java 中:

Integer num = 100;
Object obj = 100;

  dynamic 变量的类型检查时在运行时发生的, 而对象类型变量的类型检查时在编译时发生的。

dynamic a = 20;

  String 类型允许给变量分配任何字符串值,可以通过两种形式:引号和@引号

string a = @"c:\windows";
string b = "c:\\windows";
string c = @"<script type=""text/javascript"">
    <!--
    -->
</script>"; // 在写usage方法时,非常有用

2.2.2 指针类型

C# 中指针与 c/c++中的指针有相同的功能。

type* tptr;
char* cptr;
int* iptr;

2.2.3 变量

本质上讲,变量就是特定内存的名称,而类型就是变量所指的内存块的大小。

int num = Convert.ToInt32(Console.ReadLine());

2.3 封装

访问修饰符:

  1. public
  2. private
  3. protected
  4. internal : 只有同一个程序集的对象可以访问
  5. protected internal

2.4 参数传递

  • 值参数 : 赋值参数的实际值,实参和形参使用不同的内存区,方法调用不影响实参的值;
  • 引用参数 : 会改变实参的值
  • out 参数 : 接收返回结果,使用out就不需要通过赋值获取 return 的值了。
void swap_ref(ref int x, ref int y){ int tmp = x; x = y; y = tmp;};
void swap_value(int x, int y) {int tmp = x; x = y; y = tmp;};
void value_out(out int x) {x=8;};

  对应 java 代码:需要特别注意的是,引用本身是值变量,在进行参数传递的时候,实参和形参是不同的引用!

public static void getValue(Integer a) {
    a = 200; /、 这里的 a 引用跟形参传入的 b 已经是不同的变量了
}

public static void getValue(int[] a) {
    a[1] = 5;
}


Integer b = new Integer(100);
getValue(b);
System.out.println(b); // b = 100

int[] c = {1, 2, 3};
getValue(c);
System.out.println(c[1]); // c = {1, 5, 3}

  感受一下 Java 与 C# 之间的不同!

2.5 Nullable

int i; // 默认值为 0
int? j; // 默认值为 null

  首先,primitive type 如 int,double,bool 等无法直接赋值null,比如在 java 中:

int i; // compile error: 变量 i 未初始化
int i = null; // compile error: 不兼容的类型,nulltype 无法转换为 int;
Integer i = null; // OK

  在处理数据库和其他包含可能为负值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。

  比如使用 Hibernate 时,如果数据表中的 column 时允许为 null 的,那么 Entity 中的类型必须为 Integer,而不能为 int,否则就会出错。

2.6 String

  • String.Join
  • String.Format
  • String.Compare
  • String.Concat
  • String.Contains
  • String.Copy
  • String.Equals
  • String.EndsWith
  • String.IndexOf
  • String.Insert
  • String.Remove
  • String.Replace
  • String.Split
  • Strint.Trim
  • String.ToLower
  • String.TOUpper
  • String.Length
//方法返回字符串
string[] sarray = { "Hello", "From", "Tutorials", "Point" };
string message = String.Join(" ", sarray);
Console.WriteLine("Message: {0}", message);

//用于转化值的格式化方法
DateTime waiting = new DateTime(2012, 10, 10, 17, 58, 1);
string chat = String.Format("Message sent at {0:t} on {0:D}", 
waiting);
Console.WriteLine("Message: {0}", chat);

2.7 Struct 结构体

结构体是值类型,不是引用类型,是用来代表一个记录。

struct Books 
{
    public string title;
    public string author;
    public string subject;
    public int id;
}

Books book; // 已经进行了实例化,即分配了book的存储区
  • 可以带有方法、字段、索引、属性、运算符方法和事件;
  • 可定义构造函数,不能定义析构函数
  • 不能被继承
  • 可以实现多个接口
  • 成员访问描述符不能为: abstract,virtual,protected
  • 与类不同,接口实例化可以不适用 New

  引用对象的值是存储在堆空间的,值类型的对象值是存储在栈上的, 生存期不同。内存访问方式不同,效率也不一样,但是栈空间大小是有限的。

2.8 Enum 枚举

  C# 枚举是值类型,不能被继承;

enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat};

2.9 Class

成员函数: 对象的方法
成员变量: 对象的属性

2.9.1 继承的形式

<acess-specifier> class <base_class>
{
 ...
}

<acess-specifier> interface <base_interface>
{
 ...
}

class <derived_class> : <base_class>, <base_interface>
{
 ...
}

  • Java 中需要关键字 extends, implements 来实现继承;
  • C# 不支持多重继承,单可以使用接口来实现多重继承
  • 通过 inteface 来定义接口

2.9.2 多态

  简单的说,就是相同的接口,不同的行为;在编程语言层面,又分为静态多态性与动态多态性,也称为静态绑定与动态绑定。静态绑定主要是指函数重载,运算符重载;而动态绑定则指函数重写(),C# 通过抽象类和虚方法来实现动态多态性。

  C# 中的 virtual, override 在 Java 中体现为 @Override 方法。

2.10 运算符重载

public static result-type operator unary-operator ( op-type operand )
public static result-type operator binary-operator ( op-type operand, op-type2 operand2 )
public static implicit operator conv-type-out ( conv-type-in operand )
public static explicit operator conv-type-out ( conv-type-in operand )
  • result-type 运算符的结果类型。
  • unary-operator 下列运算符之一:+ - ! ~ ++ — true false
  • op-type 第一个(或唯一一个)参数的类型。
  • operand 第一个(或唯一一个)参数的名称。
  • binary-operator 其中一个:+ - * / % & | ^ << >> == != > < >= <=
  • op-type2 第二个参数的类型。
  • operand2 第二个参数的名称。
  • conv-type-out 类型转换运算符的目标类型。
  • conv-type-in 类型转换运算符的输入类型。

2.11 预处理指令

  预处理器指令指导编译器在实际编译开始之前对信息进行预处理。

  • define 它用于定义一系列成为符号的字符。
  • undef 它用于取消定义符号。
  • if 它用于测试符号是否为真。
  • else 它用于创建复合条件指令,与 #if 一起使用。
  • elif 它用于创建复合条件指令。
  • endif 指定一个条件指令的结束。
  • line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
  • error 它允许从代码的指定位置生成一个错误。
  • warning 它允许从代码的指定位置生成一级警告。
  • region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
  • endregion 它标识着 #region 块的结束。

2.12 正则表达式

Regex

  • Regex.IsMatch
  • Regex.Matches
  • Regex.Replace
  • Regex.Split

2.13 异常处理

  • System.IO.IOException 处理 I/O 错误。
  • System.IndexOutOfRangeException 处理当方法指向超出范围的数组索引时生成的错误。
  • System.ArrayTypeMismatchException 处理当数组类型不匹配时生成的错误。
  • System.NullReferenceException 处理当依从一个空对象时生成的错误。
  • System.DivideByZeroException 处理当除以零时生成的错误。
  • System.InvalidCastException 处理在类型转换期间生成的错误。
  • System.OutOfMemoryException 处理空闲内存不足生成的错误。
  • System.StackOverflowException 处理栈溢出生成的错误。

2.14 文件 IO

  • BinaryReader 从二进制流读取原始数据。
  • BinaryWriter 以二进制格式写入原始数据。
  • BufferedStream 字节流的临时存储。
  • Directory 有助于操作目录结构。
  • DirectoryInfo 用于对目录执行操作。
  • DriveInfo 提供驱动器的信息。
  • File 有助于处理文件。
  • FileInfo 用于对文件执行操作。
  • FileStream 用于文件中任何位置的读写。
  • MemoryStream 用于随机访问存储在内存中的数据流。
  • Path 对路径信息执行操作。
  • StreamReader 用于从字节流中读取字符。
  • StreamWriter 用于向一个流中写入字符。
  • StringReader 用于读取字符串缓冲区。
  • StringWriter 用于写入字符串缓冲区。

3 高级特性

3.1 Attribute

  C# 的 Attribute 类似于 Java 的 Annotation,用于在运行时向程序传递在代码元素(如类、方法、结构、枚举、组件等)中声明的各类信息。

  Attribute 用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息,有预定义特性和自定义特性两类。

  语法:

[attribute(positional_parameters, name_parameter = value, ...)]
element

3.1.1 预定义特性

  1. AttributeUsage
  2. Conditional
  3. Obsolete : 在 java 中使用 @Deprecated

3.1.2 创建自定义特性

。。。

3.2 反射 Reflection

  反射是指程序可以访问、检测和修改它本身状态和行为的一种能力。

  程序集包含模块,模块中有类,类有成员和方法,反射则封装了描述程序、模块、类等元信息的对象。可以通过反射动态地创建类型的实例,或者将类型绑定到现有对象,或者从现有对象中获取类型,还可以在运行时获取特性的信息。

  优点:

  • 反射提高了程序的灵活性和扩展性。
  • 降低耦合性,提高自适应能力。
  • 它允许程序创建和控制任何类的对象,无需提前硬编码目标类。

  缺点:

  • 性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此反射机制主要应用在对灵活性和拓展性要求很高的系统框架上,普通程序不建议使用。
  • 使用反射会模糊程序内部逻辑;程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。

  MemberInfo 对象中封装了类型信息

System.Reflection.MemberInfo info = typeof(MyClass);

3.3 属性 Property

  属性是 field 的扩展,通过访问器来访问。

  访问器通过上下文关键字 get/set 来定义。

    class Student
   {

      private string code = "N.A";

      // 声明类型为 string 的 Code 属性
      public string Code
      {
         get
         {
            return code;
         }
         set
         {
            code = value;
         }
      }
    }

3.4 索引器

  允许一个对象可以像被数组一样被索引,当为一个类定义一个索引器时,该类的行为就会想一个虚拟数组一样,可以使用[]来访问。

  有点类似于运算符重载的一个特例,[] 本质上可以看作为运算符,在 C++ 中就可以对其进行运算符重载。

element-type this[int index] 
{
   // get 访问器
   get 
   {
      // 返回 index 指定的值
   }

   // set 访问器
   set 
   {
      // 设置 index 指定的值 
   }
}

  注意: 索引器可以声明为多个参数,参数类型也可以为其他类型,比如字符串;

3.5 委托 Delegate

  C# 中的委托类似于c/c++中的函数指针,委托让 C# 中的函数成为第一类对象,可以作为参数和返回值来进行传递,在实现事件和回调方法时特别有用。在 Java 中需要借助 Function 接口来实现此类功能。

  声明委托,也类似于 c/c++ 声明函数指针:

delegate

  比如,声明一个带有单个 string 参数且返回 int 值的方法的委托:

public delegate int MyDelegate (string s);

  委托通过关键字 new 来实例化,

MyDelegate a = new MyDelegate(TargetMethod);

  委托可以多播!与事件处理有很大关系!!

  委托在使用形式上比 Java 的 Function 更简洁,更流畅一些。

  匿名方法:是一种无名的委托对象,将代码块赋值给匿名委托:

delegate void NumberChanger(int n);
...
NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous Method: {0}", x);
};


void change(int x)
{
    ...
}

NumberChanger nc1 = new NumberChanger(change;

3.6 事件 Event

  • 发布器: 管理事件与订阅者的映射关系,并发布事件到器订阅者;
  • 订阅器: 又称为监听器,处理器等,接收一个事件,并处理

  事件声明:

public delegate void BoilerLogHandler(string status); // 声明事件的委托类型,也就是定义 handler
public event BoilerLogHandler BoilerEventLog; // 定义事件

  使用事件的步骤:

  1. 实例化发布器
  2. 实例化订阅器
  3. 使用订阅器的方法来实例化发布器的委托
  4. 将实例化的委托添加到发布器事件中

3.7 集合 Collection

  集合类是专门用于数据存储和检索的类,根据结构可分为 Stack,Deque,List,Set,Map

  System.Collection 命名空间的类:

  1. 动态数组(ArrayList):它代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。
  2. 哈希表(Hashtable):它使用键来访问集合中的元素。当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。哈希表中的每一项都有一个键/值对。键用于访问集合中的项目。
  3. 排序列表(SortedList):它可以使用键和索引来访问列表中的项。排序列表是数组和哈希表的组合。它包含一个可使用键或索引访问各项的列表。如果您使用索引访问各项,则它是一个动态数组(ArrayList),如果您使用键访问各项,则它是一个哈希表(Hashtable)。集合中的各项总是按键值排序。
  4. 堆栈(Stack):它代表了一个后进先出的对象集合。当您需要对各项进行后进先出的访问时,则使用堆栈。当您在列表中添加一项,称为推入元素,当您从列表中移除一项时,称为弹出元素。
  5. 队列(Queue):它代表了一个先进先出的对象集合。当您需要对各项进行先进先出的访问时,则使用队列。当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队。
  6. 点阵列(BitArray):它代表了一个使用值 1 和 0 来表示的二进制数组。当您需要存储位,但是事先不知道位数时,则使用点阵列。您可以使用整型索引从点阵列集合中访问各项,索引从零开始。

3.8 泛型 Generic

  泛型允许参数化类型,在集合类中大量使用。

  使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:

  1. 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
  2. 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
  3. 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
  4. 您可以对泛型类进行约束以访问特定数据类型的方法。
  5. 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。

3.9 多线程

System.Threading

3.10 Iterator

  直接 yield return

public static System.Collections.IEnumerable SomeNumbers()  
{  
    yield return 3;  
    yield return 5;  
    yield return 8;  
}

  通过循环来 yield return

public static System.Collections.Generic.IEnumerable<int>  
    EvenSequence(int firstNumber, int lastNumber)  
{  
    for (int number = firstNumber; number <= lastNumber; number++)  
    {  
        if (number % 2 == 0)  
        {  
            yield return number;  
        }  
    }  
}  

  创建 Collection类: foreach 语句会隐式调用 GetEnumerator 方法

public class DaysOfTheWeek : IEnumerable  
{  
    private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };  

    public IEnumerator GetEnumerator()  
    {  
        for (int index = 0; index < days.Length; index++)  
        {  
            // Yield each day of the week.  
            yield return days[index];  
        }  
    }  
}  

  yield return 可以在 method 或者 get 访问器中调用。尽管 iterator 的定义像个方法,但其实编译器会将其转换为一个嵌套类(nested class),本质上是个状态机。这个类会在 佛reach loop 中跟踪 iterator 的位置。

  可以通过 Ildasm.exe 工具去查看编译器为 iterator 生成的中间代码。

3.11 表达式树 Expression Trees

  简单来说就是语法树,通过树形结构的节点来存储表达式。通过表达式树可以动态修改可执行代码。

  下面的代码展现了如何通过 API 来创建 lambda 表达式对应的 Expression tree。

ParameterExpression numParam = Expression.Parameter(typeof(int), "num");  
ConstantExpression five = Expression.Constant(5, typeof(int));  
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);  
Expression<Func<int, bool>> lambda1 =  
    Expression.Lambda<Func<int, bool>>(  
        numLessThanFive,  
        new ParameterExpression[] { numParam });  

  Expression tree api 也支持赋值语句,控制语句(循环,条件),以及 try-catch 代码块。

  编译与执行

// Creating an expression tree.  
Expression<Func<int, bool>> expr = num => num < 5;  

// Compiling the expression tree into a delegate.  
Func<int, bool> result = expr.Compile();  

// Invoking the delegate and writing the result to the console.  
Console.WriteLine(result(4));  

// Prints True.  

// You can also use simplified syntax  
// to compile and run an expression tree.  
// The following line can replace two previous statements.  
Console.WriteLine(expr.Compile()(4));  

4 异步编程

4.1 async, await

  异步/非阻塞编程,可提升应用的响应性。比如在 web 开发,以及 ui 开发上,大有益处。

  但传统的实现异步的方式都比较复杂,比如 Java 中的 nio,需要了解 channel,selector,buffer 这些概念,或者使用 netty 这样的网络框架。c/c++ 进行异步/非阻塞编程,则需要理解 select,poll,epoll 等概念。难以开发,也难以维护。

  C# 则通过引入 async,await 等关键字,让编译器去处理复杂的异步/非阻塞逻辑,让语言层面的异步逻辑清晰流畅。

  使用 async 来指定 method,lambda expression, or anonymous method 为异步。

  例如:

public async Task<int> ExampleMethodAsync()  
{  
    // . . . .  
} 

  任何函数(方法),如果内部调用了 async 方法,那么该方法也必须声明为 async 方法,除非通过 await 关键字来调用 async 方法或者其返回结果 。

async Task<int> AccessTheWebAsync()  
{   
    HttpClient client = new HttpClient();  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  

    
    DoIndependentWork();  
    string urlContents = await getStringTask;  

    return urlContents.Length;  
}  
  1. 方法签名中有 async
  2. 方法名后缀 Async
  3. 返回值为 Task

  await 标明方法可以执行到何处,await 之前应该为结果无关的独立逻辑,当执行到 awati 时停止,等待异步操作完成。同时该方法被挂起,控制权返回至上层函数。

4.2 控制流程

这里写图片描述

  1. 一个 event handler 调用并 await 一个异步方法: AccessTheWebAsync
  2. AccessTheWebAsync 调用异步方法 GetStringAsync 去网站下载数据
  3. 当 GetStringAsync 因为某种原因需要挂起时,不会阻塞,会将控制权重新交给 AccessTheWebAsync
  4. AccessTheWebAsync 会执行它的同步代码逻辑,直到 await 语句,然后挂起并将控制权交给 event handler
  5. 当 GetStringAsync 完成时,结果会存放在其返回的 Task 对象中,await getStringTask 返回结果。

 async 方法是非阻塞的,await 表达式并不会使当前线程阻塞,它只是标注挂载点并将控制权转移至调用方。async 和 await 关键字并不会去创建额外的线程,异步机制并不是通过多线程来实现的。

  • 27
    点赞
  • 94
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值