C# 基础复习笔记

最近项目需要涉及 Windows 桌面以及后端程序开发,趁着年底不是很忙,抽空复习了下 C# 基础知识,其中很多东西是和 Java 类似的,学习理解起来并不困难。

  1. 与 Java 不同的是,C# 文件名可以不同于类的名称。
  2. C# 中的注释:// 不会被编译,而 /// 会被编译。一般输入 ///,VS.Net 会自动增加 XML 格式的注释。
  3. 在 C# 中,变量分为以下几种类型:
    • 值类型(Value types)
    • 引用类型(Reference types)
    • 指针类型(Pointer types)
  4. C# string 字符串的前面可以加 @(称作”逐字字符串”)将转义字符(\)当作普通字符对待。

    1
    string str = @"C:\Windows";
  5. Console.ReadLine() 用于接收来自用户的输入,并把它存储到一个变量中。

  6. C# 常量是使用 const 关键字来定义。
  7. C# 支持 foreach 循环,例如:

    1
    2
    3
    4
    foreach (int element in fibarray)
    {
    System.Console.WriteLine(element);
    }
  8. C# 访问修饰符范围比较 private < internal/protected < protected internal < public

    • Pubilc :任何公有成员可以被外部的类访问。
    • Private :只有同一个类中的函数可以访问它的私有成员。
    • Protected :该类内部和继承类中可以访问。
    • internal : 同一个程序集的对象可以访问。
    • Protected internal :3 和 4 的并集,符合任意一条都可以访问。
  9. C# 中,使用 ref 关键字声明引用参数。
  10. C# 中,使用 out 关键字声明参数需要 retrun 多个返回值的地方。
  11. 简单来说,ref 是有进有出,而 out 是只出不进。
  12. C# 支持可空类型语法,例如 int? num1 = null;
  13. Null 合并运算符,如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值,例如:

    1
    num3 = num1 ?? 5.34;
  14. 通过 String.Join(",", array) 可以拼接字符串数组。

  15. 通过 String.Format(xxx{0}yyy{1}, unKnowString1, unKnowString2) 可以格式化输出字符串。
  16. 在 C# 中,结构是值类型数据结构,struct 关键字用于创建结构。

    1
    2
    3
    4
    5
    6
    7
    struct Books
    {
    public string title;
    public string author;
    public string subject;
    public int book_id;
    };
  17. 枚举类型是使用 enum 关键字声明的,例如 enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

  18. C# 中的析构函数,其名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。用于在结束程序(比如关闭文件、释放内存等)之前释放资源。析构函数不能继承或重载。
  19. C# 声明一个类成员为 static 静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
  20. C# 继承通过冒号(:)语法来实现,例如 class <derived_class> : <base_class>,当然接口也是一样的。
  21. public DerivedClass() : base() 的意思就是先调用基类的构造函数,然后执行 DerivedClass()
  22. 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承,类似于 Java 中 final 关键字。
  23. 虚方法是使用关键字 virtual 声明的,虚方法可以在不同的继承类中有不同的实现。
  24. C# 动态多态性是通过 抽象类虚方法 实现的。
  25. 区别:抽象方法是需要子类去实现的。虚方法是已经实现了的,可以被子类覆盖,也可以不覆盖,取决于需求。
  26. C# 运算符重载通过关键字 operator 后跟运算符的符号来定义。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static Box operator+ (Box b, Box c)
    {
    Box box = new Box();
    box.length = b.length + c.length;
    box.breadth = b.breadth + c.breadth;
    box.height = b.height + c.height;
    return box;
    }
    ...
    Box3 = Box1 + Box2;
  27. C# 预处理器指令指导编译器在实际编译开始之前对信息进行预处理,预处理器指令都是以 # 开始,简单说就是针对条件编译符号生成不同的程序版本,设置方法: VS -> 生成 -> 条件编译符号。

  28. C# 中可用的预处理器指令:
预处理器指令 描述
#define 它用于定义一系列成为符号的字符。
#undef 它用于取消定义符号。
#if 它用于测试符号是否为真。
#else 它用于创建复合条件指令,与 #if 一起使用。
#elif 它用于创建复合条件指令。
#endif 指定一个条件指令的结束。
#line 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。
#error 它允许从代码的指定位置生成一个错误。
#warning 它允许从代码的指定位置生成一级警告。
#region 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。
#endregion 它标识着 #region 块的结束。
  1. C# 特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签,类似于 Java 的注解(Annotation)。
  2. .Net 框架提供了三种预定义特性:
    • AttributeUsage
    • Conditional
    • Obsolete
  3. AttributeUsage 用于实现自定义特性类,语法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    [AttributeUsage(
    validon, // 规定特性可被放置的语言元素,如类或者字段,等等。
    AllowMultiple=allowmultiple, // 是否允许多个特性一起使用,缺省值 false。
    Inherited=inherited // 是否允许特性继承,缺省值 false。
    )]

    ...

    [AttributeUsage(AttributeTargets.Class)]
    class MyAttribute : Attribute
    {
    public string Name { get; set; }

    public int Age { get; set; }
    }

    ...

    [MyAttribute(Name ="liyu", Age =18)]
    public partial class Form1 : Form
    {
    ...

    private void Form1_Load(object sender, EventArgs e)
    {
    var info = typeof(Form1);
    var classAttribute = (MyAttribute)Attribute.GetCustomAttribute(info, typeof(MyAttribute));
    Console.WriteLine(classAttribute.Name);
    Console.WriteLine(classAttribute.Age);
    }
    }
  4. Conditional 绝大部分情况下可以取代预处理标识符 #if,它更整洁,更别致、减少了出错的机会,但是其使用限制也有很多。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [Conditional("DEBUG")]
    public static void Test()
    {
    Console.WriteLine("Debug version");
    }

    // 等同于

    #if DEBUG
    Console.WriteLine("Debug version");
    #else
    Console.WriteLine("Release version");
    #endif
  5. Obsolete 特性用于标记被弃用的方法,类似于 Java 的 @Deprecated,其有两个参数,第一个参数表示描述内容,第二个参数表示是否让编译器生成错误提示。

    1
    2
    3
    4
    5
     [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
    static void OldMethod()
    {
    Console.WriteLine("It is the old method");
    }
  6. C# 反射(Reflection)指程序可以访问、检测和修改它本身状态或行为的一种能力,这和 Java 是一样的。

  7. 使用反射(Reflection)可以查看属性(attribute)信息

    1
    2
    3
    4
    5
    6
    System.Reflection.MemberInfo info = typeof(MyClass);
    object[] attributes = info.GetCustomAttributes(true);
    for (int i = 0; i < attributes.Length; i++)
    {
    System.Console.WriteLine(attributes[i]);
    }
  8. C# 属性(Property)的访问器(accessor)包含有助于获取(读取或计算)或设置(写入)属性的可执行语句,一个简单的例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Student
    {
    private string code = "N.A";

    // 声明类型为 string 的 Code 属性
    public string Code
    {
    get
    {
    return code;
    }
    set
    {
    code = value;
    }
    }
    }
  9. C# 抽象类可拥有抽象属性(Abstract Properties),这些属性将在派生类中被实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public abstract class Person
    {
    public abstract string Name { get; set; }
    public abstract int Age { get; set; }
    }
    public class Student : Person
    {
    public string Code { get; set; } = "N.A";
    public override string Name { get; set; } = "N.A";
    public override int Age { get; set; } = 0;
    public override string ToString()
    {
    return $"Code:{Code},Name:{Name},Age:{Age}";
    }
    }

    static void Main(string[] args)
    {
    var s = new Student()
    {
    Code = "001",
    Name = "Zara",
    Age = 10
    };
    System.Console.WriteLine($"Student Info:={s}");

    s.Age++;
    System.Console.WriteLine($"Student Info:={s}");
    }
  10. C# 索引器(Indexer) 允许一个对象可以像数组一样被索引,在某种程度上类似于属性(property),索引器定义的时候不带有名称,但带有 this 关键字,它指向对象实例。此外,索引器(Indexer)可被重载。索引器声明的时候也可带有多个参数,且每个参数可以是不同的类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    class IndexedNames
    {
    private string[] namelist = new string[size];
    static public int size = 10;
    public IndexedNames()
    {
    for (int i = 0; i < size; i++)
    {
    namelist[i] = "N. A.";
    }
    public string this[int index]
    {
    get{
    string tmp;
    if( index >= 0 && index <= size-1 ){
    tmp = namelist[index];
    }else{
    tmp = "";
    }
    return (tmp);
    }
    set{
    if( index >= 0 && index <= size-1 )
    {
    namelist[index] = value;
    }
    }
    }
    static void Main(string[] args)
    {
    IndexedNames names = new IndexedNames();
    names[0] = "Zara";
    names[1] = "Riz";
    names[2] = "Nuha";
    names[3] = "Asif";
    names[4] = "Davinder";
    names[5] = "Sunil";
    names[6] = "Rubic";
    for ( int i = 0; i < IndexedNames.size; i++ )
    {
    Console.WriteLine(names[i]);
    }
    Console.ReadKey();
    }
    }
  11. C# 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量,特别用于实现事件和回调方法。简单的说,委托可以将方法当作另一个方法的参数来进行传递,同时委托也是支持泛型的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // 委托常规写法
    public delegate void myDelegate(string str);

    public static void HellowChinese(string strChinese)
    {
    Console.WriteLine("Good morning," + strChinese);
    }

    myDelegate d = new myDelegate(HellowChinese);
    d("Liyu");

    // Action 是无返回值的泛型委托,至少 0 个参数,最多 16 个参数。
    // 简化了委托的调用:
    Action<string> action = HellowChinese;
    action("minmin");

    // Func 是有返回值的泛型委托,至少 0 个参数,最多 16 个参数,根据返回值泛型返回。
    // Func<T1,T2,,T3,int> 表示传入参数为 T1,T2,,T3(泛型)返回值为 int 的委托。
    public static string HelloEnglish(string strEnglish)
    {
    return "Hello." + strEnglish;
    }

    Func<string, string> f = HelloEnglish;
    Console.WriteLine(f("liyu"));

    // Predicate 是返回bool型的泛型委托
    // Predicate<int> 表示传入参数为 int 返回 bool 的委托
    // Predicate 有且只有一个参数,返回值固定为 bool
    public delegate bool Predicate<in T>(
    T obj
    )
  12. 事件是用于进程间通信,事件使用 发布-订阅(publisher-subscriber) 模型。

    • 发布器(publisher) 是一个包含事件委托定义的对象
    • 订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      public class Test
      {
      public delegate void ShowLog(string a);

      public event ShowLog log;

      public void setLog(string s)
      {
      if (log != null)
      {
      log(s);
      }
      else
      {
      Console.WriteLine("Event fired!");
      }
      }
      }

      private void Form1_Load(object sender, EventArgs e)
      {
      Test test = new Test();
      test.log += writeLog; // 接受者需要实现时间的处理,也可以使用以下匿名方式
      //test.log += delegate (string s) {
      // Console.WriteLine(s);
      //};
      // test.log += (s) => { Console.WriteLine(s); }; // 也可以使用 Lambda 表达式完成匿名委托
      test.setLog("hahaha");
      }

      private void writeLog(string s)
      {
      Console.WriteLine(s);
      }
  13. C# 集合(Collection)提供了动态数组(ArrayList)、哈希表(Hashtable)、排序列表(SortedList)、堆栈(Stack)、队列(Queue)和点阵列(BitArray)的支持。

  14. C# 泛型(Generic)作用:
    • 它有助于最大限度地重用代码、保护类型的安全以及提高性能。
    • 可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。可以使用这些泛型集合类来替代 System.Collections 中的集合类。
    • 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
    • 可以对泛型类进行约束以访问特定数据类型的方法。
    • 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
  15. C# 匿名方法是通过使用 delegate 关键字创建委托实例来声明:

    1
    2
    3
    4
    5
    6
    delegate void NumberChanger(int n);
    ...
    NumberChanger nc = delegate(int x)
    {
    Console.WriteLine("Anonymous Method: {0}", x);
    };
  16. C# 不安全代码或非托管代码是指使用了指针变量的代码块。当一个代码块使用 unsafe 修饰符标记时,C# 允许在函数中使用指针变量。

  17. C# 的线程使用示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 1. 原始写法,CallMethod 为无参数方法体
    ThreadStart threadStart = new ThreadStart(CallMethod);
    Thread thread = new Thread(threadStart);
    thread.Start();

    // 2. 原始写法,CallMethod 为有参数方法体,参数为 param
    ParameterizedThreadStart threadStart = new ParameterizedThreadStart(CallMethod);
    Thread thread = new Thread(threadStart);
    thread.Start(param);
  18. 挂起线程使用 Thread.Sleep(time); 方法

  19. 终止线程使用 Abort() 方法,线程内部使用 Thread.CurrentThread.Abort(n) ,其中 n 为异常状态代号。

  20. Thread.Join() 的作用是当 childThread 调用 Join 方法的时候,MainThread 就会被停止(阻塞),直到 childThread 线程执行完毕才恢复。

  21. 通过设置 thread.IsBackground = true; 使之变成后台线程。通俗点讲,当程序正常关闭时,后台线程会随即关闭,而前台线程不会。

  22. 使用匿名委托简化线程的创建过程:

    1
    2
    3
    4
    5
    Thread thread = new Thread(delegate()
    {
    ....
    });
    thread.Start();
  23. 线程中更新 UI 控件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Thread thread = new Thread(delegate() 
    {

    Action<string> action = (x) =>
    {
    button1.Text = x;
    };

    if (button1.InvokeRequired)
    {
    button1.Invoke(action, "我是好人");// 也可以使用异步 BeginInvoke 方法
    }
    });
    thread.Start();
  1. 子线程操作 UI 控件推荐使用 BackgroundWorker 组件。
    1
    2
    3
    4
    5
    6
    using (BackgroundWorker bw = new BackgroundWorker())
    {
    bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
    bw.DoWork += new DoWorkEventHandler(bw_DoWork);
    bw.RunWorkerAsync("Download");
    }