• C#高效编程话题集3(每期10话题)


    前两期地址:

    C#高效编程话题集1(每期10话题)

    C#高效编程话题集2(每期10话题)

    本期话题:

    1:使用属性还是字段

     首先重大区别就是属性实质是方法,所以:
    1:可以为属性添加代码;
    2:可以让属性支持线程安全;见effective c#第一版的第一章;
    3:属性得到了VS编辑器的支持,得以实现自动属性这种功能。
    4:自动属性的特点在LINQ中得到了广泛应用,尤其是匿名类型中,只能实现只读的自动属性,匿名类型不支持字段;
    5:从设计的角度,也就是面向对象的角度,建议使用属性;
    6:如果某个属性仅仅作为类型内部使用,而且不涉及到上面5点内容,则建议使用字段

    还有一点,属性在序列化中有点尴尬,由于属性是方法,所以不能为其指定[NonSerialized]特性。

    2:利用泛型拓宽方法的应用范围

    假设存在方法:

    static void PrintSalary(ISalary<Employee> s)
    {
    s.Pay();
    }

    然后像下面这样使用它:

    ISalary<Programmer> s = new BaseSalaryCounter<Programmer>();
    PrintSalary(s);

    注意,几个主要类的代码:

    interface ISalary<T>
    {
    void Pay();
    }

    class BaseSalaryCounter<T> : ISalary<T>
    {
    public void Pay()
    {
    Console.WriteLine(
    "Pay base salary");
    }
    }

    class Employee
    {
    public string Name { get; set; }
    }
    class Programmer : Employee
    {
    }

    运行结果是:

    无法从“ConsoleApplication4.ISalary<ConsoleApplication4.Programmer>”转换为“ConsoleApplication4.ISalary<ConsoleApplication4.Employee>”

    显然我们认为PrintSalary方法因为能支持ISalary<Employee> ,所以肯定能支持ISalary<Programmer>,这是一种很容易犯的尝试错误。

    改进方法先提供一种思路,就是将该方法改成泛型。

    3:利用协变关键字out

     要解决话题2中的问题,还可以使用out关键字。即修改接口为:

    interface ISalary<out T>
    {
    void Pay();
    }


    4:减少使用类型的静态变量

    1:静态变量一旦被创建不被释放; 

    2:静态变量不是线程安全的,它不像类型变量只对创建类型的那个线程有效;

    5:线程同步有多少种方法?

     所谓线程同步,采用的技术,就是在某个对象上等待(也理解为锁定该对象)。C#中类型对象的分类:引用类型和值类型。在这两种类型上的等待是不一样的,我们也可以简单的理解为在CLR中,值类型是不能被锁定的(参考MSDN的volatile关键字)。

    在引用类型上的等待,又分为两种技术:锁定和信号机制。

    锁定使用:关键字lock和类型Monitor,前者其实是后者的语法糖。这是最常用的同步技术,小伙们应该都用过;

    信号机制:信号机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。它们之间有一定的区别,值得一提的是Mutex能同步不同应用程序域。其它几个类型的不同点MSDN写的很伤人,而且未进行横向比较,简单来说,就是EventWaitHandle通过布尔型进行同步,其中AutoResetEvent又自动恢复类型的阻滞状态,ManualResetEvent则必须手动恢复阻滞状态。Semaphore通过整型进行同步。 

    6:让对象=null能否加速对象被垃圾回收器回收?

     注意,以下描述都只针对引用类型

    1:在方法内,方法参数和局部变量,赋值为null无助于加速被垃圾回收器标识为垃圾;

    2:实例变量,随着实例=null,实例变量也会自动=null。所以,一般情况下,无需显式为实例变量=null,除非你的实例生存周期较长,并且实例变量是个大对象;

    3:静态变量,如果不显式置为null,就永远不会被回收; 

    7:不建议lock(this)

     1:如果两个对象的实例分别执行了lock(this)这样的代码,实际锁定的是两个对象,完全不能达到同步的目的。

    2:最好避免锁定 public 类型或锁定不受应用程序控制的对象实例。例如,如果该实例可以被公开访问,则 lock(this) 可能会有问题,因为不受控制的代码也可能会锁定该对象。这可能导致死锁,即两个或更多个线程等待释放同一对象。出于同样的原因,锁定公共数据类型(相比于对象)也可能导致问题。 

    8:区分计算密集型和I/O密集型的多线程应用场景

    I/O密集型操作。硬盘、网卡、声卡、显卡等都是。CLR所提供的异步编程模型就是让我们充分利用硬件的DMA功能来提高CPU的利用率。

    一个在大多数情况下正确的技巧是,凡是FCL中类型提供了类似BeginDoSomething方法的,都建议使用这个异步调用来完成多线程编码,异步在后台调用线程池线程完成调度,最大化的节约了系统的性能。 

    9:使用Parallel的一个陷阱

     以下的代码的输出是什么? 

    int[] nums = new int[] { 1, 2, 3, 4 };
    long total = 0;
    Parallel.For
    <long>(0, nums.Length, () =>
    {
    return 1;
    }, (i, loopState, subtotal)
    =>
    {
    subtotal
    += nums[i];
    return subtotal;
    },
    (x)
    => Interlocked.Add(ref total, x)
    );
    Console.WriteLine(
    "The total is {0}", total);

    实际上,它有可能是11,较少的情况下会是12,几乎不可能出现13,14。

    要从方法的最后一个参数Action<TLocal> localFinally讲起,它表示的其实是并行中如果新起了一个线程,那么这个线程结束时会调用这个localFinally。 而我们知道并行在后台采用的是线程池,也就是,我们不知道上面代码所发起的这个并行实际会发起多少个线程,如果是1个,自然就是11,如果是2,结果自然就是12了,依此类推。之所以不可能为14,是因为,并发的循环体就只有4个循环,并发显然不会傻到新起4个线程来执行并发。

    10:不建议lock(类型的type)

     由于typeof(SampleClass),是SampleClass的所有实例所共有的,这会导致当前应用程序中的所有SampleClass的实例的线程,将会全部被同步。

    更多内容,期待下一期。。。

  • 相关阅读:
    QSslError 类
    QNetworkRequest 请求类
    QFTP走了以后QNetworkAccessManager出现了
    Android之SQLite总结
    Android之Handler机制
    Android之SeekBar总结(一)
    Android之测试相关知识点
    Android数据储存之SharedPreferences总结
    android studio的常用快捷键
    BitmapFactory.Options详解
  • 原文地址:https://www.cnblogs.com/luminji/p/2019597.html
Copyright © 2020-2023  润新知