如何停止线程

使用 System.Threading.CancellationTokenSource 停止线程。

示例代码

点击查看代码
using System;
using System.Threading;

namespace StopThread
{
    class Program
    {
        static void Main(string args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            Thread t = new Thread(() => {
                while (true)
                {
                    if (cts.IsCancellationRequested)
                    {
                        Console.WriteLine("线程被终止!");
                        break;
                    }
                    Console.WriteLine(DateTime.Now.ToString());
                    Thread.Sleep(1000);
                }
            });

            t.Start();
            cts.Token.Register(() => {
                Console.WriteLine("工作线程被终止!");
            });
            Console.ReadLine();

            cts.Cancel();
            Console.ReadLine();
        }
    }
}
避免线程过多

下面的例子中创建了 201 个线程,用 VS 直接运行了 1 个小时还是只有第一个时间输出到了控制台,只好手动停了。

点击查看代码
using System;
using System.Threading;

namespace TooMuchThread
{
    class Program
    {
        static void Main(string args)
        {
            Console.WriteLine(DateTime.Now.ToString());
            for (int i = 0; i < 200; i++)
            {
                Thread t = new Thread(() =>
                {
                    int j = 1;
                    while (true)
                    {
                        j++;
                    }
                });
                t.IsBackground = true;
                t.Start();
            }
            Thread.Sleep(5000);
            Thread t201 = new Thread(() => {
                while (true)
                {
                    Console.WriteLine("T201 正在执行");
                }
            });
            t201.Start();
            Console.WriteLine(DateTime.Now.ToString());
            Console.ReadKey();
        }
    }
}
线程同步 - 线程锁

线程锁的原理:锁住一个资源,使得应用程序在同一时刻只有一个线程访问该资源。

通俗地讲,就是让多线程变成单线程。

需要锁定的资源就是 C# 中的一个对象。在选择锁对象(同步对象)时,应当始终注意以下几点:

  1. 同步对象在需要同步的多个线程中是可见的同一个对象;

  2. 在非静态方法中,静态变量不应作为同步对象;

  3. 值类型对象不能作为同步对象;

  4. 避免将字符串作为同步对象;

  5. 降低同步对象的可见性;

线程同步 - 信号同步 -ManualResetEvent

AutoResetEvent 改为 ManualResetEvent 后,执行效果变为新起的两个线程都阻塞,收到信号后都继续执行。但是再次新起线程后,两个线程都没有阻塞。

点击查看代码
using System;
using System.Threading;
using System.Windows.Forms;

namespace WaitHandleSample
{
    public partial class Form3 : Form
    {
        ManualResetEvent manualesetEvent = new ManualResetEvent(false);

        public Form3()
        {
            InitializeComponent();
            // 允许新线程访问主线程的控件
            CheckForIllegalCrossThreadCalls = false;
        }

        private void btnStartAThread_Click(object sender, EventArgs e)
        {
            StartThread1();
            StartThread2();
        }

        private void StartThread1()
        {
            Thread tWork1 = new Thread(() => {
                label1.Text = "线程 1 启动。。。" + Environment.NewLine;
                label1.Text += "开始处理一下实际的工作" + Environment.NewLine;
                // 省略工作代码
                label1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
                manualesetEvent.WaitOne();
                label1.Text += "我继续做了一些工作,然后结束了";
            });
            tWork1.IsBackground = true;
            tWork1.Start();
        }

        private void StartThread2()
        {
            Thread tWork2 = new Thread(() => {
                label2.Text = "线程 2 启动。。。" + Environment.NewLine;
                label2.Text += "开始处理一下实际的工作" + Environment.NewLine;
                // 省略工作代码
                label2.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
                manualesetEvent.WaitOne();
                label2.Text += "我继续做了一些工作,然后结束了";
            });
            tWork2.IsBackground = true;
            tWork2.Start();
        }

        private void btnSet_Click(object sender, EventArgs e)
        {
            // 给在 autoResetEvent 上等待的线程一个信号
            manualesetEvent.Set();
        }
    }
}
线程同步 - 信号同步 -AutoResetEvent(2)

下面的例子中,本意是两个线程都阻塞,收到信号时都继续工作。

但实际的运行结果却是,收到信号后,一个继续工作,另一个继续阻塞,直到再一次收到信号,另一个才会继续工作。

这时应该用 ManualResetEvent 替换 AutoResetEvent

点击查看代码
using System;
using System.Threading;
using System.Windows.Forms;

namespace WaitHandleSample
{
    public partial class Form2 : Form
    {
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);

        public Form2()
        {
            InitializeComponent();
            // 允许新线程访问主线程的控件
            CheckForIllegalCrossThreadCalls = false;
        }

        private void btnStartAThread_Click(object sender, EventArgs e)
        {
            StartThread1();
            StartThread2();
        }

        private void StartThread1()
        {
            Thread tWork1 = new Thread(() => {
                label1.Text = "线程 1 启动。。。" + Environment.NewLine;
                label1.Text += "开始处理一下实际的工作" + Environment.NewLine;
                // 省略工作代码
                label1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
                autoResetEvent.WaitOne();
                label1.Text += "我继续做了一些工作,然后结束了";
            });
            tWork1.IsBackground = true;
            tWork1.Start();
        }

        private void StartThread2()
        {
            Thread tWork2 = new Thread(() => {
                label2.Text = "线程 2 启动。。。" + Environment.NewLine;
                label2.Text += "开始处理一下实际的工作" + Environment.NewLine;
                // 省略工作代码
                label2.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
                autoResetEvent.WaitOne();
                label2.Text += "我继续做了一些工作,然后结束了";
            });
            tWork2.IsBackground = true;
            tWork2.Start();
        }

        private void btnSet_Click(object sender, EventArgs e)
        {
            // 给在 autoResetEvent 上等待的线程一个信号
            autoResetEvent.Set();
        }
    }
}
线程的 IsBackground

下面的代码需要工作结束后,敲一下回车,程序才会结束。

如果将 IsBackground 改为 true,效果会怎样?

using System;
using System.Threading;

namespace LookOutForIsBackground
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread t = new Thread(() =>
            {
                Console.WriteLine("线程开始工作。。。");
                // 省略工作代码
                Console.ReadKey();
                Console.WriteLine("线程结束");
            });
            // 注意,默认就是 false
            t.IsBackground = false;
            t.Start();
            Console.WriteLine("主线程结束");
        }
    }
}
线程不是立即启动的

先看一下下面的代码:

using System;
using System.Threading;

namespace ThreadIsNotStartImmediately
{
    class Program
    {
        static int _id = 0;

        static void Main(string args)
        {
            for (int i = 0; i < 10; i++, _id++)
            {
                Thread t = new Thread(() =>
                {
                    Console.WriteLine(string.Format("{0}:{1}", 
                        Thread.CurrentThread.Name, _id));
                });
                t.Name = string.Format("Thread{0}", i);
                t.IsBackground = true;
                t.Start();
            }
            Console.ReadLine();
        }
    }
}
使用 lock 实现线程同步

通过 lock 一个私有的引用成员变量来完成成员方法内的线程同步;

通过 lock 一个私有的静态引用成员变量来完成静态方法内的线程同步;

示例代码

Lock.cs

点击查看代码
using System;
using System.Threading;

namespace UseLock
{
    class Lock
    {
        /// <summary>
        /// 用来在静态方法中同步
        /// </summary>
        private static object o1 = new object();
        /// <summary>
        /// 用来在成员方法中同步
        /// </summary>
        private object o2 = new object();
        /// <summary>
        /// 静态变量
        /// </summary>
        private static int i1 = 0;
        /// <summary>
        /// 成员变量
        /// </summary>
        private int i2 = 0;

        /// <summary>
        /// 测试静态方法的同步
        /// </summary>
        /// <param name="state"></param>
        public static void Increment1(object state)
        {
            lock (o1)
            {
                Console.WriteLine("i1 的值为:{0}", i1.ToString());
                // 这里刻意制造线程并行机会,来检查同步功能
                Thread.Sleep(200);
                i1++;
                Console.WriteLine("i1 自增后为:{0}", i1.ToString());
            }
        }

        /// <summary>
        /// 测试成员方法的同步
        /// </summary>
        /// <param name="state"></param>
        public void Increment2(object state)
        {
            lock (o2)
            {
                Console.WriteLine("i2 的值为:{0}", i2.ToString());
                // 这里刻意制造线程并行机会,来检查同步功能
                Thread.Sleep(200);
                i2++;
                Console.WriteLine("i2 自增后为:{0}", i2.ToString());
            }
        }
    }
}
使用 lock(this) 进行同步

使用 lock(this) 能够实现线程同步,但是会导致类型缺乏健壮性,容易发生死锁。

下面的示例展示了一个使用 lock(this) 导致死锁的情况。

示例代码

SynchroThis.cs

using System;
using System.Threading;

namespace SynchroThis
{
    /// <summary>
    /// 使用 this 来同步线程
    /// 缺乏健壮性
    /// </summary>
    class SynchroThis
    {
        private int i = 0;

        public void Work(Object state)
        {
            lock(this)
            {
                Console.WriteLine("i 的值为:{0}", i.ToString());
                i++;
                Thread.Sleep(200);
                Console.WriteLine("i 自增 1 后的值为:{0}", i.ToString());
            }
        }
    }
}
线程同步 - 信号同步-AutoResetEvent(1)

using System;
using System.Threading;
using System.Windows.Forms;

namespace WaitHandleSample
{
    public partial class Form1 : Form
    {
        AutoResetEvent autoResetEvent = new AutoResetEvent(false);

        public Form1()
        {
            InitializeComponent();
            // 允许新线程访问主线程的控件
            CheckForIllegalCrossThreadCalls = false;
        }

        private void btnStartAThread_Click(object sender, EventArgs e)
        {
            Thread twork = new Thread(() =>
            {
                label1.Text = "线程启动。。。" + Environment.NewLine;
                label1.Text += "开始处理一下实际的工作" + Environment.NewLine;
                // 省略工作代码
                label1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
                autoResetEvent.WaitOne();
                label1.Text += "我继续做了一些工作,然后结束了";
            });
            twork.IsBackground = true;
            twork.Start();
        }

        private void btnSet_Click(object sender, EventArgs e)
        {
            // 给在 autoResetEvent 上等待的线程一个信号
            autoResetEvent.Set();
        }
    }
}
使用互斥体(Mutex)实现同步

互斥体是操作系统内同步线程的内核对象,有相应的 Win32 函数来操作互斥体对象。

在.NET 中,Mutex类封装了所有互斥体的操作。

Mutex类型和Monitor类型(lock)的区别:

  1. 使用Mutex的效率非常低,相对于Monitor类型等线程同步机制来说,操作系统互斥体的效率要慢 10 倍以上。

  2. 操作系统的互斥体对系统上的所有进程都有效,也就是说它是跨进程的。

线程上下文流动

当程序中新建一个线程时,执行上下文会自动地从当前线程流入到新建的线程之中,这样做可以保证新建的线程天生具有与主线程相同的安全设置和文化设置。

代码示例

点击查看代码
using System;
using System.IO;
using System.Security;
using System.Security.Permissions;
using System.Threading;

namespace ExecutionContextFlow
{
    class Program
    {
        private static string _testFIle = "D:\\TestContext.txt";

        static void Main(string args)
        {
            try
            {
                // 建立测试文件
                CreateTestFile();

                // 测试当前线程安全上下文
                Console.Write("主线程权限测试:");
                TestPermission(null);
                // 建立一个子线程
                Console.Write("子线程权限测试:");
                Thread son1 = new Thread(TestPermission);
                son1.Start();
                son1.Join();
                // 现在修改安全上下文
                // 阻止文件访问
                FileIOPermission fip = new FileIOPermission(FileIOPermissionAccess.AllAccess, _testFIle);
                fip.Deny();
                Console.WriteLine("已阻止文件访问");

                // 测试当前线程安全上下文
                Console.Write("主线程权限测试:");
                TestPermission(null);
                // 建立一个子线程
                Console.Write("子线程权限测试:");
                Thread son2 = new Thread(TestPermission);
                son2.Start();
                son2.Join();
                // 现在修改安全上下文
                // 恢复文件访问
                SecurityPermission.RevertDeny();
                Console.WriteLine("已恢复文件访问");

                // 测试当前线程安全上下文
                Console.Write("主线程权限测试:");
                TestPermission(null);
                // 建立一个子线程
                Console.Write("子线程权限测试:");
                Thread son3 = new Thread(TestPermission);
                son3.Start();
                son3.Join();

                Console.Read();
            }
            finally 
            {
                // 删除测试文件
                DeleteTesfFile();
            }
        }

        /// <summary>
        /// 建立测试文件
        /// </summary>
        static void CreateTestFile()
        {
            if (!File.Exists(_testFIle))
            {
                using (FileStream fs = File.Create(_testFIle))
                {

                }
            }
        }

        /// <summary>
        /// 删除测试文件
        /// </summary>
        private static void DeleteTesfFile()
        {
            try
            {
                if (File.Exists(_testFIle))
                {
                    File.Delete(_testFIle);
                }
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// 尝试访问文件来测试安全上下文
        /// </summary>
        /// <param name="state"></param>
        private static void TestPermission(object state)
        {
            try
            {
                // 尝试访问文件
                File.GetCreationTime(_testFIle);
                // 如果没有异常则测试通过
                Console.WriteLine("权限测试通过");
            }
            catch (SecurityException)
            {
                // 表明没有权限访问
                Console.WriteLine("权限测试没有通过");
            }
        }

    }
}
用异步模式读取一个文件

调用 FileStreamBeginReadEndRead 方法实现文件的异步模式读取。

代码示例

ReadFileClass.cs

点击查看代码
using System;
using System.IO;
using System.Text;

namespace AsyncReadFile
{
    /// <summary>
    /// 打包传递给完成异步后回调的方法
    /// </summary>
    class ReadFileClass
    {
        /// <summary>
        /// 以便回调方法中释放异步读取的资源
        /// </summary>
        public FileStream _fs;
        /// <summary>
        /// 文件内容
        /// </summary>
        public Byte _data;

        public ReadFileClass(FileStream fs, Byte data)
        {
            _fs = fs;
            _data = data;
        }
    }
}
阻止线程上下文的流动

执行上下文的流动使得程序的执行效率下降很多,线程上下文的包装是一个成本较高的工作,而有时这样的包装并不是必需的。

这种情况下,程序员就需要手动地防止线程上下文的流动,常用的有下面两种方法:

  • 使用定义在 System.Threading.ThreadPool 类型中的 UnsafeQueueUserWorkItem 方法

  • 使用定义在 ExecutionContext 类型中的 SuppressFlow 方法

使用线程池

由于线程的创建和销毁需要非常大的性能开销,在 WindowsNT 内核的操作系统上,每个进程都会包含一个线程池。线程池由 CLR 管理。

.NET 提供 System.Threading.ThreadPool 类型来提供关于线程池的操作。

常用的有下面 3 个静态方法:

  1. public static bool QueueUserWorkItem;

  2. public static bool QueueUserWorkItem;

  3. public static bool UnsafeQueueUserWorkItem;

线程本地存储(Thread Local Storage, TLS)(2)ThreadStaticAttribute

申明了 ThreadStaticAttribute 特性的静态变量,会被.NET 作为线程独享的数据来使用。

示例代码

ThreadStaticSample.cs

using System;
using System.Threading;

namespace ThreadStaticSample
{
    class ThreadStaticSample
    {
        /// <summary>
        /// 值类型的线程静态数据
        /// </summary>
        [ThreadStatic]
        static int _threadid = 0;
        /// <summary>
        /// 引用类型的线程静态数据
        /// </summary>
        static Ref _refthreadid = new Ref();

        public static void DoWork()
        {
            _threadid = Thread.CurrentThread.ManagedThreadId;
            _refthreadid._id = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("[{0}线程]:线程静态变量:{1},线程静态引用变量:{2}",
                Thread.CurrentThread.ManagedThreadId.ToString(),
                _threadid,
                _refthreadid._id);
            Thread.Sleep(1000);
            Console.WriteLine("[{0}线程]:线程静态变量:{1},线程静态引用变量:{2}",
                Thread.CurrentThread.ManagedThreadId.ToString(),
                _threadid,
                _refthreadid._id);

        }
    }
}
线程本地存储(Thread Local Storage, TLS)(1)线程数据插槽

所谓的线程本地存储(TLS),是指存储在线程环境块内的一个结构,用了存放该线程内独享的数据。

进程内的线程不能访问不属于自己的 TLS,这就保证了 TLS 内的数据在线程内是全局共享的,而对线程外却是不可见的。

.NET 提供了简单的机制供程序员使用本地存储,定义在 System.Threading.Thread 类型内的 AllocateDataSlotAllocateNamedDataSlot 方法提供一个存储在所有线程内的数据插槽,而通过这个插槽结构,程序员可以使用下列两个方法来存取线程独享的数据,这两个方法都定义在 System.Threading.Thread 类型中。

简单多线程程序

示例代码

using System;
using System.Threading;

namespace MultiThreads
{
    class Program
    {
        static void Main(string args)
        {
            Console.WriteLine("进入多线程工作");
            for (int i = 0; i < 10; i++)
            {
                // 新建一个 thread 对象
                Thread newthread = new Thread(Work);
                // 新建线程
                newthread.Start();
            }
            Console.Read();
        }

        /// <summary>
        /// 线程方法
        /// </summary>
        private static void Work()
        {
            Console.WriteLine("线程开始");
            // 模拟做了一些工作
            Thread.Sleep(1000);
            Console.WriteLine("线程结束");
        }
    }
}
控制并查看线程的状态

.NET 提供了 System.Threading.Thread 类型封装了线程的操作,通过该类型,可以手动的创建、查询、控制以及结束线程。

线程状态

  • Unstarted:线程建立但未启动

  • Running:线程运行

  • WaitSleepJoin:线程休眠

  • SuspendRequested:已申请挂起

  • Suspended:线程挂起

  • AbortRequested:已申请中止

  • Stopped:线程中止

利用反射实现工厂模式

需求

工厂类根据参数生成对应类的实例。

示例

RoomParts.cs

namespace ReflectionFactory
{
    /// <summary>
    /// 屋子产品的零件
    /// </summary>
    public enum RoomParts
    {
        Roof,
        Window,
        Pillar
    }
}
.NET 提供了哪些类型来实现反射

实现反射的类型大多数都定义在 System.Reflection 命名空间之下。

  • Assembly:定义一个 Assembly,它是可重用、无版本冲突并且可自我描述的公共语言运行库应用程序构造块。

  • AssemblyName:完整描述程序集的唯一标识

  • EventInfo:发现事件的属性(Attribute)并提供对事件元数据的访问权

  • FieldInfo:发现字段属性(Attribute)并提供对字段元数据的访问权

  • LocalVariableInfo:发现局部变量的属性,并提供对局部变量元数据的访问

  • ManifestResourceInfo:包含清单资源拓扑信息

  • MemberInfo:获取有关成员属性的信息,并提供对成员元数据的访问

  • MethodBase:提供有关方法和构造函数的信息

  • MethodBody:提供对用于方法体的元数据和 MSIL 的访问

  • Module:在模块上执行反射

  • ParameterInfo:发现参数属性(Attribute)并提供对参数元数据的访问

  • PropertyInfo:发现属性(Property)的属性(Attribute)并提供对属性(Property)元数据的访问

如何设计一个带有很多事件的类型?

当某个类型包含较多的事件时,可以考虑集中把所有事件的委托链表存储在一个集合之中,这样做能有效地减少对象的大小,因为在实际逻辑世界中一个对象使用所有事件的概率相对很低。

.NET 的内建类型 System.ComponentModel.EventHandlerList 提供了这样一个存储事件集合的封装。

代码示例

1. 创建要给多事件的类 MultiEventsClass.cs

点击查看代码
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MultiEvents
{
    /// <summary>
    /// 多事件类型
    /// </summary>
    public class MultiEventsClass : IDisposable
    {
        /// <summary>
        /// System.ComponentModel.EventHandlerList 包含了一个委托链表的容器
        /// 实现了多事件存放在一个容器之中的包装
        /// EventHandlerList 使用的是链表数据结果
        /// </summary>
        private EventHandlerList _events;

        /// <summary>
        /// 构造函数
        /// </summary>
        public MultiEventsClass()
        {
            _events = new EventHandlerList();
        }

        // 申明事件 1
        #region event1
        // 事件 2 的委托原型
        public delegate void Event1Handler(Object sender, EventArgs e);
        // 静态字段,提高性能
        protected static readonly Object Event1Key = new object();

        /// <summary>
        /// 一组订阅、取消订阅事件的方法
        /// 注意 EventHandlerList 并不提供线程同步,
        /// 所以在 add 和 remove 方法前加上线程同步属性
        /// (可以采取 lock 机制来代替)
        /// </summary>
        public event Event1Handler Event1
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                _events.AddHandler(Event1Key, value);
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                _events.RemoveHandler(Event1Key, value);
            }
        }

        /// <summary>
        /// 触发事件 1
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnEvent1(EventArgs e)
        {
            _events[Event1Key].DynamicInvoke(this, e);
        }
        /// <summary>
        /// 这个方法简单的触发事件 2,以便于测试
        /// </summary>
        public void RaiseEvent1()
        {
            OnEvent1(EventArgs.Empty);
        }

        #endregion

        // 申明事件 2
        #region Event2
        // 事件 2 的委托原型
        public delegate void Event2Handler(Object sender, EventArgs e);
        // 静态字段,提高性能
        protected static readonly Object Event2Key = new object();
        /// <summary>
        /// 一组订阅、取消订阅事件的方法
        /// 注意 EventHandlerList 并不提供线程同步,
        /// 所以在 add 和 remove 方法前加上线程同步属性
        /// (可以采取 lock 机制来代替)
        /// </summary>
        public event Event2Handler Event2
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                _events.AddHandler(Event2Key, value);
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                _events.RemoveHandler(Event2Key, value);
            }
        }
        /// <summary>
        /// 触发事件 2
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnEvent2(EventArgs e)
        {
            _events[Event2Key].DynamicInvoke(this, e);
        }
        /// <summary>
        /// 这个方法简单的触发事件 2,以便于测试
        /// </summary>
        public void RaiseEvent2()
        {
            OnEvent2(EventArgs.Empty);
        }
        #endregion

        /// <summary>
        /// 释放 EventHandlerList
        /// </summary>
        public void Dispose()
        {
            _events.Dispose();
        }
    }
}
元数据

源代码

using System;

namespace SimpleAssembly
{
    /// <summary>
    /// 创建一个包含私有成员、特性、方法的简单类型
    /// </summary>
    [Serializable]
    class Program
    {
        private string _MyString;
        public Program(string mystring)
        {
            _MyString = mystring;
        }
        public override string ToString()
        {
            return _MyString;
        }
        static void Main(string[] args)
        {
            Console.WriteLine("简单程序集");
            Console.Read();
        }
    }
}
用代码表示如下情景:猫叫、老鼠逃跑、主人惊醒

1. 定义猫类型,并且该类型负责维护猫叫事件 Cat.cs

点击查看代码
using System;

namespace CatCry
{
    /// <summary>
    /// 猫类型,维护猫叫事件
    /// </summary>
    public class Cat
    {
        /// <summary>
        /// 猫名
        /// </summary>
        private String _name;
        /// <summary>
        /// 猫叫的事件
        /// </summary>
        public event EventHandler<CatCryEventArgs> CatCryEvent;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="name"></param>
        public Cat(string name)
        {
            _name = name;
        }
        /// <summary>
        /// 触发猫叫事件
        /// </summary>
        public void CatCry()
        {
            CatCryEventArgs args = new CatCryEventArgs(_name);
            Console.WriteLine(args);
            CatCryEvent(this, args);
        }
    }
}
什么是字符串池机制?

字符串池机制致力于改善程序的性能。

CLR 会保留程序中出现过的字符串对象的集合,并且在需要新的字符串时,先检查已有的集合,在查找成功时返回已有对象的引用。

字符串池机制可以通过程序集元数据特性进行控制,C# 默认的机制是打开字符串池机制。

using System;

namespace StringPool
{
    class Program
    {
        static void Main(string args)
        {
            // 两个字符串对象,理论上引用应该不相等
            // 但由于字符串池的机制,两者实际指向同一对象
            string a = "abcde";
            string b = "abcde";
            Console.WriteLine(Object.ReferenceEquals(a, b));    // True

            // 这行代码等同于 c = "abcde"
            // 由于字符串池,c 和 a 还是指向同一对象
            string c = "a" + "b" + "c" + "d" + "e";
            Console.WriteLine(Object.ReferenceEquals(a, c));    // True

            // 显示使用 new 来分配内存
            // 这时候字符串池机制不能起作用,d 和 a 指向不同对象
            Char chars = { 'a', 'b', 'c', 'd', 'e' };
            string d = new string(chars);
            Console.WriteLine(Object.ReferenceEquals(a, d));    // False
            Console.Read();
        }
    }
}
委托的基本原理

委托是一类继承自 System.Delegate 的类型,每个委托对象至少包含一个指向某个方法的指针,该方法可以是实例方法,也可以是静态方法。

委托实现了回调方法的机制,能够帮助程序员设计更加简洁优美的面上对象程序。

using System;

namespace SimpleDelegate
{
    class Program
    {
        public delegate void TestDelegate(int i);
        static void Main(string args)
        {
            TestDelegate d = new TestDelegate(PrintMessage);
            d(0);       // output : 第 0 个方法
            d(1);       // output : 第 1 个方法

            d.Invoke(2);    // output : 第 2 个方法
            d.Invoke(3);    // output : 第 3 个方法

            Console.Read();
        }

        static void PrintMessage(int i)
        {
            Console.WriteLine("第" + i + "个方法");

        }
    }
}
链式委托

链式委托是指一个由委托串成的链表,当链表上的一个委托被回调时,所有链表上该委托的后续委托将会被顺序执行。

using System;

namespace MulticastDelegate
{
    class Program
    {
        /// <summary>
        /// 定义委托
        /// </summary>
        public delegate void TestMultiDelegate();

        static void Main(string args)
        {
            // 申明一个委托变量,并绑定第一个方法
            TestMultiDelegate handler = new TestMultiDelegate(PrintMessage1);
            // 绑定第二个方法
            handler += new TestMultiDelegate(PrintMessage2);
            // 绑定第三个方法
            handler += new TestMultiDelegate(PrintMessage3);
            // 检查结果
            // Output :
            //      第一个方法
            //      第二个方法
            //      第三个方法
            handler();
            Console.Read();
        }

        static void PrintMessage1()
        {
            Console.WriteLine("第一个方法");
        }

        static void PrintMessage2()
        {
            Console.WriteLine("第二个方法");
        }

        static void PrintMessage3()
        {
            Console.WriteLine("第三个方法");
        }
    }
}
事件的基本使用方法

  • 事件是一种使对象或者类能够提供通知的成员。

  • 客户端可以通过提供事件处理程序为相应的事件添加可执行代码。

  • 事件是一种特殊的委托。

代码示例:

ConsoleEventArgs.cs

点击查看代码
using System;

namespace ConsoleEvent
{
    /// <summary>
    /// 自定义事件参数类型
    /// </summary>
    public class ConsoleEventArgs : EventArgs
    {
        // 控制台输出的消息
        private string _message;

        /// <summary>
        /// 构造方法
        /// </summary>
        public ConsoleEventArgs() : base()
        {
            _message = string.Empty;
        }

        /// <summary>
        /// 构造方法
        /// </summary>
        /// <param name="message"></param>
        public ConsoleEventArgs(string message) : base()
        {
            _message = message;
        }

        /// <summary>
        /// 只读属性
        /// </summary>
        public string Message
        {
            get
            {
                return _message;
            }
        }
    }
}