stl高效编程,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 s) { s.Pay(); }
然后像下面这样使用它:
ISalary s = new BaseSalaryCounter(); PrintSalary(s);
注意,几个主要类的代码:
interface ISalary { void Pay(); } class BaseSalaryCounter : ISalary { public void Pay() { Console.WriteLine("Pay base salary"); } } class Employee { public string Name { get; set; } } class Programmer : Employee { }
运行结果是:
无法从“ConsoleApplication4.ISalary”转换为“ConsoleApplication4.ISalary
显然我们认为PrintSalary方法因为能支持ISalary ,所以肯定能支持ISalary,这是一种很容易犯的尝试错误。
改进方法先提供一种思路,就是将该方法改成泛型。 3:利用协变关键字out
要解决话题2中的问题,还可以使用out关键字。即修改接口为:
interface ISalary { 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(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 localFinally讲起,它表示的其实是并行中如果新起了一个线程,那么这个线程结束时会调用这个localFinally。 而我们知道并行在后台采用的是线程池,也就是,我们不知道上面代码所发起的这个并行实际会发起多少个线程,如果是1个,自然就是11,如果是2,结果自然就是12了,依此类推。之所以不可能为14,是因为,并发的循环体就只有4个循环,并发显然不会傻到新起4个线程来执行并发。 10:不建议lock(类型的type)
由于typeof(SampleClass),是SampleClass的所有实例所共有的,这会导致当前应用程序中的所有SampleClass的实例的线程,将会全部被同步。
更多内容,期待下一期。。。
Tags:  编程话题话题 强心脏每期嘉宾 高效编程 stl高效编程

延伸阅读

最新评论

发表评论