长远领悟C#:编制程序技巧总计(贰)

原创小说,转发请评释出处! 以下总计参阅了:MSDN文书档案、《C#高档编制程序》、《C#本质论》、前辈们的博客等质感,如有不得法的地方,请扶助及时建议!防止误导!

在上1篇 深远领悟C#:编制程序技巧计算(1) 中总括了25点,那一篇再三再四:

二陆.体系化与反连串化

  • 利用的场子:
    造福保存,把持有运市场价格况的对象体系化后保存到地头,在下次运维程序时,反类别化该目的来还原状态
    有利于传输,在网络中传输类别化后的对象,接收方反类别化该对象还原
    复制黏贴,复制到剪贴板,然后黏贴

  • 用来协助方向和反种类化的表征:在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;
        }
    }
}

2七.老大处理:抛出13分是索要消耗品质的(但针锋相对于低可能率事件,那点品质影响是卑不足道的)

  • 不要运用卓殊处理体制来兑现控制流的更换
  • 决不对能预见到的大可能率、可过来的错误抛出很是,而应该用实际代码来拍卖恐怕出现的不当
  • 仅在为了防止出现小概率预感错误、不可能预见的错误和不能够处理的意况才尝试抛出万分(如:运转代码会促成内部存款和储蓄器泄漏、财富不可用、应用程序状态不行恢复生机,则必要抛出万分)
  • 若要把错误展现给最终的用户,则应超越捕获该特别,对灵活音信进行李包裹装后,重新抛出二个出示本人消息的新至极
  • 底层代码引发的越发对于高层代码没有意义时,则足以捕获该尤其,并再度引发意思显然的卓殊
  • 在重复引发那么些时,总是为新至极对象提供Inner
    Exception对象参数(不须求提供消息时最佳直接用空的throw;语句,它会把原来格外对象重新抛出),该对象保存了旧很是的满贯信息,包含丰裕调用栈,便于代码调节和测试
  • 用万分处理代替再次回到错误代码的秘诀,因为再次来到错误代码不便宜维护
  • 相对不要捕获在脚下上下文中不能处理的那多少个,不然就恐怕创制了2个掩蔽的很深的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块总是会被执行

2捌.八线程的不行处理

  • 在线程上若有未处理的不得了,则会触发过程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); //触发!!!
        }
    }
    

2玖.自定义万分

  • 仅在有出色须要的时候才使用自定义相当
  • 为了酬答不相同的事体环境,能够在底层捕获各个业务环境恐怕引发的相当(如使用不相同的数据库类型等),然后都抛出2个联合实行的自定义卓殊给调用者,那样壹来,调用者只要捕获该自定义万分类型即可
  • 让自定义1贰分类派生自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.在CL猎豹CS六中艺术的实践进度:

  • 首先将参数值依次存进内部存款和储蓄器栈,执行代码的经过中,会依照须要去栈中取用参数值
  • 遇上return语句时,方法再次来到,并把return语句的结果值存入栈顶,这几个值就是最终的再次来到值
  • 若方法内设有finally块,则便是在try块中有return语句,最终也会在履行finlly块之后才脱离办法,在那种景色下,若重返值的类型为值类型,则在finally块中对回到变量的修改将无济于事,方法的末梢再次来到值都以基于return语句压入栈顶中的值(对于引用类型,再次来到值只是2个引用,能成功修改该引用指向的靶子,但对该引用笔者的修改也是没用的),如下:

    //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本身的改变不影响返回值对象
    

3壹.线程池与线程的差别

  • 线程:通过System.Threading.Thread类开辟的线程,用完就自动销毁,不可重用。主要用以密集型复杂运算
  • 线程池:由System.Threading.ThreadPool类管理的1组线程,可选拔。主要用于I/O等异步操作。线程池中的一条线程职务成功后,该线程不会自行销毁。相反,它会以挂起状态再次来到线程池。假使应用程序再一次向线程池发出请求,那么这一个挂起的线程将激活并施行任务,而不会创制新线程。那节约了广大支付。只要线程池中应用程序职分的排队速度低于3个线程处理每项义务的快慢,那么就能够屡屡重用同壹线程,从而在应用程序生存期内节约多量开发。
  • 线程池能够提供八种意义:异步调用方法、以自然的小时距离调用方法、当单个内核对象获得实信号文告时调用方法、当异步
    I/O 请求截止时调用方法

32.二十四线程和异步的分别

  • 异步操作的真面目:是硬件的功效,不消耗CPU财富。硬件在接到CPU的指令后,本身一向和内存交换数据,完结后会触发2个停顿来打招呼操作达成(如:委托的BeginInvoke()措施,执行该办法时,在线程池ThreadPool中启用一条线程来处理义务,完毕后会调用方法参数中钦点的回掉函数,线程池中的线程是分配好的,使用时不须求new操作)
  • 线程的原形:是操作系统提供的一种逻辑功效,它是经过中一段并发运转的代码,线程要求操作系统投入CPU财富来运转和调度
  • 异步操作的利害:因为异步操作无须额外的线程负担,并且使用回调的秘籍开始展览处理,在规划美貌的动静下,处理函数能够不必选取共享变量(尽管不恐怕完全不用,最起码能够减中国少年共产党享变量的多寡),收缩了死锁的或是。当然异步操作也并非全盘无暇。编写异步操作的复杂程度较高,程序首要利用回调格局开始展览处理,与老百姓的合计方法有点
    出入,而且难以调节和测试。
  • 多线程的利害:八线程的长处很分明,线程中的处理程序还是是逐1执行,符合老百姓的思维习惯,所以编制程序简单。可是三10贰线程的瑕疵也1如既往明显,线程的运用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量大概导致死锁的出现。
  • 哪一天使用:当要求实践I/O操作时,应该采纳异步操作。I/O操作不仅囊括了间接的文件、网络的读写,还包含数据库操作、Web
    Service、HttpRequest以及.net
    Remoting等跨进度的调用。而线程的适用范围则是那种须求长日子CPU运算的地方,例如耗费时间较长的图形处理和算法执行。

3二.线程同步

  • 线程同步:正是七个线程在有些对象上实行等待(等待被解锁、等待同步确定性信号),直到该指标被解锁或接收频域信号。被等候的靶子必须是引用类型
  • 锁定:使用首要字lock和类型Monitor(两者本质上是千篇1律的,lock只是Monitor的语法糖),锁定3个对象并创办壹段代码的块成效域,同时只同意3个线程进入该代码块,退出代码块时解锁对象,后续线程按顺序进入
  • 随机信号同步:涉及的种类都一连自抽象类WaitHandle,包括SemaphoreMutexEventWaitHandle(子类AutoResetEventManualResetEvent),他们的法则都一模一样,都以珍贵二个系统基本句柄。
  • EventWaitHandle护卫二个由基础发生的布尔变量(阻滞状态),false表示阻塞线程,true则解除阻塞。它的子类AutoResetEvent在执行完Set()方法后会自动还原状态(每一遍只给三个WaitOne()方法发信号),而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,或百度

再有许多别样方面包车型地铁….待续

相关文章