深切明C#:编程技巧总结(二)

原创文章,转载请注明出处! 以下总结参阅了:MSDN文档、《C#高档编程》、《C#本质论》、前辈们的博客当材料,如产生不正确的地方,请帮忙就指出!以免误导!

当达成一样篇 深刻理解C#:编程技巧总结(一) 中总结了25碰,这等同篇延续:

26.系列化与相反系列化

  • 动用的场所:
    有利保存,把持有运行状态的目标系列化后保存至当地,在下次运行程序时,反系列化该目标来回复状态
    便利传输,在网络中传系列化后的对象,接收方反系列化该目标还原
    复制黏贴,复制到剪贴板,然后黏贴

  • 用来援助方向和反系列化的风味:在System.Runtime.Serialization取名空间下
    OnDeserialized,应用被某某方法,该方法会在倒系列化后迅即叫自动调用(可用以拍卖生成的对象的成员)
    OnDeserializing,应用叫某某方法,该方法会在执行反系列化时为自动调用
    OnSerialized,应用叫某方法,对象在被矛头后调用该方式
    OnSerializing,应用被有方法,在倾向对象前调用该法
    一旦以上扶持特性仍不克满足要求,那便使吧对象对象实现ISerializable接口了

  • ISerializable接口:该接口运行目标好主宰方向与相反系列化的历程(实现该接口的还要为必须用Serializable特性)

原理:若系列化一个目标时,发现目标实现了ISerializable接口,则会忽视掉类型有的取向特性应用,转而调用型的GetObjectData()接口方法,该方法会构造一个SerializationInfo靶,方法中负责对拖欠目标设置需要系列化的字段,然后系列化器根据该对象来系列化。反系列化时,若觉察倒系列化后的对象实现了ISerializable接口,则相反系列化器会管数据反系列化为SerializationInfo项目的目标,然后调用匹配的构造函数来布局目标项目的靶子。

方向:需要实现GetObjectData()术,该措施以目标吃矛头时自动为调用
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context){ }

反序列化:需要为其定义一个牵动参数的受保障之构造函数,用于反系列化后重新组织对象
protected Person(SerializationInfo info, StreamingContext context) { }
运该接口,可以实现将数据流反系列化为其他随意指定对象(在原先对象定义GetObjectData()方,在靶靶定义用于反系列化的构造函数,但少独对象都须兑现ISerializable接口)
注意:
假定父类为兑现ISerializable接口,只有子类实现ISerializable接口,若想系列化从父类继承的字段,则用以子类的倒系列化构造器中和GetObjectData()方法吃,添加继承自父类的字段的处理代码
若父类也落实了ISerializable接口,则就需要于子类的相反系列化构造器中和GetObjectData()术中调用父类的本即可

public class Class1
{
    public static void Main()
    {
        Person person = new Person() { FirstName = "RuiFu", LastName = "Su"};
        //系列化person对象并存进文件中,MyBinarySerializer为自定义工具类
        MyBinarySerializer.SerializeToFile<Person>(person, @"c:\", "Person.txt");
        //从文件中取出数据反系列化为Man类型的对象
        Man man = MyBinarySerializer.DeserializeFromFile<Man>(@"c:\Person.txt");
        Console.WriteLine(man.Name);
        Console.ReadKey();
    }
}
[Serializable]
public class Man:ISerializable
{
    public string Name;
    protected Man(SerializationInfo info,StreamingContext context)
    {
        Name = info.GetString("Name");
    }
    void ISerializable.GetObjectData(SerializationInfo info,StreamingContext context)
    { }
}
[Serializable]
public class Person:ISerializable
{
    public string FirstName;
    public string LastName;
    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        //设置反系列化后的对象类型
        info.SetType(typeof(Man)); 
        //根据原对象的成员来为info对象添加字段(这些字段将被系列化)
        info.AddValue("Name", string.Format("{0} {1}", LastName, FirstName));
    }
}

莫应当让矛头的分子:
为节省空间、流量,如果一个字段反系列化后对封存状态无意义,就从未有过必要系列化它
若果一个字段可以经另外字段推算出来,则没有必要系列化它,而用OnDeserializedAttribute特性来点推算方法执行
对此私密信息不应当吃矛头
设若成员对应之色我不被设置为而系列化,则当将他标注为不可系列化[NonSerialized],否则运行时见面抛出SerializationException
管性能设置为不可系列化:把它们的后备字段设置也不可系列化即可实现
将事件设置也不可系列化:[field:NonSerialized]

正规系列化与相反系列化示例:自定义了工具类MyBinarySerializer

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
public class Class1
{
    public static void Main()
    {
        Person person1 = new Person() { 
            FirstName = "RuiFu", LastName = "Su", FullName = "Su RuiFU",IDCode="0377"};
        //系列化person1并存进文件中
        MyBinarySerializer.SerializeToFile<Person>(person1, @"c:\", "Person.txt");
        //从文件中取出数据反系列化为对象(文件中不含FullName信息,但系列化后自动执行了预定义的推算方法)
        Person person2 = MyBinarySerializer.DeserializeFromFile<Person>(@"c:\Person.txt");
        Console.WriteLine(person2.FullName);
        Console.ReadKey();
    }
}
[Serializable]
public class Person
{
    public string FirstName;
    public string LastName;
    [NonSerialized] //禁止被系列化
    public string FullName; //可被以上2个字段推算出来
    [OnDeserialized] //反系列化后将被调用的方法
    void GetFullName(StreamingContext context)
    {
        FullName = string.Format("{0} {1}", LastName, FirstName);
    }
    [NonSerialized]
    private string idCode;
    public string IDCode
    {
        get
        {
            return idCode;
        }
        set
        {
            idCode = value;
        }
    }
    [field: NonSerialized]
    public event EventHandler NameChanged;
}
//自定义的系列化与反系列化工具类
public class MyBinarySerializer
{
    //将类型系列化为字符串
    public static string Serialize<T>(T t)
    {
        using(MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, t);
            return System.Text.Encoding.UTF8.GetString(stream.ToArray());
        }
    }
    //将类型系列化为文件
    public static void SerializeToFile<T>(T t, string path, string fullName)
    {
        if(!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        string fullPath = string.Format(@"{0}\{1}", path, fullName);
        using(FileStream stream = new FileStream(fullPath,FileMode.OpenOrCreate))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, t);
            stream.Flush();
        }
    }
    //将字符串反系列化为类型
    public static TResult Deserialize<TResult>(string s) where TResult: class
    {
        byte[] bs = System.Text.Encoding.UTF8.GetBytes(s);
        using(MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            return formatter.Deserialize(stream) as TResult;
        }
    }
    //将文件反系列化为类型
    public static TResult DeserializeFromFile<TResult>(string path) where TResult: class
    {
        using(FileStream stream = new FileStream(path,FileMode.Open))
        {
            BinaryFormatter formatter = new BinaryFormatter();
            return formatter.Deserialize(stream) as TResult;
        }
    }
}

27.生处理:抛来怪是用耗费性能的(但相对于低概率事件,这点性能影响是无所谓的)

  • 勿设动很处理机制来落实控制流的换
  • 毫无对会预知到的大概率、可还原的谬误抛来深,而应当据此实际代码来拍卖可能出现的缪
  • 就以以防止出现小概率预知错误、无法预知的左和无法处理的情况才尝试摒弃来十分(如:运行代码会造成内存泄漏、资源不可用、应用程序状态不行恢复,则要抛来老)
  • 假使要把错呈现于最终之用户,则当先捕获该生,对快信息进行打包后,重新抛来一个来得自己信息的新老
  • 根代码引发的好对于高层代码没有意思时,则可捕获该特别,并再次掀起意思显然的深
  • 每当重复掀起那个时,总是为新大对象提供Inner
    Exception对象参数(不需提供信息经常最好好直接用空的throw;语句,它会拿旧异常对象又抛出),该对象保存了本来面目好的整整信息,包括充分调用栈,便于代码调试
  • 因而很处理代替返回错误代码的法,因为归错误代码不便宜维护
  • 断不要捕获在眼前及下文中无法处理的十分,否则即可能制造了一个东躲西藏的万分老的BUG
  • 免采用多重叠嵌套的try…catch,嵌套多矣,谁还见面蒙掉
  • 对正规的事情逻辑,使用Test-Doer模式来代替抛来大

    private bool CheckNumber(int number, ref string message)
    {
    if(number < 0)
    {
        message = "number不能为负数。";
        return false;
    }
    else if(number > 100)
    {
        message = "number不能大于100。";
        return false;
    }
    return true;
    }
    //调用:
    string msg = string.Empty;
    if(CheckNumber(59, ref msg)
    {
    //正常逻辑处理代码
    }
    
  • 对此try…finally,除非在实施try块的代码时先后竟然退出,否则,finally块总是会于执行

28.多线程的不得了处理

  • 以线程上如有免处理的挺,则会硌进程AppDomain.UnHandledException事件,该事件会收到至非处理非常的通报所以调用在她点注册之方式,然后使程序退出(注册方式无法阻挡应用程序的离,我们只能采取该法来记录日志)
  • 以Windows窗体程序中,可以为此Application.ThreadException事件来处理窗体线程中所生的匪被拍卖的慌,用AppDomain.UnHandledException事件来拍卖非窗体线程中出的未为处理的死去活来。ThreadException事件可以阻止应用程序的脱离。
  • 健康状态下,try…catch只能捕获当前线程的万分,一个线程中之不胜只能于拖欠线程内部才能够于破获到,也就是说主线程无法直接捕获子线程中之充分,若要把线程中的老大抛给主线程处理,需要用异样手段,我勾勒了如下示例代码做参考:

    static Action<Exception> action;//直接用预定义的Action委托类
    static Exception exception;
    public static void Main()
    {
        action = CatchThreadException; //注册方法
        Thread t1 = new Thread(new ThreadStart(delegate
            {
                try
                {
                    Console.WriteLine("子线程执行!");
                    throw new Exception("子线程t1异常");
                }
                catch(Exception ex)
                {
                    OnCatchThreadException(ex); //执行方法
                    //如果是windows窗体程序,则可以直接用如下方法:
                    //this.BeginInvoke((Action)delegate
                    //{
                    //    throw ex; //将在主线程引发Application.ThreadException
                    //}
                }
            }));
        t1.Start();
        t1.Join();//等待子线程t1执行完毕后,再返回主线程执行
        if(exception!=null)
        {
            Console.WriteLine("主线程:{0}", exception.Message);
        }
    
        Console.ReadKey();
    }
    static void CatchThreadException(Exception ex)
    {
        exception = ex;
    }
    static void OnCatchThreadException(Exception ex) //定义触发方法
    {
        var actionCopy = action;
        if(actionCopy!=null)
        {
            actionCopy(ex); //触发!!!
        }
    }
    

29.打定义格外

  • 止以生异样需要之早晚才使用由定义格外
  • 为酬答各异之业务环境,可以于脚捕获各种事务环境或引发的深(如采用不同之数据库类型等),然后还抛弃来一个一起之自定义异常给调用者,这样一来,调用者只要捕获该由定义格外类型即可
  • 给于定义格外类派生自System.Exception类似或任何常见的为主好,并叫你的死类使用[Serializable],这样就是得在待的下系列化异常(也得针对那个类实现ISerializable接口来自定义系列化过程)
  • 万一只要指向老信息进行格式化,则需要还写Message属性
    规范于定义格外类模板:(创建于定义格外标准类的快捷方式:在VS中输入Exception后依Tab键)

    [Serializable]
    public class MyException : Exception
    {
        public MyException() { }
        public MyException(string message) : base(message) { }
        public MyException(string message, Exception inner) : base(message, inner) { }
        //用于在反系列化时构造该异常类的实例
        protected MyException(
          SerializationInfo info,
          StreamingContext context)
            : base(info, context) { }  
    }
    

30.当CLR中法的履进程:

  • 第一用参数值依次存进内存栈,执行代码的过程遭到,会根据需要去栈中取用参数值
  • 欣逢return语词时,方法返回,并拿return语句之结果值存入栈顶,这个价值就是是最后之返回值
  • 假如方法外设有finally块,则就以try块中有return语句,最终为会见当执行finlly块之后才脱离方式,在这种情景下,若返回值的品类为值类型,则于finally块中对回到变量的改动以无济于事,方法的末段回到回值都是根据return语句压入栈顶中的价值(对于引用类型,返回值仅是一个援,能成修改该引用指向的靶子,但对拖欠引用我的改动为是行不通的),如下:

    //1.值类型
       public static int SomeMethod(int a)
    {
        try
        {
            a = 10;
            return a;
        }
        finally
        {
            a = 100;
            Console.WriteLine("a={0}", a);
        }
    }
    //调用
    Console.WriteLine(SomeMethod(1));
    //a=100
    //10   这是方法的返回值,finally无法修改返回值
    //2.引用类型
    public class Person:ISerializable
    {
    public string FirstName;
    }
    public static Person SomeMethod(Person a)
    {
        try
        {
            a.FirstName = "Wang";
            return a;
        }
        finally
        {
            a.FirstName = "Su";
            a = null;
            Console.WriteLine("a={0}", a);
        }
    }
    //调用
    Person person = new Person();
    Console.WriteLine(SomeMethod(person).FirstName);  
    //a=
    //Su  finally成功修改了对象的字段,但对引用a本身的改变不影响返回值对象
    

31.线程池同线程的界别

  • 线程:通过System.Threading.Thread恍如开辟的线程,用了便自行销毁,不可重用。主要用以密集型复杂运算
  • 线程池:由System.Threading.ThreadPool恍如管理之一律组线程,可选用。主要用以I/O等异步操作。线程池中之等同长线程任务完成后,该线程不见面自行销毁。相反,它会盖悬挂于状态返回线程池。如果应用程序再次向线程池发出请求,那么这挂起的线程将激活并实施任务,而非见面创造新线程。这节了森支。只要线程池中应用程序任务的排队速度低于一个线程处理每起任务之速度,那么即使可以屡屡用同一线程,从而以应用程序生存期内节约大量支出。
  • 线程池可以供四种意义:异步调用方法、以得之光阴间隔调用方法、当单个内核对象获得信号通知时调用方法、当异步
    I/O 请求了时调用方法

32.大抵线程和异步的分

  • 异步操作的精神:是硬件的效益,不吃CPU资源。硬件在收受CPU的命后,自己直接跟内存交换数据,完成后会触发一个搁浅来通知操作就(如:委托的BeginInvoke()方式,执行该措施时,在线程池ThreadPool中启用一漫漫线程来拍卖任务,完成后会调用方法参数中指定的回掉函数,线程池中之线程是分配好之,使用时不需要new操作)
  • 线程的面目:是操作系统提供的同种植逻辑功能,它是经过被一律截并作运行的代码,线程需要操作系统投入CPU资源来运转和调度
  • 异步操作的得失:因为异步操作无须额外之线程负担,并且用回调的章程进行拍卖,在筹划良好的状下,处理函数可以不要下共享变量(即使无法完全无用,最起码可以减少
    共享变量的数量),减少了死锁的或许。当然异步操作为绝不全盘无暇。编写异步操作的复杂程度较高,程序要运用回调方式展开拍卖,与老百姓的思考方式有些
    出入,而且难以调试。
  • 基本上线程的利害:多线程的长处很明确,线程中的处理程序依然是逐一执行,符合老百姓的思维习惯,所以编程简单。但是多线程的败笔也一致引人注目,线程的采取(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能导致死锁的出现。
  • 何时使用:当需要实行I/O操作时,应该以异步操作。I/O操作不仅囊括了直的文本、网络的读写,还连数据库操作、Web
    Service、HttpRequest以及.net
    Remoting等逾进程的调用。而线程的适用范围则是那种待添加时CPU运算的场子,例如耗时比较丰富之图形处理和算法执行。

32.线程同步

  • 线程同步:就是多独线程在某某对象及实施等(等待给解锁、等待同步信号),直到该对象被解锁或接受信号。被守候的目标要是援类型
  • 锁定:使用要字lock和类Monitor(两者本质上是一模一样的,lock只是Monitor的语法糖),锁定一个目标并创立同截代码的块作用域,同时就同意一个线程进入该代码块,退出代码块常解锁对象,后续线程按梯次上
  • 信号同步:涉及的品类且连续自抽象类WaitHandle,包括SemaphoreMutexEventWaitHandle(子类AutoResetEventManualResetEvent),他们之原理都同样,都是保障一个网基本句柄。
  • EventWaitHandle保障一个由于本产生的布尔变量(阻滞状态),false表示阻塞线程,true则解除阻塞。它的子类AutoResetEvent于实施完Set()方法后会见自行回复状态(每次就于一个WaitOne()方式发信号C#),而ManualResetEvent恍如以尽Set()晚未见面又变更状态,它的拥有WaitOne()主意都能接过信号。只要WaitOne()切莫接到信号,它就是直不通时线程,如下示例:

    public static void Main()
    {
        AutoResetEvent autoReset = new AutoResetEvent(false);
        ManualResetEvent manualReset = new ManualResetEvent(false);
        Thread t1 = new Thread(new ThreadStart(() =>
            {
                autoReset.WaitOne();
                Console.WriteLine("线程t1收到autoReset信号!");
            }));
    
        Thread t2 = new Thread(new ThreadStart(() =>
            {
                autoReset.WaitOne();
                Console.WriteLine("线程t2收到autoReset信号!");
            }));
        t1.Start();
        t2.Start();
        Thread.Sleep(1000);
        autoReset.Set();//t1 t2 只能有一个收到信号
        Thread t3 = new Thread(new ThreadStart(() =>
        {
            manualReset.WaitOne();
            Console.WriteLine("线程t3收到manualReset信号!");
        }));
        Thread t4 = new Thread(new ThreadStart(() =>
        {
            manualReset.WaitOne();
            Console.WriteLine("线程t4收到manualReset信号!");
        }));
        t3.Start();
        t4.Start();
        Thread.Sleep(1000);
        manualReset.Set();//t3 t4都能收到信号
        Console.ReadKey();
    }
    

34.实现c#每隔一段时间执行代码:

计同样:调用线程执行措施,在术被贯彻死循环,每个循环用Thread.Sleep()设定阻塞时(或因故thread.Join());
方法二:使用System.Timers.Timer类;
方法三:使用System.Threading.Timer
实际怎么落实,就无细致说了,看MSDN,或百度

再有很多别样地方的….待续

相关文章