1 / 61

DEV312 C# Best Practices

DEV312 C# Best Practices. Juval Löwy www.idesign.net. About Juval Löwy. Software architect Consults and trains on .NET migration and design MS Regional Director for the Silicon Valley Authored Programming .NET components (2003, O’Reilly) COM and .NET component services (2001, O’Reilly)

chick
Download Presentation

DEV312 C# Best Practices

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. DEV312 C# Best Practices Juval Löwy www.idesign.net

  2. About Juval Löwy • Software architect • Consults and trains on .NET migration and design • MS Regional Director for the Silicon Valley • Authored • Programming .NET components (2003, O’Reilly) • COM and .NET component services (2001, O’Reilly) • Participates in the .NET design reviews • Contributing editor and columnist to the Visual Studio Magazine • Contact at www.idesign.net

  3. Defensive Event Publishing • Exceptions thrown by subscribers propagate to publisher • Usually publisher does not care • Problem • Handling exceptions aborts publishing publicclass MySource { publicevent EventHandler MyEvent; publicvoid FireEvent(){ try { if(MyEvent != null) MyEvent(this,EventArgs.Empty); } catch{} } }

  4. Defensive Event Publishing • Can iterate manually over delegate's internal invocation list • GetInvocationList()method of delegate • Obtain target collection • Publish to each target • Catch individual exceptions • Problem: • Code is not generic

  5. Defensive Event Publishing publicclass MySource { publicevent EventHandler MyEvent; publicvoid FireEvent() { if(MyEvent == null) { return; } Delegate[] delegates = MyEvent.GetInvocationList(); foreach(Delegate del in delegates) { EventHandler sink = (EventHandler)del; try { sink(this,EventArgs.Empty); } catch{} } } }

  6. Defensive Event Publishing • Can abstract to a generic utility EventsHelper • Fire() method • params object[], uses DynamicInvoke() publicdelegatevoid SomeDelegate(int num,string str); publicclass MySource { publicevent SomeDelegate SomeEvent; publicvoid FireEvent(int num, string str) { EventsHelper.Fire(SomeEvent,num,str); } }

  7. Defensive Event Publishing • DynamicInvoke() of each delegate to actually invoke publicclass EventsHelper { publicstaticvoid Fire(Delegate del,paramsobject[] args) { if(del == null) { return; } Delegate[] delegates = del.GetInvocationList(); foreach(Delegate sink in delegates) { try { sink.DynamicInvoke(args); } catch{} } } }

  8. Asynchronous Events • Cannot just call BeginInvoke() • must have single target • Iterate over the list manually publicclass MySource { publicevent MyDelegate OnNumberEvent; publicvoid FireAsynch(int num) { Delegate[] delegates = OnNumberEvent.GetInvocationList(); foreach(Delegate del in delegates) { MyDelegate sink = (MyDelegate)del; sink.BeginInvoke(num,null,null); } } }

  9. Asynchronous Events • Can automate • The EventsHelper Class publicclass MySource { publicevent MyDelegate OnNumberEvent; publicvoid FireAsynch(int num) { EventsHelper.FireAsync(OnNumberEvent,num); } }

  10. publicclass EventsHelper { publicstaticvoid FireAsync(Delegate del,paramsobject[] args) { if(del == null) { return; } Delegate[] delegates = del.GetInvocationList(); AsyncFire asyncFire; foreach(Delegate sink in delegates) { asyncFire = new AsyncFire(InvokeDeleagte); asyncFire.BeginInvoke(sink,args,null,null); } } delegatevoid AsyncFire(Delegate del,object[] args); [OneWay] staticvoid InvokeDeleagte(Delegate del,object[] args) { del.DynamicInvoke(args); } }

  11. Interface or abstract class? • What are abstract classes • What are abstract methods • What are interfaces • Interfaces vs abstract

  12. Abstract Class • Denoted with abstract • Cannot instantiate class • Like C++ pure virtual class • Used to dictate interface and provide common behavior, while forcing sub classes to have own implementation • Sub class can still call base-class methods • Sub class is not abstract

  13. Abstract Method • Denoted with abstract • Method cannot have implementation • Method is by definition virtual • Cannot add virtual modifier • Cannot instantiate class • Class must be abstract too • Sub class must override • Otherwise considered abstract as well

  14. Interfaces • interface keyword defines a type that • Cannot have implementation (all methods are abstract) • Cannot be instantiated (like abstract class) //This definition publicinterface IMyInterface { void Method1(); void Method2(); void Method3(); } //is almost equivalent to this one publicabstractclass MyInterface { abstractpublicvoid Method1(); abstractpublicvoid Method2(); abstractpublicvoid Method3(); }

  15. Interface or abstract class? • Abstract class can still have implementation • Class can derive from only one base class • Can derive from multiple interfaces • Abstract class can derive from any other class or interface(s) • Interface can only derive from other interfaces • But can derive from multiple interfaces

  16. Interface or abstract class? • Abstract class can have non-public members • Can have constructors static members and constants • All interface members are public • Differences are deliberate to provide a formal public contract • COM-like semantics • Logical grouping of related methods

  17. Interface or abstract class? • Can and should combine interfaces with class hierarchy publicinterface ITrace { void TraceSelf(); } publicclass A : ITrace { publicvirtualvoid TraceSelf(){Trace.WriteLine("A");} } publicclass B : A { publicoverridevoid TraceSelf(){Trace.WriteLine("B");} } publicclass C : B { publicoverridevoid TraceSelf(){Trace.WriteLine("C");} } ITrace trace = new B(); trace.TraceSelf(); //output: "B"

  18. Interface or abstract class? • Can use abstract class and interface publicinterface ITrace { void TraceSelf(); } publicabstractclass A : ITrace { publicvirtualvoid TraceSelf(){Trace.WriteLine("A");} } publicclass B : A { publicoverridevoid TraceSelf(){Trace.WriteLine("B");} } publicclass C : B { publicoverridevoid TraceSelf(){Trace.WriteLine("C");} } ITrace trace = new B(); trace.TraceSelf(); //output: "B"

  19. Interface or abstract class? • Can use abstract method and interface • Rare publicinterface ITrace { void TraceSelf(); } publicabstractclass A : ITrace { publicabstractvoid TraceSelf(); } publicclass B : A { publicoverridevoid TraceSelf(){Trace.WriteLine("B");} } publicclass C : B { publicoverridevoid TraceSelf(){Trace.WriteLine("C");} } ITrace trace = new B(); trace.TraceSelf(); //output: "B"

  20. Interfaces and Structs • Can provide base interface for structs • Should use properties only • Benefits of polymorphism, even though structs cannot derive from a common base struct

  21. publicinterface IMyBaseStruct { int SomeNumber{ get; set; } string SomeString{ get; set; } } struct MyStruct : IMyBaseStruct { publicint SomeNumber { get{…} set{…} } publicstring SomeString { get{…} set{…} } } struct MyOtherStruct : IMyBaseStruct { publicint SomeNumber { get{…} set{…} } publicstring SomeString { get{…} set{…} } } publicclass MyClass { publicvoid DoWork(IMyBaseStruct storage){…} }

  22. Interface Factoring • Is this a good design?

  23. Interface Factoring • Is this a good design?

  24. Interface Factoring • Is this a good design?

  25. Interfaces Factoring • Balance number of modules with effort Total software cost Minimum cost Cost or Effort Cost to interface Cost / module Number of modules

  26. Interfaces Factoring • When factoring interface, think always in terms of reusable elements • Example: a dog interface • Requirements • Bark • Fetch • Veterinarian clinic registration number • A property for having received shots

  27. Interfaces Factoring • Could define IDog • This interface is not well factored • Bark() and Fetch() are more logically related to each other than to VetClinicNumber and HasShots publicinterface IDog{void Fetch();void Bark();long VetClinicNumber{ get; set; }bool HasShots{ get; set; }} publicclass Poodle : IDog{…}  publicclass GermanShepherd : IDog{…}

  28. Interfaces Factoring • Better factoring: publicinterface IPet{long VetClinicNumber{ get; set; }bool HasShots{ get; set; }}publicinterface IDog{void Fetch();void Bark();}publicinterface ICat{void Purr();void CatchMouse();}publicclass Poodle : IDog,IPet{…} publicclass Siamese : ICat, IPet{…}

  29. Interfaces Factoring • If operations are logically related, but repeated, factor to hierarchy of interfaces publicinterface IMammal{void ShedFur();void Lactate();} publicinterface ICat : IMammal{void Purr();void CatchMouse();} publicinterface IDog : IMammal{void Fetch();void Bark();}

  30. Interface Factoring Metrics • Interface factoring results in interfaces with fewer members • Balance out two counter forces • Too many granular interfaces Vs few complex, poorly factored interfaces • Just one member is possible, but avoid it • Dull facet • Too many parameters • Too coarse: should be factored into several methods • Refactor into an existing interface • Optimal number 3 to 5 • No more than 20 (12)

  31. Interface Factoring Metrics • Ratio of methods, properties and events • Interfaces should have more methods than properties • Just-enough-encapsulation • Ratio of at least 2:1 • Exception is interfaces with properties only • Should have no methods • Avoid defining events

  32. .NET Factoring Metrics • 300+ interfaces examined • On average, 3.75 members per interface • Methods to properties ratio of 3.5:1 • Less than 3 percent of the members are events • On average, .NET interfaces are well factored

  33. Finalization and Destructor • In C#, even if you add a destructor, the compilers converts it to Finalize() • In C#, do not use Finalize(), always use destructor

  34. Finalization and Destructor publicclass MyClass { public MyClass(){} ~MyClass(){} } /// code that is actually generated: public class MyClass { public MyClass(){} protected virtual void Finalize() { try { //Your destructor code goes here } finally { base.Finalize();//everybody has one, from Object } } }

  35. IDisposable • Object implements System.IDisposable interface publicinterface IDisposable { void Dispose(); } publicinterface IMyInterface { void MyMethod(); } publicclass MyClass : IMyInterface,IDisposable { publicvoid MyMethod(){} publicvoid Dispose() { //do object cleanup, call base.Dispose() if has one } }

  36. IDisposable • Client uses domain-methods, and then tries to dispose: IMyInterface obj; obj = new MyClass(); obj.MyMethod(); //Client wants to expedite whatever needs expediting: IDisposable disposable = obj as IDisposable; if(disposable != null) disposable.Dispose();

  37. Disposing and Error Handling • Should use a try/finally blocks to dispose of objects • Code gets messy if multiple objects are involved MyClass obj; Obj = new MyClass(); try { obj.SomeMethod(); } finally { IDisposable disp; disp = (IDisposable)obj; disp.Dispose(); }

  38. Disposing and Error Handling • To automate, use the using(x) statement • Only is C# for now • Type must derive from IDisposable • Cannot use with interfaces • Unless derived from IDisposable MyClass obj; obj = new MyClass(); using(obj) { obj.SomeMethod(); }

  39. Disposing and Error Handling • Can stack multiple using statements MyClass obj1; MyClass obj2; MyClass obj3; obj1 = new MyClass(); obj2 = new MyClass(); obj3 = new MyClass(); using(obj1) using(obj2) using(obj3) { obj1.SomeMethod(); obj2.SomeMethod(); obj3.SomeMethod(); }

  40. using and Interfaces • using statement requires type to support IDisposable class MyClass : IDisposable {…} MyClass obj; Obj = new MyClass(); try { obj.SomeMethod(); } finally { IDisposable disp; disp = obj; disp.Dispose(); }

  41. using and Interfaces • Does not work in general with interfaces • Even if underlying type supports IDisposable interface IMyInterface { void SomeMethod(); } class MyClass : IMyInterface,IDisposable {…} IMyInterface obj; obj = new MyClass(); using(obj) //this does not compile { obj.SomeMethod(); }

  42. using and Interfaces • Solution #1 • Derive all interfaces from IDisposable interface IMyInterface : IDisposable { void SomeMethod(); } class MyClass : IMyInterface {…} IMyInterface obj; obj = new MyClass(); using(obj) { obj.SomeMethod(); }

  43. using and Interfaces • Solution #2 • Explicit casting to IDisposable interface IMyInterface { void SomeMethod(); } class MyClass : IMyInterface,IDisposable {…} IMyInterface obj; obj = new MyClass(); using((IDisposable)obj) { obj.SomeMethod(); }

  44. Killing a Thread • Do not call Abort() • Thread may need to do clean-up • Abort() does not allow graceful exit • Abort() is not guaranteed to work • Thread can do indefinite processing in catch{} • ResetAbort(), suspended, interop calls • The thread method should check a flag • Protect flag with a lock • Kill() method should set flag and wait for thread to terminate

  45. public class WorkerThread : IDisposable { protected Thread m_ThreadObj; protected bool m_EndLoop; protected Mutex m_EndLoopMutex; protected bool EndLoop { set { m_EndLoopMutex.WaitOne(); m_EndLoop = value; m_EndLoopMutex.ReleaseMutex(); } get { bool result = false; m_EndLoopMutex.WaitOne(); result = m_EndLoop; m_EndLoopMutex.ReleaseMutex(); returnresult; } } public WorkerThread() { m_EndLoop = false; m_ThreadObj = null; m_EndLoopMutex = new Mutex(); }

  46. public class WorkerThread : IDisposable { public void Start() { m_ThreadObj = Thread.CurrentThread; int i = 0; while(EndLoop == false) { //do work here } } //Kill is called on client thread - must use cached thread object public void Kill() { Debug.Assert(m_ThreadObj != null); if(m_ThreadObj.IsAlive == false) return; EndLoop = true; //Wait for thread to die m_ThreadObj.Join(); if(m_EndLoopMutex != null) m_EndLoopMutex.Close(); } //Rest of WorkerThread

  47. Thread Handle • Thread does not provide a WaitHandle • Unlike Windows • Join() is not good enough • Cannot combine in multiple wait operation • No atomic wait • Have the wrapper class expose a event handle • Signaled when the thread method exists • In finally statement

  48. publicclass WorkerThread { protected ManualResetEvent m_ThreadHandle; public WorkerThread() { m_ThreadHandle = new ManualResetEvent(false); //More initialization } public WaitHandle Handle { get { return m_ThreadHandle; } } privatevoid Run() { try {//Do work here } finally { m_ThreadHandle.Set(); } } //Rest of the implementation }

  49. Preparing for generics • Simple ArrayList will be easily replaced with a type-specific collection • You can create your own type-specific collections now publicclass IntQueue { Queue m_Queue = new Queue(); publicvirtualvoid Enqueue(int num) { m_Queue.Enqueue(num); } publicint Dequeue() { int num = (int)m_Queue.Dequeue(); return num; } /* Other wrapepr methods */ }

  50. More at TechEd • C# Best Practices • DEV312 • Distributed Security Practices • DEV354 • Building High-Performance Applications with Visual Studio .NET • DEV326 • Application and Library Versioning • DEV343 • Software Legends – VS.NET and C# tips and tricks • SWL004

More Related