C#语言进阶学习笔记

1. 特性

特性(Attribute)是一种用于在程序运行时传递各种元素(例如类、方法、结构、枚举等)行为信息的声明性代码。使用特性可以将元数据(例如编译器指令、注释、描述、方法和类等信息)添加到程序中。.Net Framework提供了两种类型的特性,分别是预定义特性和自定义特性。

在C#中,特性具有以下属性:

  • 使用特性可以向程序中添加元数据,元数据是指程序中各种元素的相关信息,所有.NET程序中都包含一组指定的元数据;
  • 可以将一个或多个特性应用于整个程序、模块或者较小的程序元素(例如类或属性)中;
  • 特性可以像方法和属性一样接收自变量;
  • 程序可使用反射来检查自己的元数据或其他程序中的元数据

定义特性的语法如下:

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

其中,[]中用来定义特性的名称和值,positional_parameters用来指定基本信息,name_parameter用来指定可选信息

1.1 预定义特性

.Net Framework中提供了三个预定义的属性:AttributeUsage、Conditional和Obsolete

AttributeUsage

预定义特性AttributeUsage用来描述如何使用自定义特性类,其中定义了可以引用特性的项目类型。其语法如下:

[AttributeUsage(
    validon,
    AllowMultiple = allowmultiple,
    Inherited = inherited
)]
  • 参数validon用了定义特性可被放置的语言元素,它是枚举器AttributeTargets的值的集合,默认是AttributeTargets.All;
  • 参数allowmultiple(可选参数)用来为该特性的AllowMultiple属性(property)提供一个布尔值,默认为false,单用的,如果为true表示多用的
  • 参数inherited(可选参数)用来对该特性的Inherited属性(property)提供一个布尔值,默认为false,不被继承的,如果为true表示可被派生类继承

示例代码:

[AttributeUsage(AttributeTargets.Class |
                AttributeTargets.Constructor | 
                AttributeTargets.Field | 
                AttributeTargets.Method |
                AttributeTargets.Property,
                AllowMultiple=true)]

Conditional

预定义特性Conditional用来定义条件方法。Conditional属性通过测试条件编译符号来确定适用的条件,当运行到一个条件方法调用时,是否执行该调用,要根据出现该调用时是否已定义了此符号来确定。使用Conditional是封闭#if和#endif内部方法的替代方法,它更整洁、别致、减少出错的机会。其语法如下:

[Conditional(conditionalSymbol)]

示例代码:

#define DEBUG
using System;
using System.Diagnostics;

namespace Application
{
    class ConditionalTest
    {
        [Conditional("DEBUG")]
        static void Func1()
        {
            MyClass.Message("Func1函数");
        }
        
        [Conditional("BUG")]
        static void Func2()
        {
            MyClass.Message("Func2函数");
        }
        
        static void Main(string[] args)
        {
            MyClass.Message("Main函数");
            Func1();
            Func2();
            Console.ReadKey();
        }
    }
    public class MyClass
    {
        public static void Message(string msg)
        {
            Console.WriteLine(msg);
        }
    }
    // Main函数
    // Func1函数
}

Obsolete

预定义特性Obsolete用来标记不应该被使用的程序,可以使用它来通知编译器放弃某个目标元素。例如当你需要使用一个新方法来代替类中的某个旧方法时,就可以使用该特性将旧方法标记为Obsolete(过时)的并输出一条信息,来提示我们应该使用新方法替代旧方法。其语法格式如下:

[Obsolete(message)]

[Obsolete(message, iserror)]
  • 参数message是一个用来描述项目为什么过时以及用什么替代的字符串
  • 参数iserror是一个布尔值,默认值为false,如果设置为true,则编译器会把该项目当做一个错误

示例代码:

using System;
namespace Application
{
    class ObsoleteTest
    {
        [Obsolete("OldMethod已弃用,请改用NewMethod", true)]
        static void OldMethod()
        {
            Console.WriteLine("已弃用的函数");
        }
        static void NewMethod()
        {
            Console.WriteLine("新定义的函数");
        }
        static void Main(string[] args)
        {
            OldMethod();
        }
        // Main.cs(17,13): error CS0619: `Application.ObsoleteTest.OldMethod()' is obsolete: `OldMethod已弃用,请改用NewMethod'
        // Compilation failed: 1 error(s), 0 warnings
    }
}

1.2 自定义特性

.Net Framework允许创建自定义特性,自定义特性不仅可以用于存储声明性的信息,还可以在运行时被检索。创建并使用自定义特性分为四个步骤:

  • 声明自定义特性
  • 构建自定义特性
  • 在目标程序上应用自定义特性
  • 通过反射访问自定义特性

最后一步涉及编写一个简单的程序来读取元数据以便查找各种符号。元数据是有关数据或用于描述其他数据信息的数据,该程序应在运行时使用反射来访问属性。

声明自定义特性

自定义特性应该继承System.Attribute类,如下所示:

using System;
[AttributeUsage(
    AttributeTargets.All,
    AllowMultiple = true,
    Inherited = true
)]
public class SomethingAttribute : Attribute {}

构建自定义特性

public class SomethingAttribute : Attribute 
{
    private string name;
    private string data;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
    public string Data
    {
        get { return data; }
        set { data = value; }
    }
    public SomethingAttribute(string name, string data)
    {
        this.name = name;
        this.data = data;
    }
}

应用自定义特性

[SomethingAttribute("Amy", data = "Name is Amy")]
[SomethingAttribute("Tom", data = "Name is Tom")]
class Test {}

访问自定义特性

Type t = typeof(Test);
var something = t.GetCustomAttributes(typeof(SomethingAttribute), true);
foreach(SomethingAttribute each in something)
{
    Console.WriteLine("Name: {0}", each.Name);
    Console.WriteLine("Data: {0}", each.Data);
}

2. 反射

反射(Reflection)是指程序可以访问、检查、修改它本身行为或状态的一种能力,反射中提供了用来描述程序集、模块和类型的对象,可以使用反射动态地创建类型的实例,并将类型绑定到现有对象,或者从现有对象中获取类型,然后调用其方法或访问其字段和属性。如果代码中使用了特性,也可以利用反射来访问他们。

2.1 反射优缺点

优点:

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

缺点:

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

2.2 反射用途

  • 允许在运行时查看特性信息
  • 允许审查集合中的各种类型,以及实例化这些类型
  • 允许延迟绑定的方法和属性
  • 允许在运行时创建新类型,然后使用这些类型执行一些任务

2.3 查看元数据

使用反射可以查看特性信息,System.Reflection类的MemberInfo对象需要被初始化,用于发现与类相关的特性,为此可以定义目标类的一个对象:

using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
    public readonly string Url;
    public string Topic
    {
        get
        {
            return topic;
        }
        set
        {
            topic = value;
        }
    }
    public HelpAttribute(string url)
    {
        this.Url = url;
    }
    private string topic;
}
[HelpAttribute("Infomation to the class MyClass")]
class MyClass {}

namespace Application
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Reflection.MemberInfo info = typeof(MyClass);
            object[] attributes = info.GetCustomAttributes(true);
            for	(int i = 0; i < attributes.Length; i++)
            {
                System.Console.WriteLine(attributes[i]);  // HelpAttribute
            }
            Console.ReadKey();
        }
    }
}

3. 属性

属性(Property)是类、结构和接口的命名成员。类或结构中的成员变量或方法称为域,属性是域的扩展,且可以使用相同的语法来访问,他们使用访问器让私有域的值可被读写或操作。

属性不会确定存储位置,他们具有可读写或计算他们值的访问器。

例如,一个名为Student的类,带有age、name或code的私有域,我们不能在类的范围外直接访问这些域,但是可以拥有访问这些私有域的属性。

3.1 get/set访问器

属性的访问器包括获取或写入属性的可执行语句,访问器可包含一个get访问器或一个set访问器,或者两种都包含。

using System;
namespace Application
{
    class Teacher
    {
        private string name = "NAN";
        private int age = 0;
        
        public string Name
        {
            get 
            {
                return name;
            }
            set
            {
                name = value;
            }
        }
        public int Age 
        { 
            get 
            {
                return age;
            }
            set
            {
                age = value;
            }
        }
        public override string ToString()
        {
            return "Name = " + Name + ", Age = " + Age;
        }
    }
    class PropertiesTest
    {
        public static void Main(string[] args)
        {
            Teacher t = new Teacher();
            t.Name = "Zhang";
            t.Age = 26;
            Console.WriteLine("Teacher Info: {0}", t);
            t.Age += 1;
            Console.WriteLine("Teacher Info: {0}", t);
            // Teacher Info: Name = Zhang, Age = 26
            // Teacher Info: Name = Zhang, Age = 27
            Console.ReadKey();
        }
    }
}

3.2 抽象属性

抽象类在中可以拥有抽象属性,这些属性会在派生类中实现

using System;
namespace Application
{
    public abstract class Person
    {
        public abstract string Name { get; set; }
        public abstract int Age { get; set; }
    }
    class Teacher : Person
    {
        public string Project { get; set; } = "NAN";
        public override string Name { get; set; } = "NAN";
        public override int Age { get; set; } = 0;
        public override string ToString()
        {
            return $"Project = {Project}, Name = {Name}, Age = {Age}";
        }
    }
    class AbstractPropertiesTest
    {
        public static void Main(string[] args)
        {
            var t = new Teacher()
            {
                Project = "Math",
                Name = "Zhang",
                Age = 26
            };
            Console.WriteLine($"Teacher Info: {t}");
            t.Age++;
            Console.WriteLine($"Teacher Info: {t}");
			// Teacher Info: Project = Math, Name = Zhang, Age = 26
			// Teacher Info: Project = Math, Name = Zhang, Age = 27
            Console.ReadKey();
        }
    }
}

4. 索引器

索引器(Indexer)是类中的一个特殊成员,他能够让对象以类似数组的形式来操作,使程序看起来更直观、更易编写。索引器与属性类似,在定义索引器时同样会get和set访问器,不同的是,访问索引器需要提供相应的参数。

4.1 索引器定义

索引器允许对象使用下标的方式来访问,定义时,该类的行为会像一个虚拟数组一样,可以使用数组访问符[ ]来访问类的成员。

其语法为:

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

4.2 索引器用途

索引器实例数据分为更小的部分,并索引每部分,获取或设置每部分。索引器定义时不带名称,但带this关键字,它指向对象实例。

using System;
namespace Application
{
    class IndexedNames
    {
        private string[] nameList = new string[size];
        static public int size = 5;
        public IndexedNames()
        {
            for	(int i = 0; i < size; i++)
            {
                nameList[i] = "NAN";
            }
        }
        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] = "AAA";
            names[1] = "BBB";
            names[2] = "CCC";
            for (int i = 0; i < IndexedNames.size; i++)
            {
                Console.WriteLine(names[i]);
            }
            Console.ReadKey();
        }
        // AAA
        // BBB
        // CCC
        // NAN
        // NAN
    }
}

4.3 重载索引器

索引器可以被重载,而且在声明索引器时也可以带多个参数,每个参数可以是不同的类型,另外,索引器的索引不必是整数,也可以是其他类型,例如字符串类型。

using System;
namespace Application
{
    class IndexedNames
    {
        private string[] nameList = new string[size];
        static public int size = 5;
        public IndexedNames()
        {
            for	(int i = 0; i < size; i++)
            {
                nameList[i] = "NAN";
            }
        }
        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;
                }
            }
        }
        public int this[string name]
        {
            get
            {
                int index = 0;
                while (index < size)
                {
                    if (nameList[index] == name)
                    {
                        return index;
                    }
                    index++;
                }
                return index;
            }
        }
        static void Main(string[] args)
        {
            var names = new IndexedNames();
            names[0] = "AAA";
            names[1] = "BBB";
            names[2] = "CCC";
            for (int i = 0; i < IndexedNames.size; i++)
            {
                Console.WriteLine(names[i]);
            }
            Console.WriteLine("'BBB'的索引为: {0}", names["BBB"]);
            Console.ReadKey();
        }
        // AAA
        // BBB
        // CCC
        // NAN
        // NAN
        // 'BBB'的索引为: 1
    }
}

5. 委托

委托(Delegate)类似于C或C++中函数的指针,是存有对某个方法的引用的一种引用类型的变量。引用可在运行时被改变。

委托特别用于实现事件和回调方法,所有的委托都派生自System.Delegate类。

5.1 声明委托

委托声明决定了可由委托引用的方法。委托可指向一个与其具有相同标签的方法。

声明委托的语法如下:

delegate <return type> <delegate-name> <parameter list>

5.2 实例化委托

声明委托类型后,委托对象必须由new关键字来创建,且与一个特定的方法关联。当创建委托时,传递到new语句的参数就像方法调用一样书写,但是不带参数。

示例如下:

using System;
// 声明委托
delegate int NumberChanger(int n);
namespace Application
{
    class DelegateTest1
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }
        public static int MulNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }
        
        static void Main(string[] args)
        {
            // 实例化委托
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MulNum);
            // 使用委托对象调用方法
            nc1(25);
            Console.WriteLine("Value of Num: {0}", getNum());  // Value of Num: 35
            nc2(5);
            Console.WriteLine("Value of Num: {0}", getNum());  // Value of Num: 175
            Console.ReadKey();
        }
    }
}

5.3 委托多播

委托对象可以使用+运算符进行合并。一个合并委托调用它所合并的两个委托,只有同类型的委托才能被合并。-运算符可用于从合并的委托中移除组件委托。

使用委托合并或移除的操作,可以创建一个委托被调用时要调用的方法的调用类别,这称为委托的多播。

示例如下:

using System;
// 声明委托
delegate int NumberChanger(int n);
namespace Application
{
    class DelegateTest2
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }
        public static int MulNum(int q)
        {
            num *= q;
            return num;
        }
        public static int getNum()
        {
            return num;
        }
        
        static void Main(string[] args)
        {
            // 实例化委托
            NumberChanger nc1 = new NumberChanger(AddNum);
            NumberChanger nc2 = new NumberChanger(MulNum);
            NumberChanger nc = nc1 + nc2;
            // 调用多播
            nc(5);
            Console.WriteLine("Value of Num: {0}", getNum());  // Value of Num: 75
            Console.ReadKey();
        }
    }
}

6. 事件

事件(Event)基本上说是一个用户操作,例如按键、点击、鼠标移动等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。C#中使用事件机制实现线程间的通信。

6.1 声明事件

在类的内部声明事件,首先必须声明该事件的委托类型,如:

public delegate void LogHandler(string status);

然后,声明事件本身,使用event关键字:

public event LogHandler EventLog;

上面代码定义了一个名为EventLog的事件和一个名为LogHandler的委托,该事件在生成时会调用委托。

6.2 使用事件

示例1:

using System;
namespace Application
{
    public class MyEvent
    {
        // 定义一个委托
        public delegate void MyDelegate();
        // 定义一个事件
        public MyDelegate MyDelegateEvent;
        // 定义一个事件的触发函数
        public void OnMyDelegateEvent()
        {
            if (MyDelegateEvent != null)
            {
                // 执行事件
                MyDelegateEvent();
            }
        }
    }
    class EventTest
    {
        public static void PutOutChar()
        {
            Console.WriteLine("I was fired");
        }
        public static void PutOutChar2()
        {
            COnsole.WriteLine("I was fired too");
        }
        static void Main(string[] args)
        {
            // 实例化MyEvent类
            MyEvent myEvent = new MyEvent();
            // 注册一个事件
            myEvent.MyDelegateEvent += new MyEvent.MyDelegate(PutOutChar);
            myEvent.MyDelegateEvent += new MyEvent.MyDelegate(PutOutChar2); 
            // 执行触发事件的函数
            myEvent.OnMyDelegateEvent();
            // 解绑一个事件
            myEvent.MyDelegateEvent -= new MyEvent.MyDelegate(putOutChar);
            // 再次执行触发事件的函数
            myEvent.OnMyDelegateEvent();
            Console.ReadKey();
        }
        // I was fired
		// I was fired too
		// I was fired too
    }
}

示例2:

using System;
namespace Application
{
    // 发布器
    public class Publisher
    {
        private string value;
        // 定义一个委托
        public delegate void MyDelegate(string str);
        // 定义一个事件
        public event MyDelegate MyEvent;
        public void SetValue(string s)
        {
            value = s;
            // 触发事件
            MyEvent(value);
        }
    }
    
    // 订阅器
    public class Subscriber
    {
        public void PrintF(string str)
        {
            Console.WriteLine(str);
        }
    }
    
    class Test
    {
        static void Main(string[] args)
        {
            Publisher p = new Publisher();
            Subscriber s = new Subscriber();
            p.MyEvent += new Publisher.MyDelegate(s.PrintF);
            p.SetValue("Hello");  // Hello
        }
    }
}

7. 集合

集合(Collection)类是专门用于数据存储和检索的类,这些类提供了对栈(Stack)、队列(Queue)、列表(List)和哈希表(Hashtable)的支持。大多数集合类实现了相同的接口。

集合类服务于不同的目的,如为元素动态分配内存,基于索引访问列表项等,这些类创建Object类的对象的集合,在C#在,Object类是所有数据类型的基类。

7.1 动态数组ArrayList

动态数组ArrayList代表了可被单独索引的对象的有序集合,它基本上可以替代一个数组。与数组不同的是,可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。

下表为ArrayList类的常用属性:

属性描述属性描述
Capacity获取或设置ArrayList可包含的元素个数Count获取ArrayList中实际包含的元素个数
IsFixedSize获取一个值,表示ArrayList是否具有固定大小IsReadOnly获取一个值,表示ArrayList是否只读
IsSynchronized获取一个值,表示访问ArrayList是否同步(线程安全)Item[Int32]获取或设置指定索引处的元素
SyncRoot获取一个对象用于同步访问ArrayList

下表为ArrayList类的常用方法:

方法名描述方法名描述
public virtual int Add(object value);在ArrayList的列表末尾添加一个对象public virtual void AddRange(ICollection c);在ArrayList的列表末尾添加ICollection的元素
public virtual void Clear();在ArrayList中移除所有元素public virtual bool Contains(object item);判断某个元素是否存在ArrayList中
public virtual ArrayList GetRange(int index, int count);返回一个ArrayList,表示源ArrayList中元素的子集public virtual int IndexOf(object);返回某个值在ArrayLust中第一次出现的索引,索引从零开始
public virtual void Insert(int index, object value);在ArrayList指定索引处插入一个元素public virtual void InsertRange(int index, ICollection c);在ArrayList的指定索引处,插入某个集合的元素
public virtual void Remove(object obj);从ArrayList中移除第一次出现的指定对象public virtual void RemoveAt(int index);移除ArrayList的指定索引处的元素
public virtual void RemoveRange(int index, int count);在ArrayList中移除某个范围的元素public virtual void Reverse();逆转ArrayList在元素的顺序
public virtual void SetRange(int index, ICollection c);复制某个集合的元素到ArrayList中某个范围的元素上public virtual void Sort();对ArrayList中的元素进行排序
public virtual void TrimToSize();设置容量为ArrayList中元素的实际个数
using System;
using System.Collections;

namespace Application
{
    class ArrayListTest
    {
        static void Main(string[] args)
        {
            ArrayList arrList = new ArrayList();
            arrList.Add(42);
            arrList.Add(35);
            arrList.Add(55);
            arrList.Add(29);
            arrList.Add(7);
            Console.WriteLine("Capacity: {0}", arrList.Capacity);  // Capacity: 8
            Console.WriteLine("Count: {0}", arrList.Count); // Count: 5
            foreach (int e in arrList)
            {
                Console.Write(e + " ");
            }
            // 42 35 55 29 7
            Console.WriteLine();
            Console.Write("Sorted: ");
            arrList.Sort();
            foreach (int e in arrList)
            {
                Console.Write(e + " ");
            }
            // Sorted: 7 29 35 42 55
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

7.2 哈希表Hashtable

哈希表Hashtable类代表了一系列基于键的哈希代码组织起来的键/值对。它是由键来访问集合中的元素。哈希表中的每一项都有一个键/值对,键用于访问集合中的项目。

下表为Hashtable类的常用属性:

属性描述属性描述
Keys获取一个ICollection,包含Hashtable中的键Values获取一个ICollection,包含Hashtable中的值
IsFixedSize获取一个值,表示Hashtable是否具有固定大小IsReadOnly获取一个值,表示Hashtable是否只读
Count获取Hashtable中实际包含的键值对个数Item获取或设置指定键相关的值

下表为Hashtable类的常用方法:

方法名描述方法名描述
public virtual void Add(object key, object value)向Hashtable添加一个带有指定键和值的元素public virtual void Clear()从Hashtable中移除所有的元素
public virtual bool ContainsKey(object key)判断Hashtable是否包含指定的键public virtual bool ContainsValue(object value)判断Hashtable是否包含指定的值
public virtual void Remove(object key);从Hashtable移除带有指定的键的元素
using System;
using System.Collections;

namespace Application
{
    class HashtableTest
    {
        static void Main(string[] args)
        {
            Hashtable hashT = new Hashtable();
            hashT.Add("001", "AAA");
            hashT.Add("002", "BBB");
            hashT.Add("003", "CCC");
            hashT.Add("004", "DDD");
            hashT.Add("005", "EEE");
            if (hashT.ContainsValue("FFF"))
            {
                Console.WriteLine("The 'BBB' is already in the table");
            }
            else
            {
                hashT.Add("006", "FFF");
            }
            // 获取键的集合
            ICollection keys = hashT.Keys;
            foreach (string k in keys)
            {
                Console.WriteLine(k + ": " + hashT[k]);
            }
            // 002: BBB
			// 001: AAA
			// 003: CCC
			// 004: DDD
			// 006: FFF
			// 005: EEE
            Console.ReadKey();
        }
    }
}

7.3 排序列表SortedList

排序列表SortedList类代表了一系列按照键来排序的键/值对,这些键值对可以通过键或索引来访问。

排序列表是数组和哈希表的组合,包含了一个可使用键或索引访问各项的列表。如果使用索引访问各项,则它是一个动态数组,如果使用键访问各项,则它是一个哈希表。集合中的各项总是按键值排序。

下表为SortedList类的常用属性:

属性描述属性描述
Capacity获取或设置SortedList可包含的元素个数Count获取SortedList中实际包含的元素个数
IsFixedSize获取一个值,表示SortedList是否具有固定大小IsReadOnly获取一个值,表示SortedList是否只读
Item获取或设置SortedList中指定键相关的值Keys获取SortedList中的键
Values获取SortedList中的值

下表为ArrayList类的常用方法:

方法名描述方法名描述
public virtual int Add(object key, object Value);向SortedList添加一个带有指定键和值的元素public virtual bool ContainsKey(object key);判断SortedList是否存在指定的键
public virtual void Clear();在SortedList中移除所有元素public virtual bool ContainsValue(object value);判断SortedList是否存在指定的值
public virtual object GetByIndex(int index);获取SortedList的指定索引处的值public virtual object GetKey(int index);获取SortedList的指定索引处的键
public virtual IList GetKeyList();获取SortedList中的键public virtual IList GetValueList();获取SortedList中的值
public virtual void Remove(object key);从SortedList中移除带有指定的键的元素public virtual void RemoveAt(int index);移除SortedList中指定索引处的元素
public virtual int IndexOfKey(object key);返回SortedList中的指定键的索引public virtual int IndexOfValue(object key);返回SortedList中的指定值第一次出现的索引
public virtual void TrimToSize();设置容量为SortedList中元素的实际个数
using System;
using System.Collections;

namespace Application
{
    class SortedListTest
    {
        static void Main(string[] args)
        {
            SortedList sortedL = new SortedList();
            sortedL.Add("001", "AAA");
            sortedL.Add("002", "BBB");
            sortedL.Add("003", "CCC");
            sortedL.Add("004", "DDD");
            sortedL.Add("005", "EEE");
            if (sortedL.ContainsValue("FFF"))
            {
                Console.WriteLine("The 'BBB' is already in the table");
            }
            else
            {
                sortedL.Add("006", "FFF");
            }
            // 获取键的集合
            ICollection keys = sortedL.Keys;
            foreach (string k in keys)
            {
                Console.WriteLine(k + ": " + sortedL[k]);
            }
			// 001: AAA
            // 002: BBB
			// 003: CCC
			// 004: DDD
			// 005: EEE
            // 006: FFF
            Console.ReadKey();
        }
    }
}

7.4 堆栈Stack

堆栈Stack代表了一个后进先出的对象集合,当需要对各项进行后进先出的访问时,则使用堆栈。当在列表中添加一项,称为推入元素,当从列表中移除一项时,称为弹出元素。

下表为Stack类的常用属性:

属性描述
Count获取Stack中包含的元素个数

下表为Stack类的常用方法:

方法名描述方法名描述
public virtual void Clear();从Stack中移除所有的元素public virtual bool Contains(object obj);判断某个元素是否在Stack中
public virtual object Peek();返回在Stack的顶部的对象,但不移除它public virtual object Pop();移除并返回Stack的顶部的对象
public virtual void Push(object obj);向Stack的顶部添加一个对象public virtual object[] ToArray();复制Stack到一个新的数组中
using System;
using System.Collections;

namespace Application
{
    class StackTest
    {
        static void Main(string[] args)
        {
            Stack st = new Stack();
            st.Push('A');
            st.Push('B');
            st.Push('C');
            st.Push('D');
            foreach (char c in st)
            {
            	Console.Write(c + " "); 
            }
            // D C B A
            Console.WriteLine();
            st.Push('E');
            st.Push('F');
            Console.WriteLine("The next popable value in stack: {0}", st.Peek());
            // The next popable value in stack: F
            foreach (char c in st)
            {
            	Console.Write(c + " "); 
            }
            // F E D C B A
            Console.WriteLine();
            st.Pop();
            st.Pop();
            st.Pop();
            foreach (char c in st)
            {
            	Console.Write(c + " "); 
            }
            // C B A
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

7.5 队列Queue

队列Queue代表了一个先进先出的对象集合,当需要对各项进行先进先出的访问时,则使用队列。当在列表中添加一项,称为入队,当从列表中移除一项,称为出队。

下表为Queue类的常用属性:

属性描述
Count获取Queue中包含的元素个数

下表为Stack类的常用方法:

方法名描述方法名描述
public virtual void Clear();从Queue中移除所有的元素public virtual bool Contains(object obj);判断某个元素是否在Queue中
public virtual object Dequeue();移除并返回在Queue的开头的对象public virtual void Enqueue(object obj);向Queue的末尾添加一个对象
public virtual object[] ToArray();复制Queue到一个新的数组中public virtual void TrimToSize();设置容量为Queue中元素的实际个数
using System;
using System.Collections;

namespace Application
{
    class QueueTest
    {
        static void Main(string[] args)
        {
            Queue q = new Queue();
            q.Enqueue('A');
            q.Enqueue('B');
            q.Enqueue('C');
            q.Enqueue('D');
            foreach (char c in q)
            {
            	Console.Write(c + " "); 
            }
            // A B C D
            Console.WriteLine();
            q.Enqueue('E');
            q.Enqueue('F');
            foreach (char c in q)
            {
            	Console.Write(c + " "); 
            }
            // A B C D E F
            Console.WriteLine();
            q.Dequeue();
            q.Dequeue();
            foreach (char c in q)
            {
            	Console.Write(c + " "); 
            }
            // C D E F
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

7.6 点阵列BitArray

点阵列BitArray类管理一个紧凑型的位值数组,它是由布尔值来表示,其中true表示位是开启的,false表示位是关闭的,当你需要存储位,但是事先不知道位数时,则使用点阵列。可以使用整型索引从点阵列集合中访问各项,索引从零开始。

下表为BitArray类的常用属性:

属性描述
Count获取BitArray中包含的元素个数
IsReadyOnly获取一个值,表示BitArray是否只读
Item获取或设置BitArray中指定位置的位的值
Length获取或设置BitArray中的元素个数

下表为BitArray类的常用方法:

方法名描述方法名描述
public BitArray And(BitArray value);对当前的BitArray中的元素和指定的BitArray中的相应元素执行按位与操作public bool Get(int index);获取BitArray中指定位置的位的值
public BitArray Not();把当前BitArray中的位值反转,以便设置为true的元素为false,设置为false的元素为truepublic BitArray Or(BitArray value);对当前的BitArray中的元素和指定的BitArray中的相应元素执行按位或操作
public void Set(int index, bool value);把BitArray中指定位置的位设置为指定的值public void SetAll(bool value);把BitArray中所有位设置为指定的值
public BitArray Xor(BitArray value);对当前的BitArray中的元素和指定的BitArray中的相应元素执行按位异或操作
using System;
using System.Collections;

namespace Application
{
    class BitArrayTest
    {
        static void Main(string[] args)
        {
            BitArray ba1 = new BitArray(8);
            BitArray ba2 = new BitArray(8);
            byte[] a = {60};
            byte[] b = {13};
            ba1 = new BitArray(a);
            ba2 = new BitArray(b);
            Console.WriteLine("BitArray ba1: 60");
            for (int i = 0; i < ba1.Count; i++)
            {
                Console.Write("{0, -6}", ba1[i]);
            }
            // False False True True True True False False
            Console.WriteLine();
            Console.WriteLine("BitArray ba2: 13");
            for (int i = 0; i < ba2.Count; i++)
            {
                Console.Write("{0, -6}", ba2[i]);
            }
            // True False True True False False False False
            Console.WriteLine();

            
            BitArray ba3 = new BitArray(8);
            ba3 = ba1.And(ba2);
            Console.WriteLine("BitArray ba3 After And Operation: 12");
            for (int i = 0; i < ba3.Count; i++)
            {
                Console.Write("{0, -6}", ba3[i]);
            }
            // False False True True False False False False
            Console.WriteLine();    
            Console.ReadKey();
        }
    }
}

8. 泛型

泛型Generic通过数据类型的代替参数编写类或方法,允许编写一个可以与任何类型一起工作的方法或类。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。

示例如下:

using System;
using System.Collections.Generic;

namespace Application
{
    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 GenericTest
    {
        static void Main(string[] args)
        {
            // 声明一个整型数组
            MyGenericArray<int> intArray = new MyGenericArray<int>(5);
            // 设置值
            for (int i = 0; i < 5; i++)
            {
                intArray.SetItem(i, i*i);
            }
            // 获取值
            for (int i = 0; i < 5; i++)
            {
                Console.Write(intArray.GetItem(i) + " ");
            }
            // 0 1 4 9 16
            Console.WriteLine();
            // 声明一个字符数组
            MyGenericArray<char> charArray = new MyGenericArray<char>(5);
            // 设置值
            for (int i = 0; i < 5; i++)
            {
                charArray.SetItem(i, (char)(i+97));
            }
            // 获取值
            for (int i = 0; i < 5; i++)
            {
                Console.Write(charArray.GetItem(i) + " ");
            }
            // a b c d e
            Console.WriteLine();
            Console.ReadKey();
        }
    }
}

8.1 特性

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

  • 有助于最大限度地重用代码、保护类型的安全以及提高性能
  • 可以创建泛型集合类,使用泛型集合类来代替System.Collections中的集合类
  • 可以创建自己的泛型接口、泛型类、泛型方法、泛型事件、泛型委托。
  • 对泛型类进行约束以访问特定数据类型的方法
  • 关于泛型数据类型中使用的类型的信息可运行时通过使用反射获取

8.2 泛型方法

可以通过类型参数声明泛型方法

using System;
using System.Collections.Generic;

namespace Application
{
    class GenericTest
    {
        static void Swap<T>(ref T l, ref T r)
        {
            T temp;
            temp = l;
            l = r;
            r = temp;
        }
        static void Main(string[] args)
        {
            int a, b;
            char c, d;
            a = 10;
            b = 20;
            c = 'M';
            d = 'N';
            Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}", a, b, c, d);
            // a = 10, b = 20, c = M, d = N
            Swap<int>(ref a, ref b);
            Swap<Char>(ref c, ref d);
            Console.WriteLine("a = {0}, b = {1}, c = {2}, d = {3}", a, b, c, d);
            // a = 20, b = 10, c = N, d = M
            Console.ReadKey();
            
        }
    }
}

8.3 泛型委托

可以通过类型参数定义泛型委托,如delegate T NumberChange<T>(T n);

示例如下:

using System;
using System.Collections.Generic;

delegate T NumberChanger<T>(T n);
namespace Application
{
    class GenericTest
    {
        static int num = 10;
        public static int AddNum(int p)
        {
            num += p;
            return num;
        }
        public static int MultNum(int q)
        {
            num *= q;
            return num;
        }
        public static int GetNum()
        {
            return num;
        }
        static void Main(string[] args)
        {
            NumberChanger<int> nc1 = new NumberChanger<int>(AddNum);
            NumberChanger<int> nc2 = new NumberChanger<int>(MultNum);
            nc1(5);
            Console.WriteLine("num = {0}", GetNum());  // num = 15
            nc2(5);
            Console.WriteLine("num = {0}", GetNum());  // num = 75
            Console.ReadKey();
        }
    }
}

8.4 泛型约束

在声明泛型方法或泛型类时,可以给泛型加上一定的约束来满足特定的一些条件

如:

using System;
using System.Collections.Generic;

namespace Application
{
    public class Helper<T> where T : new()
}

泛型限定条件:

  • T : 结构 (类型参数必须是值类型,可以指定出Nullable以外的任何值类型)
  • T : 类 (类型参数必须是引用类型,包括任何类、接口、委托或数组类型)
  • T : new() (类型参数必须具有无参数的公共构造函数,当与其他约束一起使用new()约束必须最后指定)
  • T : <基类名> (类型参数必须是指定的基类或派生自指定的基类)
  • T : <接口名称> (类型参数必须是指定的接口或实现指定的接口,可以指定多个接口约束。约束接口也可以是泛型的。

9. 匿名方法

匿名方法Anonymous methods提供了一种传递代码块作为委托参数的技术,匿名方法是没有名称只有主体的方法。在匿名方法中不需要指定返回类型,它是从方法主体内的return语句推断的。

9.1 匿名方法的语法

匿名方法是通过使用delegate关键字创建的委托实例来声明的。

delegate void NumberChanger(int n);

NumberChanger nc = delegate(int x)
{
    Console.WriteLine("Anonymous methods: {0}", x);  // 匿名方法的主体
};

委托可以通过匿名函数调用,也可以通过普通有名称的函数调用,只需要向委托对象中传递相应的方法参数即可。注:匿名函数主体后面要以;结尾。

示例如下:

using System;

namespace Application
{
    class AnonymousTest
    {
        delegate void NumberChanger(int n);
        static int num = 10;
        // 命名函数
        public static void AddNum(int p)
        {
            num += p;
            Console.WriteLine("命名函数:{0}", num);
        }
        public static void MultNum(int q)
        {
            num *= q;
            Console.WriteLine("命名函数:{0}", num);
        }
        static void Main(string[] args)
        {
            NumberChanger nc = delegate(int x)
            {
            	Console.WriteLine("匿名函数:{0}", x);  
            };
            // 使用匿名函数调用委托
            nc(10);  // 匿名函数:10
            // 使用命名函数实例化委托
            nc = new NumberChanger(AddNum);
            nc(5);  // 命名函数:15
            nc = new NumberChanger(MultNum);
            nc(5);  // 命名函数:75
            Console.ReadKey();
        }
    }
}

10. 不安全代码

为了保持类型的安全性,默认情况下C#是不支持指针的,但是如果使用unsafe关键字来修饰类或类中的成员,则会被视为不安全代码。C#允许不安全代码中使用指针变量。

在公共语言运行中,不安全代码是指无法验证的代码,需要程序员来保证代码的安全性,不安全代码并不意味着一定是危险的。

10.1 指针变量

指针也是变量,但是它的值是另一个变量的内存地址,在使用指针前需要先声明指针。

示例说明示例说明
int* pp是指向整数的指针double* pp是指向双精度数的指针
float* pp是指向浮点数的指针int** pp是指向整数的指针的指针
int*[] pp是指向整数的指针的一维数组char* pp是指向字符的指针
void* pp是指向未知类型的指针

与声明变量相同,我们同样可以在一行代码中同时声明多个指针,如下:

int* p1, p2, p3;  // 同时定义p1、p2、p3三个整数指针

注意:指针不能从对象中继承,并且装箱和拆箱也不支持指针,但是不同的指针类型以及指针与整型之间可以进行转换。

using System;

namespace Application
{
    class PointerTest
    {
        static unsafe void Main(string[] args)
        {
            double f = 3.1415;
            double* p = &f;
            Console.WriteLine("数据的内容是:{0}", f);  // 字符串的内容是: 3.1415
            Console.WriteLine("数据在内存中的地址是:{0}", (int)p);  // 字符串在内存中的地址是: 12569862
            Console.ReadKey();
        }
    }
}

注意:在编译上述代码时需要在编译命令中添加-unsafe,例如csc -unsafe pointerTest.cs

10.2 使用指针检索数据的值

可以使用ToString()来获取指针变量所指向的数据的值,如下所示:

using System;

namespace Application
{
    class PointerTest
    {
        unsafe
        {
            int var = 123456;
            int* p = &var;
            Console.WriteLine("变量var的值为:{0}", var);  // 变量var的值为: 123456
            Console.WriteLine("指针p指向的值为:{0}", p->ToString());  // 指针p指向的值为: 123456
            Console.WriteLine("指针p的值为:{0}", (int)p);  // 指针p的值为: 15332624
           
        }
        Console.ReadKey();
    }
}

10.3 将指针作为参数传递给函数

可以将指针变量作为参数传递给函数,如下所示:

using System;

namespace Application
{
    class PointerTest
    {
        public unsafe void Swap(int* p, int* q)
        {
            int temp = *p;
            *p = *q;
            *q = temp;
        }
        
        public unsafe static void Main(string[] args)
        {
            PointerTest p = new PointerTest();
            int var1 = 10;
            int var2 = 20;
            int* x = &var1;
            int* y = &var2;
            Console.WriteLine("调用Swap函数前:var1 = {0}, var2 = {1}", var1, var2);
            // 调用Swap函数前:var1 = 10, var2 = 20
            p.Swap(x, y);
            Console.WriteLine("调用Swap函数后:var1 = {0}, var2 = {1}", var1, var2);
            // 调用Swap函数后:var1 = 20, var2 = 10
            Console.ReadKey();
        }
    }
}

10.4 使用指针访问数组元素

在C#中,数组和指向该数组且与数组名称相同的指针是不同的数据类型,例如int* pint[] p就是不同的数据类型。可以增加指针p的值,因为它在内存中不是固定的,但数组地址在内存中是固定的,因此不能增加数组p的值,如果需要使用指针变量访问数组数据,则可以使用fixed关键字来固定指针。

using System;

namespace Application
{
    class PointerTest
    {
        public unsafe static void Main(string[] args)
        {
            int[] list = {10, 100, 200};
            fixed(int *ptr = list);
            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine("List[{0}]的内存地址为:{1}", i, (int)(ptr + i));
                Console.WriteLine("List[{0}]的值为:{1}", i, *(ptr + i));
                
            }
            // list[0] 的内存地址为:51981272
            // list[0] 的值为:10
            // list[1] 的内存地址为:51981276
            // list[1] 的值为:100
            // list[2] 的内存地址为:51981280
            // list[2] 的值为:200
        }
    }
}

11. 正则表达式

正则表达式时一种匹配文本输入的模式。.Net框架提供了允许这种匹配的正则表达式引擎。模式由一个或多个字符、运算符和结构组成。

11.1 定义正则表达式

用于定义正则表达式的各种类型的字符、运算符和结构有:

  • 字符转义,正则表达式中的反斜杠\用来表示它后面跟随的字符时特殊字符,具有特殊的含义。如\a、\b、\t等
  • 字符类,字符类可以与一个字符串中的任何一个字符相匹配。如\w、[character_group]、\p{name}等
  • 定位符,可以根据字符串出现的具体位置来判断匹配是成功还是失败。如^、$、\A等
  • 分组结构,描述了正则表达式的子表达式,并捕获输入字符串的子字符串。如(\w)\1、\w+(?=\.)、(?<double>\w)\k<double>等
  • 限定符,用来指定在字符串中必须存在某个元素才能匹配成功。如*、+、?等
  • 反向引用构造,允许先前匹配的子表达式随后在相同的正则表达式中进行标志。如\number、\k<name>
  • 备用构造,用于修改正则表达式以启用either/or匹配。如|、(?(expresion) yes | no)、(?(name)yes | no)
  • 替换,替换模式中使用的正则表达式,如$number、${name}、$&等
  • 杂项构造,如(?#注释)、#[行尾]、(?imnsx-imnsx)

11.2 Regex类

rege类用于使用一个正则表达式,下表列出来Regex类中一些常用的方法:

方法描述方法描述
public bool IsMatch( string input )指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项public bool IsMatch( string input, int startat )指示 Regex 构造函数中指定的正则表达式是否在指定的输入字符串中找到匹配项,从字符串中指定的位置开始查找
public static bool IsMatch( string input, string pattern )指示指定的正则表达式是否在指定的输入字符串中找到匹配项public MatchCollection Matches( string input )在指定的输入字符串中搜索正则表达式的所有匹配项
public string Replace( string input, string replacement )在指定的输入字符串中,把所有匹配正则表达式模式的所有匹配的字符串替换为指定的替换字符串public string[] Split( string input )把输入字符串分割为子字符串数组,根据在 Regex 构造函数中指定的正则表达式模式定义的位置进行分割
using System;
using System.Text.RegularExpressions;

namespace Application
{
    class RegexTest
    {
        private static void ShowMatch(string text, string expr)
        {
            MatchCollection mc = Regex.Matches(text, expr);
            foreach (Match m in mc)
            {
                Console.WriteLine(m);
            }
        }
        static void Main(string[] args)
        {
            string str = "make and then manage your maze";
            ShowMatch(str, @"\bm\S*e\b");
            // make
            // manage
            // maze
            
            Console.ReadKey();
        }
    }
}

12. 多线程

多线程就是多个线程同时工作的过程,可将线程看做程序的执行路径,每个线程定义了一个独特的控制流,用来完成特定的任务。

多线程可以节省CPU资源,同时提高应用程序的执行效率。

12.1 线程生命周期

线程声明周期开始于我们创建System.Theading.Thread类对象的时候,当线程被终止或完成执行时生命周期终止。

下面列出来线程生命周期中的各种状态:

  • 未启动状态:当线程实例被创建但Start方法未被调用时的状况;
  • 就绪状态:当线程准备好运行并等待CPU周期时的状况;
  • 不可运行状态:
    • 已经调用Sleep方法
    • 已经调用Wait方法
    • 通过I/O操作阻塞
  • 死亡状态:当线程已完成执行或已中止时的状况。

12.2 主线程

在多线程中执行的第一个线程称为主线程,C#程序开始执行时,将自动创建主线程,使用Thread类创建的线程称为子线程,可以使用Thread类的CurrentThread属性访问线程。

using System;
using System.Threading;

namespace Application
{
    class ThreadTest
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "主线程";
            Console.WriteLine("这是{0}", th.Name);  // 这是主线程
            
            Console.ReadKey();
        }
    }
}

12.3 Thread类的常用属性和方法

下表为Thread类的常用属性:

属性描述属性描述
CurrentContext获取线程正在其中执行的当前上下文CurrentCulture获取或设置当前线程的区域性
CurrentPrincipal获取或设置线程的当前负责人CurrentThread获取当前正在运行的线程
CurrentUICulture获取或设置资源管理器使用的当前区域性以便在运行时查找区域特定的资源ExecutionContext获取一个ExecutionContext对象,该对象包含有关当前线程的各种上下文信息
IsAlive获取一个值,该值指示当前线程的执行状态IsBackground获取或设置一个值,该值指示某个线程是否为后台线程
IsThreadPoolThread获取一个值,该值指示线程是否属于托管线程池ManagedThreadId获取当前托管线程的唯一标识符
Name获取或设置线程的名称Priority获取或设置一个值,该值指示线程的调度优先级
ThreadState获取一个值,该值包含当前线程的状态

下表为Thread类的常用方法:

方法名描述方法名描述
public void Abort()在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程public static LocalDataStoreSlot AllocateDataSlot()在所有线程上分配未命名的数据槽
public static LocalDataStoreSlot AllocateNamedDataSlot( string name)在所有线程上分配已命名的数据槽public static void BeginCriticalRegion()通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常的影响可能会危害应用程序域中的其他任务
public static void BeginThreadAffinity()通知主机托管代码将要执行依赖于当前物理操作系统线程的标识的指令public static void EndCriticalRegion()通知主机执行将要进入一个代码区域,在该代码区域内线程中止或未经处理的异常仅影响当前任务。
public static void EndThreadAffinity()通知主机托管代码已执行完依赖于当前物理操作系统线程的标识的指令public static void FreeNamedDataSlot(string name)为进程中的所有线程消除名称与槽之间的关联
public static Object GetData( LocalDataStoreSlot slot )在当前线程的当前域中从当前线程上指定的槽中检索值public static AppDomain GetDomain()返回当前线程正在其中运行的当前域
public static AppDomain GetDomainID()返回唯一的应用程序域标识符public static LocalDataStoreSlot GetNamedDataSlot( string name )查找已命名的数据槽
public void Interrupt()中断处于WaitSleep.Join线程状态的线程public void Join()在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止
public static void MemoryBarrier()按如下方式同步内存存取:执行当前线程的处理器在对指令重新排序时,不能采用先执行 MemoryBarrier 调用之后的内存存取,再执行 MemoryBarrier 调用之前的内存存取的方式public static void ResetAbort()取消为当前线程请求的Abort
public static void SetData( LocalDataStoreSlot slot, Object data )在当前正在运行的线程上为此线程的当前域在指定槽中设置数据public void Start()开始一个线程
public static void Sleep( int millisecondsTimeout )让线程暂停一段时间public static void SpinWait( int iterations )导致线程等待由 iterations 参数定义的时间量
public static byte VolatileRead( ref byte address )
public static double VolatileRead( ref double address )
public static int VolatileRead( ref int address )
public static Object VolatileRead( ref Object address )
读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值public static void VolatileWrite( ref byte address, byte value )
public static void VolatileWrite( ref double address, double value )
public static void VolatileWrite( ref int address, int value )
public static void VolatileWrite( ref Object address, Object value )
立即向字段写入一个值,以使该值对计算机中的所有处理器都可见
public static bool Yield()导致调用线程执行准备好在当前处理器上运行的另一个线程。由操作系统选择要执行的线程

12.4 创建线程

线程是通过扩展Thread类创建的,扩展的Thread类调用Start()方法来开始子线程的执行。

using System;
using System.Threading;

namespace Application
{
    class ThreadTest
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
        }
        
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Thread childThread = new Thread(childref);
            childThread.Start();  // Child thread starts
            Console.ReadKey();
        }
    }
}

12.5 管理线程

Thread类提供了各种管理线程的方法,下面演示了Sleep()方法的使用,用于在一个特定的时间暂停线程。

using System;
using System.Threading;

namespace Application
{
    class ThreadTest
    {
        public static void CallToChildThread()
        {
            Console.WriteLine("Child thread starts");
            int sleepTime = 5000;
            Console.WriteLine("Child thread paused for {0} seconds", sleepTime / 1000);
            Thread.Sleep(sleepTime);
            Console.WriteLine("Child thread resumes");
        }
        
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Thread childThread = new Thread(childref);
            childThread.Start();
            // Child thread starts
            // Child thread pasued for 5 seconds
            // Child thread resumes
            Console.ReadKey();
        }
    }
}

12.6 销毁线程

Abort()方法用于销毁进行,通过抛出ThreadAbortException在运行时中止线程。这个异常不能被捕获,如果有finally块,控制会被送至finally块。

using System;
using System.Threading;

namespace Application
{
    class ThreadTest
    {
        public static void CallToChildThread()
        {
			try
            {
                Console.WriteLine("Child thread starts");
                for (int counter = 0; counter <= 10; counter++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(counter);
                }
                Console.WriteLine("Child thread resumes");
            }
            catch (ThreadAbortException e)
            {
                Console.WriteLine("Thread Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn't catch the Thread Exception");
            }
        }
        
        static void Main(string[] args)
        {
            ThreadStart childref = new ThreadStart(CallToChildThread);
            Thread childThread = new Thread(childref);
            childThread.Start();
            Thread.Sleep(2000);
            childThread.Abort();
            // Child thread starts
            // 0
            // 1
            // 2
            // Thread Abort Exception
            // Couldn't catch the Thread Exception
            Console.ReadKey();
        }
    }
}

参考文章

本文是笔者通过下列网站教程学习C#的记录,有部分修改和补充,转载请注明出处,并附带下面链接。

1.【菜鸟教程C#教程】

2.【C语言中文网C#教程】

3.【微软C#官方文档】

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值