C# 精华总结 ---- 基础

文章目录

    • 1. C# 和 C++ 区别
      • 1.1 程序效率
      • 1.2 编译目标
      • 1.3 内存管理
      • 1.4 指针
      • 1.5 运算符的重载
      • 1.6 库
      • 1.7 目标环境
      • 1.8 预处理指令
      • 1. 9 枚举
      • 1.10 析构函数
      • 1.11 类与结构
      • 1.12 委托
      • 1.13 事件
      • 1. 14 接口
      • 1. 15 属性
      • 1. 16 确定线程
      • 1. 17 反射
      • 1. 18 开发项目来说
    • 2. C# 特性
      • 2.1 delegate
      • 2.2 event
      • 2.3 泛型
      • 2.4 动态对象
      • 2.5 值和引用
      • 2.6 装箱拆箱

C# 精华总结 ---- 基础_第1张图片

1. C# 和 C++ 区别

1.1 程序效率

C# 它通过底层的虚拟机机制减少了 C/C++ 语言中常常容易发生的内存泄漏和安全性问题,它代价是运行速度相对减慢

1.2 编译目标

C# 则编译成中间语言, 它与 Java 在字节代码上有些相似。而 C++ 经常把代码编译成汇编语言。之后 IL 在通过 Iust-In-Time 编译进程转换成本机的可执行代码

1.3 内存管理

C# 将开发人员从记账式的内存管理任务中解放出来,不再需要显式地删除动态分配的给堆的内存,而 C++ 则要这么做,无用存储单元收集器将周期性地清空不再使用的内存

1.4 指针

C# 中指针的运用可能与 C++ 一样,但它只用于已特别标记为应用指针的代码块中。对于大部分的程序,C# 依赖 VB/Java 风格的对类实例的引用,而不需和 C++ 一样频繁地使用指针

1.5 运算符的重载

在 C++ 比 C# 中用来显式重载的运算符比要多,主要是 C# 编译器是运用一些定制的基本操作符重载(如=)来自动计算出组合操作符的重载(如+=)

1.6 库

C++ 依赖于标准库,C# 依赖于.NET 基类。而 .NET 基类是以单一的继承为基础,而标准库是以继承和模板为基础

1.7 目标环境

C# 是基于 GUI 的环境下(不仅仅是 Windows 环境,尽管现在只是在 Windows 中可用)专门为编程和背景服务(如 Web 服务)而设计的。这与语言本身无关,而是反映在基类库的设计中

1.8 预处理指令

C# 包含一些预处理指令,它的语法和 C++ 一样。但 C# 的预处理指令少得多,因为 C# 的其它语言特性使得这些指令不再重要

1. 9 枚举

C# 的枚举比 C++ 中枚举的功能更为广泛。它们在权限范围内的语法结构很成熟,可支持不同的属性与方法。枚举仍是作为基本的数字类型来执行的,所以不会存在性能上的损失。

1.10 析构函数

当析构函数被调用后,C# 不能保证它的执行, 除非要除空的是具体的外部源代码,如文件与数据库连接,C# 不可以在析构函数中放置代码,而 C++ 则可以。

1.11 类与结构

C# 正式区分了类(一般用于包含许多方法的大型对象)和结构(一般用于只包含变量集合的小型对象)类和结构的存储方式不同,结构不支持继承。

1.12 委托

C# 不支持函数指针。但委托可以实现对应的功能,它把引用以一种特殊的形式封装到方法中。委托可以在方法之间传递,用于调用包含引用的方法。这与 C++ 指针的工作方式相同

1.13 事件

事件与委托相似,但它支持回调模式。就是当执行一些操作时,客户通知机器,将这些操作通知给它。工作方式与 VB 相同。 特性:这一概念在 VB 和COM 中应用的很广,C# 中也导入了这一概念

1. 14 接口

可以将接口看为是一个抽象的类,其目的是用来定义类同意执行的方法和属性。C# 接口与 COM 接口不同 ; C# 接口是简单的方法列表,而 COM 接口有其它的相关的特性,如GUIDS,但他们的原理基本相同。

1. 15 属性

C# 可以用特性元信息(如属性)来修饰类、方法和参数等。可以在运行时内访问属性,已决定代码的执行。

1. 16 确定线程

C#的lock语句可支持线程同步(C++不支持线程,必须在代码中通过调用API或其它的类库来实现)

1. 17 反射

C#中,代码可自动获得已编译的装配件(库和可执行文件)中的类定义的信息。可以编写显示类和方法信息的程序

1. 18 开发项目来说

C#适合企业各应用程序,C++适合底层开发(游戏等)

另外关于 C# 的语法以及数据结构 会在下一篇文章进行总结,接下来看看 C# 这门语言的一些特性(必须要掌握)

2. C# 特性

2.1 delegate

概念: C# 中的委托类似于 C/C++中的函数指针, 使用委托使程序员可以将方法引用封装在委托对象内。然后可以将该委托对象传递给可调用所引用方法的代码,而不必在编译时知道将调用哪个方法。与 C/C++中的函数指针不同,委托是面向对象、类型安全的,并且是安全的。

案例

using System;

class Program{
     
    static void OtherClassMethod(){
     
        Console.WriteLine("Delegate an other class's method");
    }
 
    static void Main(string[] args){
     
        var test = new TestDelegate();
        //deleagteMethod 搭载了三个函数
        test.delegateMethod = new TestDelegate.DelegateMethod(test.NonStaticMethod);
        test.delegateMethod += new TestDelegate.DelegateMethod(TestDelegate.StaticMethod);
        test.delegateMethod += Program.OtherClassMethod;
        test.RunDelegateMethods();
        Console.ReadKey();
    }
}

class TestDelegate{
     
    public delegate void DelegateMethod();  //声明了一个Delegate Type

    public DelegateMethod delegateMethod;   //声明了一个Delegate对象

    public static void StaticMethod(){
     
        Console.WriteLine("Delegate a static method");
    }

    public void NonStaticMethod(){
     
        Console.WriteLine("Delegate a non-static method");
    }

    public void RunDelegateMethods(){
     
        if (delegateMethod != null){
     
            Console.WriteLine("---------");
            //运行被搭载在 delegateMethon 上的函数
            delegateMethod.Invoke();
            Console.WriteLine("---------");
        }
    }
}
输出如下
---------
Delegate a non-static method
Delegate a static method
Delegate an other class's method
---------

delegate 和 C++ 函数指针的区别?

  1. 一个 delegate对象一次可以搭载多个方法(methods), 而不是一次一个。当我们唤起一个搭载了多个方法(methods)的delegate,所有方法以其“被搭载到delegate对象的顺序”被依次唤起。
  2. 一个delegate对象所搭载的方法(methods)并不需要属于同一个类别。一个delegate对象所搭载的所有方法(methods)必须具有相同的原型和形式。然而,这些方法(methods)可以即有static也有non-static,可以由一个或多个不同类别的成员组成。
  3. 一个 delegate type的声明在本质上是创建了一个新的subtype instance,该 subtype 派生自 .NET library framework 的 abstract base classes Delegate 或 MulticastDelegate,它们提供一组public methods用以询访delegate对象或其搭载的方法(methods) ,与函数指针不同,委托是面向对象、类型安全并且安全的。

delegate 使用场景

首先来看一个案例

class Car{
     
    public delegate void Notify(int value);
    public event Notify notifier;

    private int petrol = 0;
    public int Petrol{
       //没有在这里直接调用 Alerter.Notify 是为了降低耦合性
        get {
      
            return petrol;
        }
        set{
     
            petrol = value;
            if (petrol < 10)  //当petrol的值小于10时,出发警报
            {
     
                if (notifier != null){
     
                    notifier.Invoke(Petrol);
                }
            }
        }
    }

    public Car(int petrol){
     
        Petrol = petrol;
    }

    public void Run(int speed){
     
        int distance = 0;
        while (Petrol > 0){
     
            Thread.Sleep(500);
            Petrol--;
            distance += speed;
            Console.WriteLine("Car is running... Distance is " + distance.ToString());
        }
    }
}
class Alerter{
     
    public Alerter(Car car){
     
        car.notifier += new Car.Notify(NotEnoughPetrol);
    }

    public void NotEnoughPetrol(int value){
     
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("You only have " + value.ToString() + " gallon petrol left!");
        Console.ResetColor();
    }
}
using System;
using System.Threading;

class Program{
     
    static void Main(string[] args){
     
        var car = new Car(15);
        new Alerter(car);
        car.Run(120);
    }
}
//输出以下内容
Car is running... Distance is 120
Car is running... Distance is 240
Car is running... Distance is 360
Car is running... Distance is 480
Car is running... Distance is 600
You only have 9 gallon petrol left!
Car is running... Distance is 720
You only have 8 gallon petrol left!
Car is running... Distance is 840
You only have 7 gallon petrol left!
Car is running... Distance is 960
You only have 6 gallon petrol left!
Car is running... Distance is 1080
You only have 5 gallon petrol left!
Car is running... Distance is 1200
You only have 4 gallon petrol left!
Car is running... Distance is 1320
You only have 3 gallon petrol left!
Car is running... Distance is 1440
You only have 2 gallon petrol left!
Car is running... Distance is 1560
You only have 1 gallon petrol left!
Car is running... Distance is 1680
You only have 0 gallon petrol left!
Car is running... Distance is 1800

总结: 其实以上的代码是设计模式中的观察者模式(观察者模式又称Source/Listener模式)的实现,当汽车在运行中汽油量<10 时,警报器便会发出警报。在上面代码中,Delegate 相当于一个存放回调函数的函数指针,使用Delegate,我们可以非常方便地实现观察者模式。而其实,在需要使用回调函数时,我们都可以考虑使用Delegate。

delegate 的使用场景

  1. 当我们在C#中需要类似函数指针这种东西时
  2. 当我们需要使用回调函数的时候
  3. 需要异步调用的时候
  4. 实现观察者模式的时候
  5. 处理事件响应的时候

2.2 event

概要: 其实C#事件就是基于windows消息处理机制的,只是封装的更好,让开发者无须知道底层的消息处理机制,就可以开发出强大的基于事件的应用程序来。

采用事件编程的好处有哪些?

  1. 使用事件,我们可以很方便的知道程序的执行顺序
  2. 程序不会不停的检测输入设备, 而是等待消息的到来,每个输入的消息会被排进队列,等待程序处理它。如果没有消息在等待,则程序会把控制交回给操作系统,以运行其他程序。
  3. 简化了编程, 操作系统只是将消息传递给对象,有对象的事件驱动处理程序确定事件的处理方法, 所以对操作系统来说,只需要知道如何与对象进行对话, 不必要知道内部的工作原理.

下面来简单剖析 C# event 事件

事件编程可以简单分为两个部分: 事件发生的类事件接受处理的类

事件发生类说的就是: 在这个类中触发了一个事件,但这个类并不知道哪个对象或者方法会接收到并处理它触发的事件, 所需要的是在发送方和接收方之间存在一个媒介, 而这个媒介就是上面提到的 委托 ,

键盘按键 实例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1{
     
    class EventArgs{
     

    }
    // 如果事件处理程序需要状态信息,就得派生一个类
    // 因为我们这里是按键,所以需要记录下来,好让后面处理
    internal class KeyEventArgs: EventArgs{
     
        private char keyChar;
        public KeyEventArgs(char keyChar):base(){
     
            this.keyChar = keyChar;
        }
        public char KeyChar {
     
            get {
     
                return keyChar;
            }
        }
    }

    //再创建一个事件发生的类 KeyInputMonitor
    //这个类用于监控键盘按键的输入并触发一个事件:
    internal class KeyInputMonitor {
     
        // 创建一个委托,返回类型为void,两个参数
        public delegate void KeyDownHandler(object sender, KeyEventArgs e );
        // 将创建的委托和事件关联起来
        public event KeyDownHandler KeyDown;


        public void Run() {
     
            bool finished = false;
            do {
     
                Console.WriteLine("Input a char");
                string response = Console.ReadLine();

                char responseChar = (response == "") ? ' ' : char.ToUpper(response[0]);
                switch (responseChar) {
     
                    case 'X':
                        finished = true;
                        break;
                    default:
                        // 得到按键信息的参数
                        KeyEventArgs keyEventArgs = new KeyEventArgs(responseChar);
                        // 触发事件
                        //并将事件交由KeyDownHandler这个委托来处理,委托指定事件处理方法去处理事件
                        //参数this是指触发事件的对象就是本身这个对象,keyEventArgs包含了按键信息。
                        KeyDown(this, keyEventArgs);
                        break;
                }
            } while (!finished);
        }
    }//end KeyInputMonitor 


    /* 
     * 最后创建一个事件接收方的类,这个类先产生一个委托实例,
     * 再把这个委托实例添加到产生事件对象的事件列表中去,这个过程又叫订阅事件。
     * 然后提供一个方法回显按键信息。*/

    internal class EventReceiver {
     
        public EventReceiver(KeyInputMonitor monitor) {
     
            // 产生一个委托实例并添加到KeyInputMonitor产生的事件列表中
            monitor.KeyDown += new KeyInputMonitor.KeyDownHandler(this.OnKeyDown);
        }
        private void OnKeyDown(object sender, KeyEventArgs e) {
     
            // 真正的事件处理函数
            Console.WriteLine("Capture key: {0}", e.KeyChar);
        }
    }


    // 主函数调用
    public class MainEntryPoint {
     
        static void Main(String[] args) {
     
            // 实例化一个事件发送器
            KeyInputMonitor monitor = new KeyInputMonitor();
            // 实例化一个事件接收器
            EventReceiver eventReceiver = new EventReceiver(monitor);
            // 运行
            monitor.Run();
        }
    }
}

总结

C# 中使用事件的步骤

  1. 创建一个委托
  2. 将创建的委托与特定事件关联
  3. 编写事件处理程序
  4. 利用编写的事件处理程序生成一个委托实例
  5. 把这个委托实例添加到产生事件对象的事件列表中去,这个过程也叫订阅事件

C# 中事件产生和实现的流程

  1. 定义 A 为产生事件的实例, a 为 A 产生的一个事件
  2. 定义 B 为接收事件的实例, b 为处理事件的方法
  3. A 由于用户或者系统产生一个 a 事件(例如点击一个 button , 产生一个 Click 事件)
  4. A 通过事件列表中的委托对象将这个事件通知给B
  5. B接到一个事件通知(实际是B.b利用委托来实现事件的接收)
  6. 调用B.b方法完成事件处理
public class A {
     
    public delegate void EventHandler(object sender);
    public event EventHandler a;

    public void Run() {
     
        Console.WriteLine("Trigger an event.");
        a(this);
    }
}

class B {
     
    public B(A a) {
     
        a.a += new A.EventHandler(this.b);
    }
    private void b(object sender) {
     
        Console.WriteLine("Received and handled an event.");
        Console.Read();
    }
}

2.3 泛型

这里我们总结一下泛型的概念以及作用

概念: 泛型其实就是提供一个数据类型的抽象层,因为它泛所以抽象,方便了我们代码的重构和提取,我们无需hard-code接口中的数据类型,而是通过一个抽象泛型类型来指定数据类型,所以泛型可以提取出一个通用的接口。

其实这个概念在 C++ 中是有的, 很相似

使用泛型的直观好处

  • 编译时进行类型检测,减少运行时异常InvalidCastException
  • 减少代码量
  • 程序性能提高, 因为无需要类型转换
  • …(更多好处后续总结)

至于怎么使用C# 中的泛型编程, 由于篇幅太长

在这篇博客总结 : .NET 中的泛型(上篇)

2.4 动态对象

dynamic 被编译后,实际是一个 object 类型,只不过编译器会对 dynamic 类型进行特殊处理,让它在编译期间不进行任何的类型检查,而是将类型检查放到了运行期

dynamic 类型转换:

//任何实例都能隐式转换为 dynamic 类型实例
dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();

//类型为dynamic的任何表达式也能够隐式转换为其他类型。
int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;

dynamic 简化反射

案例:

public class DynamicSample{
     
    public string Name {
      get; set; }
    public int Add(int a, int b){
     
        return a + b;
    }
}

反射

public class ProMain{
     
    static void Main(string[] args) {
     
        DynamicSample dynamicSample = new DynamicSample(); 
        var addMethod = typeof(DynamicSample).GetMethod("Add");
        int re = (int)addMethod.Invoke(dynamicSample, new object[] {
      1, 2 });
        
        Console.WriteLine("re:"+re);
        Console.ReadKey();
    }
}

进行简化

public class ProMain{
     
    static void Main(string[] args) {
     
        dynamic dynamicSample2 = new DynamicSample();
        int re2 = dynamicSample2.Add(1, 2);
        
        Console.WriteLine("re:"+re2);
        Console.ReadKey();
    }
}

使用场景

  • 如果编译时函数名称确定,对象类型运行时确定,那么运用dynamic是一个好主意
  • 如果编译时函数名称确定,对象类型在编译时也确定,那就既不需要反射也不需要dynamic
  • 如果函数名称在运行时才能确定的话,那函数名称就是一个字符串,必须使用反射来完成

2.5 值和引用

C# 参数的种类一共分为四种

  • 值参数 (按值传递的参数)
  • 引用参数 (按引用传递的参数, 使用ref 修饰符)
  • 输出参数 (使用 out 修饰符)
  • 参数数组 (使用 params 修饰符)

下面主要讨论的是 按值传递和按引用传递的区别, 以及值类型和引用类型在按值传递和按引用传递时的表现

按值传递的参数

C # 的参数在默认情况下都是按值传递的。也就是说,当向方法传递参数的时候,会创建一个新的存储位置,然后将参数的值复制一份放到该存储位置中。相当于声明了一个局部变量(形参),然后用传入的参数的值初始化这个变量。如果在方法内改变形参的值,将不会影响到方法调用的上下文.

值类型按值传递: 复制该值类型本身所代表的数据

对于引用类型的实参按值传递的误区

  • 在方法内部改变实参的值,不会对外部调用上下文产生影响。
  • 对于引用类型的实参,如果在方法内部更改所引用的对象的数据,实参和外部变量仍然引用的是同样的对象,因此都会受到影响

例如下面的代码:

class Pro{
        
    static void Main(String[] args) {
     
        StringBuilder sb1 = new StringBuilder("Hello");
        M(sb1);
        Console.WriteLine(sb1);
    }
    static void M(StringBuilder sb2){
     
        sb2 = null;
    }
}
// 输出 hello
class Pro{
     
   
    static void Main(String[] args) {
     
        StringBuilder sb1 = new StringBuilder("Hello");
        M(sb1);
        Console.WriteLine(sb1);
    }
    static void M(StringBuilder sb2){
     
        sb2.Append(" C#");
    }
}
//输出 Hello C#

按引用传递的参数

按引用传递不会涉及隐式复制。它所传递的,不是在调用方法时传递给方法的变量的值,而是变量本身。它不会创建新的存储位置,而是使用与变量相同的存储位置,因此,在调用方法上下文中传递给方法的变量与方法内部使用的参数,实际上是同一个。

在按引用传递参数时,在方法的声明和调用的地方都必须显式使用 ref 修饰符,这是为了让你清楚你正在进行的是与默认传递方式不同的按引用传递。

值类型按引用传递:

class Pro{
     
   
    static void Main(String[] args) {
     
        int i = 5;
        M(ref i);
        Console.WriteLine(i);
    }
    static void M(ref int j) {
     
        j = 10;
    }
}
// 输出 10

引用类型按引用传递: 引用类型按引用传递与值类型按引用传递表现形式是一样的,在方法内部所做的任何改变,都将反映到外部变量上。

class Pro{
     
   
    static void Main(String[] args) {
     
        StringBuilder sb1 = new StringBuilder("Hello");
        Console.WriteLine(sb1); //输出 Hello
        M(ref sb1);
        Console.WriteLine(sb1); //什么都不输出
    }
 
    static void M(ref StringBuilder sb2){
     
        sb2 = null;
    }
}

2.6 装箱拆箱

你可能感兴趣的:(C#,c#,基础语法)